diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..48c2d72 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 6400354103899fbbbf41c6e1946ad719 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_images/accessing-admin.png b/_images/accessing-admin.png new file mode 100644 index 0000000..7a3ab23 Binary files /dev/null and b/_images/accessing-admin.png differ diff --git a/_images/activate_fci.png b/_images/activate_fci.png new file mode 100644 index 0000000..f4f2fa3 Binary files /dev/null and b/_images/activate_fci.png differ diff --git a/_images/apply_control.png b/_images/apply_control.png new file mode 100644 index 0000000..b08b1e9 Binary files /dev/null and b/_images/apply_control.png differ diff --git a/_images/block_parameters_apply_control.png b/_images/block_parameters_apply_control.png new file mode 100644 index 0000000..3882cb9 Binary files /dev/null and b/_images/block_parameters_apply_control.png differ diff --git a/_images/cartesian_impedance_control_apps.png b/_images/cartesian_impedance_control_apps.png new file mode 100644 index 0000000..c0c07fc Binary files /dev/null and b/_images/cartesian_impedance_control_apps.png differ diff --git a/_images/cartesian_impedance_control_hardware.png b/_images/cartesian_impedance_control_hardware.png new file mode 100644 index 0000000..7731cdf Binary files /dev/null and b/_images/cartesian_impedance_control_hardware.png differ diff --git a/_images/collision-coarse.png b/_images/collision-coarse.png new file mode 100644 index 0000000..31379e4 Binary files /dev/null and b/_images/collision-coarse.png differ diff --git a/_images/collision-fine.png b/_images/collision-fine.png new file mode 100644 index 0000000..f6d4c98 Binary files /dev/null and b/_images/collision-fine.png differ diff --git a/_images/control-static-ip.png b/_images/control-static-ip.png new file mode 100644 index 0000000..ee4cc79 Binary files /dev/null and b/_images/control-static-ip.png differ diff --git a/_images/control.png b/_images/control.png new file mode 100644 index 0000000..c4e1f1b Binary files /dev/null and b/_images/control.png differ diff --git a/_images/dh-diagram.png b/_images/dh-diagram.png new file mode 100644 index 0000000..34bfab4 Binary files /dev/null and b/_images/dh-diagram.png differ diff --git a/_images/edit-connections.png b/_images/edit-connections.png new file mode 100644 index 0000000..8b063d4 Binary files /dev/null and b/_images/edit-connections.png differ diff --git a/_images/fci-architecture-non-realtime.png b/_images/fci-architecture-non-realtime.png new file mode 100644 index 0000000..cf37d8e Binary files /dev/null and b/_images/fci-architecture-non-realtime.png differ diff --git a/_images/fci-architecture.png b/_images/fci-architecture.png new file mode 100644 index 0000000..695ec18 Binary files /dev/null and b/_images/fci-architecture.png differ diff --git a/_images/fci-on.png b/_images/fci-on.png new file mode 100644 index 0000000..6f4b7e6 Binary files /dev/null and b/_images/fci-on.png differ diff --git a/_images/frames.svg b/_images/frames.svg new file mode 100644 index 0000000..151096c --- /dev/null +++ b/_images/frames.svg @@ -0,0 +1,449 @@ + + + + + + + + + + image/svg+xml + + + + + + + + G + + + + panda_link8 + + <arm_id>_link8 + Flange Frame + + + + panda_hand + + <arm_id>_hand + <arm_id>link8 rotated by -45 degree around Z-axis + + + + panda_link8->panda_hand + + + transform defined in URDF + + + + panda_NE + + <arm_id>_NE + Nominal End-Effector Frame + + + + panda_link8->panda_NE + + + transform defined in Desk + + + + panda_hand_tcp + + <arm_id>_hand_tcp + Frame between the fingers of the hand + Is equal to <arm_id>_NE if the end-effector + was set to 'Franka Hand' inside Desk + + (can be changed by passing 'tcp_xyz' & 'tcp_rpy') + + + + panda_hand->panda_hand_tcp + + + transform defined in URDF + + + + panda_EE + + <arm_id>_EE + End-Effector Frame + Used for Cartesian Pose interface and inverse kinematics + Equal to <arm_id>_NE by default + + + + panda_NE->panda_EE + + +  transform defined via set_EE_frame + + + + panda_K + + <arm_id>_K + Stiffness frame K + Used for measuring forces + Equal to <arm_id>_EE by default + + + + panda_EE->panda_K + + +  transform defined via set_K_frame + + + diff --git a/_images/franka-gazebo-example-grasp.png b/_images/franka-gazebo-example-grasp.png new file mode 100644 index 0000000..5dd2ae8 Binary files /dev/null and b/_images/franka-gazebo-example-grasp.png differ diff --git a/_images/franka-gazebo-example.png b/_images/franka-gazebo-example.png new file mode 100644 index 0000000..172dfd5 Binary files /dev/null and b/_images/franka-gazebo-example.png differ diff --git a/_images/franka_matlab_toolbox_examples.png b/_images/franka_matlab_toolbox_examples.png new file mode 100644 index 0000000..b537981 Binary files /dev/null and b/_images/franka_matlab_toolbox_examples.png differ diff --git a/_images/get_duration.png b/_images/get_duration.png new file mode 100644 index 0000000..9ba0545 Binary files /dev/null and b/_images/get_duration.png differ diff --git a/_images/get_gripper_state.png b/_images/get_gripper_state.png new file mode 100644 index 0000000..4fc6756 Binary files /dev/null and b/_images/get_gripper_state.png differ diff --git a/_images/get_model.png b/_images/get_model.png new file mode 100644 index 0000000..3e14026 Binary files /dev/null and b/_images/get_model.png differ diff --git a/_images/get_robot_state.png b/_images/get_robot_state.png new file mode 100644 index 0000000..6081509 Binary files /dev/null and b/_images/get_robot_state.png differ diff --git a/_images/get_robot_state_settings.png b/_images/get_robot_state_settings.png new file mode 100644 index 0000000..8d91e8d Binary files /dev/null and b/_images/get_robot_state_settings.png differ diff --git a/_images/libfranka-architecture.png b/_images/libfranka-architecture.png new file mode 100644 index 0000000..9a1286b Binary files /dev/null and b/_images/libfranka-architecture.png differ diff --git a/_images/matlab_pick_and_place_with_RRT_demo.png b/_images/matlab_pick_and_place_with_RRT_demo.png new file mode 100644 index 0000000..bdf8077 Binary files /dev/null and b/_images/matlab_pick_and_place_with_RRT_demo.png differ diff --git a/_images/move-groups.png b/_images/move-groups.png new file mode 100644 index 0000000..e2b84d4 Binary files /dev/null and b/_images/move-groups.png differ diff --git a/_images/pbv_equations_max.svg b/_images/pbv_equations_max.svg new file mode 100644 index 0000000..26cba4d --- /dev/null +++ b/_images/pbv_equations_max.svg @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_images/pbv_equations_min.svg b/_images/pbv_equations_min.svg new file mode 100644 index 0000000..1b9665b --- /dev/null +++ b/_images/pbv_equations_min.svg @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_images/pbv_limits_j1.svg b/_images/pbv_limits_j1.svg new file mode 100644 index 0000000..4edacf1 --- /dev/null +++ b/_images/pbv_limits_j1.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j2.svg b/_images/pbv_limits_j2.svg new file mode 100644 index 0000000..906af35 --- /dev/null +++ b/_images/pbv_limits_j2.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j3.svg b/_images/pbv_limits_j3.svg new file mode 100644 index 0000000..909901f --- /dev/null +++ b/_images/pbv_limits_j3.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j4.svg b/_images/pbv_limits_j4.svg new file mode 100644 index 0000000..7c41c43 --- /dev/null +++ b/_images/pbv_limits_j4.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j5.svg b/_images/pbv_limits_j5.svg new file mode 100644 index 0000000..4f30758 --- /dev/null +++ b/_images/pbv_limits_j5.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j6.svg b/_images/pbv_limits_j6.svg new file mode 100644 index 0000000..5279f37 --- /dev/null +++ b/_images/pbv_limits_j6.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pbv_limits_j7.svg b/_images/pbv_limits_j7.svg new file mode 100644 index 0000000..dc985ce --- /dev/null +++ b/_images/pbv_limits_j7.svg @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_images/pop_up_fci.png b/_images/pop_up_fci.png new file mode 100644 index 0000000..c43bee0 Binary files /dev/null and b/_images/pop_up_fci.png differ diff --git a/_images/ros-architecture.png b/_images/ros-architecture.png new file mode 100644 index 0000000..41000cf Binary files /dev/null and b/_images/ros-architecture.png differ diff --git a/_images/rt-interfaces.png b/_images/rt-interfaces.png new file mode 100644 index 0000000..f40e894 Binary files /dev/null and b/_images/rt-interfaces.png differ diff --git a/_images/rt-loop.png b/_images/rt-loop.png new file mode 100644 index 0000000..0cf9d5d Binary files /dev/null and b/_images/rt-loop.png differ diff --git a/_images/simulink_library_browser.png b/_images/simulink_library_browser.png new file mode 100644 index 0000000..41f23d2 Binary files /dev/null and b/_images/simulink_library_browser.png differ diff --git a/_images/simulink_model_apply_control_only.png b/_images/simulink_model_apply_control_only.png new file mode 100644 index 0000000..07e58cb Binary files /dev/null and b/_images/simulink_model_apply_control_only.png differ diff --git a/_images/simulink_model_apply_control_only_build_error.png b/_images/simulink_model_apply_control_only_build_error.png new file mode 100644 index 0000000..a7606ca Binary files /dev/null and b/_images/simulink_model_apply_control_only_build_error.png differ diff --git a/_images/simulink_model_apply_control_only_fix.png b/_images/simulink_model_apply_control_only_fix.png new file mode 100644 index 0000000..d2920d6 Binary files /dev/null and b/_images/simulink_model_apply_control_only_fix.png differ diff --git a/_images/static-ip-ubuntu.png b/_images/static-ip-ubuntu.png new file mode 100644 index 0000000..401884b Binary files /dev/null and b/_images/static-ip-ubuntu.png differ diff --git a/_images/visual.png b/_images/visual.png new file mode 100644 index 0000000..65bddd6 Binary files /dev/null and b/_images/visual.png differ diff --git a/_images/vscode.png b/_images/vscode.png new file mode 100644 index 0000000..5eb8d58 Binary files /dev/null and b/_images/vscode.png differ diff --git a/_images/workspace_parameters.png b/_images/workspace_parameters.png new file mode 100644 index 0000000..788432e Binary files /dev/null and b/_images/workspace_parameters.png differ diff --git a/_sources/compatibility.rst.txt b/_sources/compatibility.rst.txt new file mode 100644 index 0000000..920c71c --- /dev/null +++ b/_sources/compatibility.rst.txt @@ -0,0 +1,63 @@ +Compatible versions +=================== + +.. _compatibility-libfranka: + +Compatibility with libfranka +---------------------------- + +Different versions of open source components compatible. +The following table provides an overview. It is recommended to always work with up-to-date versions. +The ``>=`` indicates that it is not tested against newer robot system versions, meaning +compatibility is not guaranteed (i.e. libfranka 0.2.0 is not compatible with the robot +system version 4.0.0). + +The Robot system versions 2.x.x are not listed in the table below, +but are included as compatible with the Robot system version >= 1.3.0. Therefore, these are +compatible with the libfranka version 0.4.0 and 0.5.0. + ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| Robot system version | libfranka version | Robot / Gripper | franka_ros version | Ubuntu / ROS | +| | | Server version | | | ++======================+===================+=================+====================+=================+ +| >= 5.2.0 (FR3) | >= 0.10.0 | 6 / 3 | >= 0.10.0 | 20.04 / noetic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 4.2.1 (FER) | >= 0.9.1 < 0.10.0 | 5 / 3 | >= 0.8.0 | 20.04 / noetic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 4.0.0 (FER) | >= 0.8.0 | 4 / 3 | >= 0.8.0 | 20.04 / noetic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 3.0.0 (FER) | 0.7.1 | 3 / 3 | 0.7.0 | 18.04 / melodic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 1.3.0 (FER) | 0.5.0 | 3 / 2 | 0.6.0 | 16.04 / kinetic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 1.2.0 (FER) | 0.3.0 | 2 / 2 | 0.4.0 | 16.04 / kinetic | ++----------------------+-------------------+-----------------+--------------------+-----------------+ +| >= 1.1.0 (FER) | 0.2.0 | 2 / 1 | | | ++----------------------+-------------------+-----------------+--------------------+-----------------+ + +`Robot version line 17 +`_ +and `Gripper version line 17 +`_ +are part of libfranka-common repository, a submodule of libfranka repository. + +Franka MATLAB® compatible versions are located :ref:`here`. + +.. caution:: + Franka Robotics currently does not provide any support for Windows or Arm + +Compatibility with the kernel +----------------------------- + +There are different kernels, which are compatible with different Ubuntu system versions. +The following table gives an overview of recommended Kernels. + ++----------------+----------------------+ +| Kernel version | Ubuntu | ++================+======================+ +| 5.9.1 | 20.04 (Focal Fossa) | ++----------------+----------------------+ +| 5.4.19 | 18.04 (Bionic) | ++----------------+----------------------+ +| 4.14.12 | 16.04 (Xenial Xerus) | ++----------------+----------------------+ diff --git a/_sources/control_parameters.rst.txt b/_sources/control_parameters.rst.txt new file mode 100644 index 0000000..eb29a50 --- /dev/null +++ b/_sources/control_parameters.rst.txt @@ -0,0 +1,275 @@ +.. _control_parameters_specifications: + +Robot and interface specifications +=================================== +Realtime control commands sent to the robot should fulfill *recommended* and *necessary* +conditions. Recommended conditions should be fulfilled to ensure optimal operation of the +robot. If necessary conditions are not met then the motion will be aborted. + +The final robot trajectory is the result of processing the user-specified trajectory ensuring +that recommended conditions are fulfilled. As long as necessary conditions are met, the robot +will try to follow the user-provided trajectory but it will only match the final trajectory +if it also fulfills recommended conditions. If the necessary conditions are violated, an error +will abort the motion: if, for instance, the first point of the user defined joint trajectory +is very different from robot start position (:math:`q(t=0) \neq q_c(t=0)`) a ``start_pose_invalid`` error +will abort the motion. + +Values for the constants used in the equations below are shown in the `Limits for Panda`_ and `Limits for Franka Research 3`_ section. + +Joint trajectory requirements +----------------------------- + +Necessary conditions +******************** + +- :math:`q_{min} < q_c < q_{max}` +- :math:`-\dot{q}_{max} < \dot{q}_c < \dot{q}_{max}` +- :math:`-\ddot{q}_{max} < \ddot{q}_c < \ddot{q}_{max}` +- :math:`-\dddot{q}_{max} < \dddot{q}_c < \dddot{q}_{max}` + +Recommended conditions +********************** + +- :math:`-{\tau_j}_{max} < {\tau_j}_d < {\tau_j}_{max}` +- :math:`-\dot{\tau_j}_{max} < \dot{\tau_j}_d < \dot{\tau_j}_{max}` + +At the beginning of the trajectory, the following conditions should be fulfilled: + +- :math:`q = q_c` +- :math:`\dot{q}_{c} = 0` +- :math:`\ddot{q}_{c} = 0` + +At the end of the trajectory, the following conditions should be fulfilled: + +- :math:`\dot{q}_{c} = 0` +- :math:`\ddot{q}_{c} = 0` + +Cartesian trajectory requirements +--------------------------------- + +Necessary conditions +******************** + +- :math:`T` is proper transformation matrix +- :math:`-\dot{p}_{max} < \dot{p_c} < \dot{p}_{max}` (Cartesian velocity) +- :math:`-\ddot{p}_{max} < \ddot{p_c} < \ddot{p}_{max}` (Cartesian acceleration) +- :math:`-\dddot{p}_{max} < \dddot{p_c} < \dddot{p}_{max}` (Cartesian jerk) + +Conditions derived from inverse kinematics: + +- :math:`q_{min} < q_c < q_{max}` +- :math:`-\dot{q}_{max} < \dot{q_c} < \dot{q}_{max}` +- :math:`-\ddot{q}_{max} < \ddot{q_c} < \ddot{q}_{max}` + +Recommended conditions +********************** + +Conditions derived from inverse kinematics: + +- :math:`-{\tau_j}_{max} < {\tau_j}_d < {\tau_j}_{max}` +- :math:`-\dot{\tau_j}_{max} < \dot{{\tau_j}_d} < \dot{\tau_j}_{max}` + +At the beginning of the trajectory, the following conditions should be fulfilled: + +- :math:`{}^OT_{EE} = {{}^OT_{EE}}_c` +- :math:`\dot{p}_{c} = 0` (Cartesian velocity) +- :math:`\ddot{p}_{c} = 0` (Cartesian acceleration) + +At the end of the trajectory, the following conditions should be fulfilled: + +- :math:`\dot{p}_{c} = 0` (Cartesian velocity) +- :math:`\ddot{p}_{c} = 0` (Cartesian acceleration) + +Controller requirements +----------------------- + +Necessary conditions +******************** + +- :math:`-\dot{\tau_j}_{max} < \dot{{\tau_j}_d} < \dot{\tau_j}_{max}` + +Recommended conditions +********************** + +- :math:`-{\tau_j}_{max} < {\tau_j}_d < {\tau_j}_{max}` + +At the beginning of the trajectory, the following conditions should be fulfilled: + +- :math:`{\tau_j}_{d} = 0` + +.. _limit_table: + +Limits for Panda +---------------- + +Limits in the Cartesian space are as follows:\ + ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| Name | Translation | Rotation | Elbow | ++========================+===============================================+==================================================+============================================+ +| :math:`\dot{p}_{max}` | 1.7 :math:`\frac{\text{m}}{\text{s}}` | 2.5 :math:`\frac{\text{rad}}{\text{s}}` | 2.1750 :math:`\frac{rad}{\text{s}}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| :math:`\ddot{p}_{max}` | 13.0 :math:`\frac{\text{m}}{\text{s}^2}` | 25.0 :math:`\frac{\text{rad}}{\text{s}^2}` | 10.0 :math:`\;\frac{rad}{\text{s}^2}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| :math:`\dddot{p}_{max}`| 6500.0 :math:`\frac{\text{m}}{\text{s}^3}` | 12500.0 :math:`\frac{\text{rad}}{\text{s}^3}` | 5000.0 :math:`\;\frac{rad}{\text{s}^3}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ + +Joint space limits are: + +.. csv-table:: + :header-rows: 1 + :file: control-parameters-joint-panda.csv + +The arm can reach its maximum extension when joint 4 has angle :math:`q_{elbow-flip}`, where :math:`q_{elbow-flip} = -0.467002423653011\:rad`. +This parameter is used to determine the flip direction of the elbow. + +Limits for Franka Research 3 +---------------------------- + +Limits in the Cartesian space are as follows:\ + ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| Name | Translation | Rotation | Elbow | ++========================+===============================================+==================================================+============================================+ +| :math:`\dot{p}_{max}` | 3.0 :math:`\frac{\text{m}}{\text{s}}` | 2.5 :math:`\frac{\text{rad}}{\text{s}}` | 2.620 :math:`\frac{rad}{\text{s}}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| :math:`\ddot{p}_{max}` | 9.0 :math:`\frac{\text{m}}{\text{s}^2}` | 17.0 :math:`\frac{\text{rad}}{\text{s}^2}` | 10.0 :math:`\;\frac{rad}{\text{s}^2}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ +| :math:`\dddot{p}_{max}`| 4500.0 :math:`\frac{\text{m}}{\text{s}^3}` | 8500.0 :math:`\frac{\text{rad}}{\text{s}^3}` | 5000.0 :math:`\;\frac{rad}{\text{s}^3}` | ++------------------------+-----------------------------------------------+--------------------------------------------------+--------------------------------------------+ + +Joint space limits are: + +.. csv-table:: + :header-rows: 1 + :file: control-parameters-joint-fr3.csv + +The arm can reach its maximum extension when joint 4 has angle :math:`q_{elbow-flip}`, where :math:`q_{elbow-flip} = -0.467002423653011\:rad`. +This parameter is used to determine the flip direction of the elbow. + + +.. important:: + + Note that the maximum joint velocity depends on the joint position. The maximum and minimum joint velocities at a certain joint position are calculated as: + + .. list-table:: + :class: borderless + + * - .. figure:: _static/pbv_equations_max.svg + :align: center + :figclass: align-center + + Maximum velocities + + * - .. figure:: _static/pbv_equations_min.svg + :align: center + :figclass: align-center + + Minimum velocities + + +As most motion planners cannot deal with those functions for describing the velocity limits of each joint but they only deal with +fixed velocity limits (rectangular limits), we are providing here a suggestion on which values to use for them. + +In the figures below the system velocity limits are visualized by the red and blue thresholds while the suggested +"position-velocity rectangular limits" are visualized in black. + +.. list-table:: Visualization of the joint limits of FR3 + :class: borderless + + * - .. figure:: _static/pbv_limits_j1.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 1 + + - .. figure:: _static/pbv_limits_j2.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 2 + + * - .. figure:: _static/pbv_limits_j3.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 3 + + - .. figure:: _static/pbv_limits_j4.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 4 + + * - .. figure:: _static/pbv_limits_j5.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 5 + + - .. figure:: _static/pbv_limits_j6.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 6 + + * - .. figure:: _static/pbv_limits_j7.svg + :align: center + :figclass: align-center + + Velocity limits of Joint 7 + - + + +Here are the parameters describing the suggested position-velocity rectangular limits: + +.. csv-table:: + :header-rows: 1 + :file: control-parameters-joint-fr3-rectangular.csv + +.. important:: + + These limits are the values that are used by default in the rate limiter and in the URDF inside :doc:`franka_ros`. + However, these are only a suggestion, you are free to define your own rectangles within the specification accordingly to your needs. + + Since FR3 does not inherently implement any restriction to the system limits (red and blue line in the plots above), you are also free + to implement your own motion generator to exploit the HW capabilities of FR3 beyond the rectangular limits imposed by existing motion generators. + + +Denavit–Hartenberg parameters +----------------------------- + +The Denavit–Hartenberg parameters for the Panda's kinematic chain are derived following Craig's convention and are as follows: + +.. figure:: _static/dh-diagram.png + :align: center + :figclass: align-center + + Panda's kinematic chain. + ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint | :math:`a\;(\text{m})` | :math:`d\;(\text{m})` | :math:`\alpha\;(\text{rad})` | :math:`\theta\;(\text{rad})` | ++=============+=======================+=======================+==============================+==============================+ +| Joint 1 | 0 | 0.333 | 0 | :math:`\theta_1` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 2 | 0 | 0 | :math:`-\frac{\pi}{2}` | :math:`\theta_2` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 3 | 0 | 0.316 | :math:`\frac{\pi}{2}` | :math:`\theta_3` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 4 | 0.0825 | 0 | :math:`\frac{\pi}{2}` | :math:`\theta_4` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 5 | -0.0825 | 0.384 | :math:`-\frac{\pi}{2}` | :math:`\theta_5` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 6 | 0 | 0 | :math:`\frac{\pi}{2}` | :math:`\theta_6` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Joint 7 | 0.088 | 0 | :math:`\frac{\pi}{2}` | :math:`\theta_7` | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ +| Flange | 0 | 0.107 | 0 | 0 | ++-------------+-----------------------+-----------------------+------------------------------+------------------------------+ + + +.. note:: + + :math:`{}^0T_{1}` is the transformation matrix which describes the position and orientation of + `frame 1` in `frame 0`. A kinematic chain can be calculated like the following: + :math:`{}^0T_{2} = {}^0T_{1} * {}^1T_{2}` diff --git a/_sources/faq.rst.txt b/_sources/faq.rst.txt new file mode 100644 index 0000000..107d164 --- /dev/null +++ b/_sources/faq.rst.txt @@ -0,0 +1,30 @@ +FAQ +=== + +How can I use libfranka in my own CMake-based project? +------------------------------------------------------ + +Add the following to your ``CMakeLists.txt``: + +.. code-block:: cmake + + find_package(Franka REQUIRED) + + # ... + + target_link_libraries( Franka::Franka) + + +To build against a custom ``libfranka`` location, compile +your project with:: + + # Build against libfranka build directory + cmake -DFranka_DIR=/path/to/libfranka/build + + # Build against custom libfranka installation + cmake -DFranka_DIR=/path/to/usr/lib/cmake/Franka + +.. important:: + + Don't forget to build your project in release mode + (``cmake -DCMAKE_BUILD_TYPE=Release``)! diff --git a/_sources/fr3-certification-remarks.rst.txt b/_sources/fr3-certification-remarks.rst.txt new file mode 100644 index 0000000..153f00a --- /dev/null +++ b/_sources/fr3-certification-remarks.rst.txt @@ -0,0 +1,16 @@ +FR3 certification remarks +========================= + +Congratulations on your new Franka Research 3. +As part of the package you received the documentation including Product Manual Franka Production 3 (110010/1.5/EN). +Even though its title indicates it is for the other Franka robot arm - FP3, +it is in general also valid for Franka Research 3. +We recommend reading this product manual before using the robot. Please be aware that especially +when using the FCI and in general use cases in research applications there might be some deviations from this manual. +Therefore, please consult the following table, where the applicability is stated and check the +referred documentation like the datasheet. + + +.. csv-table:: + :header-rows: 1 + :file: fr3-certification-remarks.csv diff --git a/_sources/franka_matlab/CHANGELOG.md.txt b/_sources/franka_matlab/CHANGELOG.md.txt new file mode 100644 index 0000000..32a097b --- /dev/null +++ b/_sources/franka_matlab/CHANGELOG.md.txt @@ -0,0 +1,62 @@ +# Changelog: + +## 1.0.0 (11-03-2024) + + - **BREAKING** Robot Settings standardization with Matlab OOP. + - **BREAKING** Adding the option to set the rate limiter and the cutoff frequency in the apply control simulink block. + - **BREAKING** Removing the get initial robot state block from the simulink library. + - **BREAKING** Enhanced modular building structure for the Franka Simulink Library. Easy incorporation to larger projects and Harware Support Packages. + - **BREAKING** New Matlab object oriented API expressed based on the new `FrankaRobot()` class. Incorporation of existing Franka MATLAB functions as methods of the new API Class. + - **BREAKING** Removing all the "Panda" naming conventions. Replaced with "Franka Robot". + - **BREAKING** Franka MATLAB is now distributed as a Toolbox Matlab Add-On. No installation script needed. + - Fixing collision threshold setting bug. + - Oldest supported Matlab version is now the R2021a. + - Adding the option to set the Nominal End-Effector to End-Effector frame NE_T_EE in the Simulink Block "Apply Control". + - Expansion of the Matlab API with the new methods `gripper_state()`, `gripper_homing()`, `gripper_grasp()`, `gripper_move()` and `gripper_stop()` for controlling the Franka Gripper. + - Expansion of the Matlab API with the new method `joint_trajectory_motion()` for following precomputed joint trajectories. + - Creation of the new demo `pick_and_place_with_RRT.mlx` showcasing a workflow approach for the new Matlab API. + +## 0.3.1 (23-03-2023) + + - Bugfix. Properly setting the collision threshold values in Simulink. + +## 0.3.0 (20-09-2022) + + - Windows 10 support (Experimental mainly due to the non-Real-Time nature of the default Windows system). + - Project now relies on the leaner "Generic Real-Time" .tlc (grt.tlc) target framework. + - Support for XCP communication protocol (vehicle network communication). Data inspector is now enabled! + - Support for "Run on custom Hardware" Simulink App for controlling the "Build-deploy-execute-connect" workflow. + - Project back-end overal simplification with modified rt_main.cpp for handling the external mode as a seperate thread. + - **BREAKING** all the Franka MATLAB functions are starting with the "`franka_`" prefix. + - Expansion of Matlab library with the functions `franka_communication_test()`, `franka_joint_poses()`, `franka_robot_state()` and `franka_joint_point_to_point_motion()`. + - Addition of the Simulink demo, "joint_impedance_control.slx". + - Fixing the bug when utilizing the Control Modes "Torque Control - X`. + +## 0.2.1 (29-04-2022) + + - Adding supoort for all versions from Matlab2019a till Matlab2021a with libfranka 0.9.0. + +## 0.2.0 (31-05-2021) + + - franka_matlab upgrade, supports Matlab2019a, Matlab2019b, Matlab2020a, Matlab2020b, Matlab2021a & libfranka 0.8.0. + +## 0.1.1 (01-07-2020) + + - Any dependences that lead to source code linking for the Simulink Franka Library during mexing removed. That fixes the memory corruption + bug when shutting down Matlab. + - Simulink Franka Library sFunctions C++ and tlc implementations decoupled from src code necessary for automatic code gen. SRC Folder can be treated seperately as a C++ project. + +## 0.1.0 (21-01-2020) + + - Features: + - **Simulink Library** for **Franka Robot**, includes the following blocks: + - **Franka Simulink Iterface** for applying the desired control, plus additional parameters. + - **Read Initial Robot State** for reading initial values during the first execution step for any desirable signal. The set of the desired signals can be set through the mask in free text form. + - **Read Robot State** for reading the values of any desirable signal during execution. The set of the desired signals can be set through the mask in free text form. + - **Franka Model** for reading the values of all Model parameters of the Franka Robot during execution. + - **Duration Period** for reading the current step, sample time. If communication is not secured during the 1ms, the block will return the value of 2ms or 3ms etc. + - **Gripper Read State** for reading the current values out of the Franka Gripper. + - **franka_robot.tlc** & **franka_robot_shrlib.tlc** custom linux targets, based on ert, that offer ext mode that is real time capable(package drop in case of main step frame loss). + - **Matlab Library**(Experimental, limited support), includes the following command: + - **automatic_error_recovery(robot_ip)**. Execute through a matlab command line for getting automatically out of an error state. + - **Simulink Library misc**(Experimental, limited support) that includes a set of UI buttons with callback scripts with the potential to automate some of the dev. workflow. \ No newline at end of file diff --git a/_sources/franka_matlab/compatibility.rst.txt b/_sources/franka_matlab/compatibility.rst.txt new file mode 100644 index 0000000..3c3c7a7 --- /dev/null +++ b/_sources/franka_matlab/compatibility.rst.txt @@ -0,0 +1,34 @@ +Compatible versions +=================== + +.. _compatibility-franka-matlab: + +Compatibility with Matlab & libfranka +------------------------------------- + ++------------------------+-------------------+----------------------------+ +| Franka MATLAB version | libfranka version | Matlab Version | ++========================+===================+============================+ +| 1.0.0 | 0.9.x & 0.13.x | :math:`\geq` R2021a | ++------------------------+-------------------+----------------------------+ +| 0.3.1 | 0.9.x & 0.10.x | :math:`\geq` R2019a | ++------------------------+-------------------+----------------------------+ +| 0.3.0 | 0.9.x & 0.10.x | :math:`\geq` R2019a | ++------------------------+-------------------+----------------------------+ +| 0.2.1 | 0.9.x | R2019a to R2021a | ++------------------------+-------------------+----------------------------+ +| 0.2.0 | 0.8.0 | R2019a to R2021a | ++------------------------+-------------------+----------------------------+ +| 0.1.1 | 0.7.1 | R2019a | ++------------------------+-------------------+----------------------------+ +| 0.1.0 | 0.7.1 | R2019a | ++------------------------+-------------------+----------------------------+ + +`libfranka and robot system compatibility `_ + +.. important:: + The `pick_and_place_with_RRT.mlx` Matlab demo is compatible with Matlab :math:`\geq` R2021b, as the required libraries from the Matlab Robotics Toolbox have only introduced since then. + +.. important:: + Make sure that you've installed and set up Matlab with a compatible compiler version! You can find the list of Matlab + compatible compilers in `this link `_. \ No newline at end of file diff --git a/_sources/franka_matlab/franka_matlab_changelog.rst.txt b/_sources/franka_matlab/franka_matlab_changelog.rst.txt new file mode 100644 index 0000000..090cf53 --- /dev/null +++ b/_sources/franka_matlab/franka_matlab_changelog.rst.txt @@ -0,0 +1,6 @@ +Franka MATLAB Toolbox changelog +================================ + +.. include:: ../franka_matlab/CHANGELOG.md + :parser: myst_parser.sphinx_ + :start-line: 2 diff --git a/_sources/franka_matlab/getting_started.rst.txt b/_sources/franka_matlab/getting_started.rst.txt new file mode 100644 index 0000000..732c1f2 --- /dev/null +++ b/_sources/franka_matlab/getting_started.rst.txt @@ -0,0 +1,112 @@ +Getting started +=============== + +Overview +-------- + +A set of Simulink & Matlab examples is included within the Franka MATLAB Toolbox. Feel free to experiment, adjust +them and expand them to fit your project needs! + +You can navigate through the examples by typing: + +.. code-block:: shell + + >> franka_matlab_toolbox_examples(); + +.. figure:: _static/franka_matlab_toolbox_examples.png + :align: center + :figclass: align-center + + Franka MATLAB Toolbox Examples Navigator. + +Initialization +-------------- + +After opening, double clicking on any of the simulink models the robot settings will be loaded automatically in the +workspace, in the form of the object `frs`. + +.. hint:: + + The Simulink models are delivered in R2021a version. They will convert automatically to your Matlab version + when you try to save the model. + +.. figure:: _static/workspace_parameters.png + :align: center + :figclass: align-center + + The Franka Robot Settings object. + +The robot_ip is set to 172.16.0.2. Make sure that the robot_ip, as well as all the other parameters matches your +setup for your intended purposes. + +.. code-block:: shell + + >> frs.robot_ip = + +You can modify the default settings for the FrankaRobotSettings with + +.. code-block:: shell + + >> edit FrankaRobotSettings.m + +Execution +--------- + +Let's start by selecting the `Run on custom hardware` App from the Apps pane in Simulink. + +.. figure:: _static/cartesian_impedance_control_apps.png + :align: center + :figclass: align-center + + "Run on custom hardware" Simulink App. + +.. important:: + + Before executing make sure that the brakes of the robot are disengaged, the FCI mode is activated + in Desk and that the robot is in execution mode(user button is released)! + +You can then select from the Hardware tab either `Monitor & Tune` in case monitoring through the external mode is +desired or `Build, Deploy & Start` for just executing the application without monitoring. + +.. figure:: _static/cartesian_impedance_control_hardware.png + :align: center + :figclass: align-center + + Hardware Simulink App. + +.. caution:: + + The robot will move! Make sure that you are monitoring the situation, ready to take action if necessary! + +Alternatively you can run the auto-generated executable located in the current working space manually from a terminal: + +In case of Linux: + + +.. code-block:: shell + + $ ./ + +or in case of Windows: + +.. code-block:: shell + + > .exe + +Automatic error recovery +------------------------ +If the robot encounters an error state and transitions to reflex mode, +you may attempt a recovery by executing the automatic error recovery command in Matlab. + +.. code-block:: shell + + >> fr = FrankaRobot(); + >> fr.automatic_error_recovery(); + +In case the command fails and the robot remains in the erroneous state try using the guiding mode to manually bring +back the robot to a valid configuration. + +.. hint:: + + Checkout the :ref:`matlab library ` for a set of helper + functions that can help to optimize your workflow. diff --git a/_sources/franka_matlab/index.rst.txt b/_sources/franka_matlab/index.rst.txt new file mode 100644 index 0000000..e87f2ed --- /dev/null +++ b/_sources/franka_matlab/index.rst.txt @@ -0,0 +1,28 @@ +Franka MATLAB Toolbox +====================== + +.. todolist:: + +Franka MATLAB Toolbox contains a set of libraries and tools for exposing the Franka Robot to the Mathworks ecosystem with the: + +* ``Simulink Franka Library``, a set of simulink blocks interfacing the Franka Robot through automatic C++ code gen with Simulink Coder, for rapid-prototyping of robot control algorithms. + +.. figure:: _static/simulink_library_browser.png + :align: center + :figclass: align-center + + Simulink Library for rapid-prototyping of controllers for the Franka robot. + +* ``Matlab Franka Library``, a set of matlab functions for reading the current robot state and applying some simple open loop motion commands. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + matlab_toolbox_dependencies + compatibility + installation + getting_started + simulink_library + matlab_library + troubleshooting diff --git a/_sources/franka_matlab/installation.rst.txt b/_sources/franka_matlab/installation.rst.txt new file mode 100644 index 0000000..814fcd1 --- /dev/null +++ b/_sources/franka_matlab/installation.rst.txt @@ -0,0 +1,94 @@ +Installation +============ + +.. hint:: + The Franka MATLAB Toolbox is based on the `Franka Control Interface (FCI) `_ and + the `libfranka C++ interface `_. + All the same + `system and network requirements `_ do therefore apply. + +Linux System Setup +------------------ + +.. important:: + For Linux system we higly recommend installing the `matlab-support package `_: + + .. code-block:: shell + + sudo apt install matlab-support + +Make sure that the following dependencies are installed: + + .. code-block:: shell + + sudo apt install build-essential cmake git libpoco-dev libeigen3-dev + +You can either let the Franka MATLAB Toolbox auto-install the libfranka locally or you can proceed with +a system-wide libfranka.*.deb installation. We recommend the former. + +.. important:: + Make sure that the Real-Time Kernel is properly installed as described in the + `libfranka documentation `_. + +Windows System Setup +-------------------- + +.. warning:: + Support for Windows is still experimental. Issues could arise due to lack of hard Real-Time scheduling capabilities of the generic Windows distributions. + +Please make sure first to install the Visual Studio 2017 Community Edition (English Version) on a Windows 10 PC. + +Additionally the following software components must be installed for windows: + +* git +* cmake +* vcpkg +* ninja + +Make sure that the vcpkg & ninja paths are exposed through the PATH environment variable. +You can modify the PATH environment variable in Windows 10: + +1. Open the Start Search, type in “env”, and choose “Edit the system environment variables”. +2. Click the “Environment Variables” button. +3. Under the “System Variables” section, find the row with “Path” in the first column, and click edit. +4. Add the vcpkg & ninja paths, like e.g C:\\Users\\{user name}\\vcpkg & C:\\Users\\{user name}\\ninja +5. Verify by opening a terminal and evaluating the `vcpkg` and `ninja` commands. + +You can then install the 64bit versions of eigen3 and poco packages: + +.. code-block:: shell + + vcpkg install eigen3:x64-windows + vcpkg install poco[netssl]:x64-windows + +Franka MATLAB Toolbox Add-On Installation & License Management +--------------------------------------------------------------- + +For installing the Franka MATLAB Toolbox either drag-and-drop the franka_matlab.mltbx +to the current Matlab Command Window or you can use the Matlab Add-On manager. + +After this process is complete simply follow the instructions in the Getting Started guided +which should have been opened after the Franka MATLAB Toolbox Add-on installation. + +In short you will need to generate a unique identifier for you PC by executing: + +.. code-block:: shell + + franka_matlab_toolbox_uid_gen(); + +Please then send this unique identifier to Franka Robotics for receiving a License Number for +the Franka MATLAB Toolbox. + +You can then proceed with the final installation step, by executing: + +.. code-block:: shell + + franka_matlab_toolbox_install('franka matlab toolbox license number as a string',['fr3' or 'fer']); + +That's it the Franka MATLAB Toolbox should be ready. + +Get a glimpse of what the capabilities of the Toolbox are by navigating through the examples provided with the Toolbox: + +.. code-block:: shell + + franka_matlab_toolbox_examples(); diff --git a/_sources/franka_matlab/matlab_library.rst.txt b/_sources/franka_matlab/matlab_library.rst.txt new file mode 100644 index 0000000..cdec0a6 --- /dev/null +++ b/_sources/franka_matlab/matlab_library.rst.txt @@ -0,0 +1,119 @@ +.. _matlab-library: + +Matlab Library +============== + +Matlab Demo Pick & Place with RRT +--------------------------------- + +For familiarization with the Matlab API for the Franka Robot the `pick_and_place_with_RRT.mlx` demo is a good starting point. + +.. figure:: _static/matlab_pick_and_place_with_RRT_demo.png + :align: center + :figclass: align-center + + Live Matlab Script and Figure of the pick-and-place demo. + +.. hint:: + The pick-and-place demo requires the `Matlab Robotics Toolbox `_ with Matlab :math:`\geq` R2021b. + +FrankaRobot Class +----------------- + +.. code-block:: shell + + >> fr = FrankaRobot(); + +Automatic Error Recovery +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.automatic_error_recovery(); + +Will attempt an automatic error recovery . + +Get Joint Poses +^^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> jp = fr.joint_poses(); + +Will return a 7 element cell array with the current robot joint poses. + +Get Robot State +^^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> rs = fr.robot_state(); + +Will return a struct will the current robot state. + +Joint Point to Point Motion +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.joint_point_to_point_motion(<7 element double array with target configuration>, <0 to 1 scalar speed factor>); + +Will move the robot into a desired joint configuration. + +Joint Trajectory Motion +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.joint_trajectory_motion(<7xn double array with desired joint trajectory>); + +Will move the robot based on the given desired joint trajectory. + +.. warning:: + Make sure that the current configuration of the robot maches the initial trajectory element `q(1:7,1)` that is passed in the function! Additionally make sure that + the given trajectory is sufficiently smooth and continuous. + +Gripper State +^^^^^^^^^^^^^ + +.. code-block:: shell + + >> gs = fr.gripper_state(); + +Will return a struct with the current gripper state. + +Gripper Homing +^^^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.gripper_homing(); + +Will perform a homing operation to the gripper and will return 1 if succesful. + +Gripper Grasp +^^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.ripper_grasp(width, speed, force, epsilon_inner, epsilon_outer); + +Will attempt a grasp and will return 1 if the object is grasped, 0 otherwise. + +Gripper Move +^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.gripper_move(width,speed); + +Will move the gripper to a desired width position. Will return 1 if succesful, 0 otherwise. + +Gripper Stop +^^^^^^^^^^^^ + +.. code-block:: shell + + >> fr.gripper_stop(); + +Will stop the current gripper operation. Will return 1 if succesful, 0 otherwise. \ No newline at end of file diff --git a/_sources/franka_matlab/matlab_toolbox_dependencies.rst.txt b/_sources/franka_matlab/matlab_toolbox_dependencies.rst.txt new file mode 100644 index 0000000..10bcc18 --- /dev/null +++ b/_sources/franka_matlab/matlab_toolbox_dependencies.rst.txt @@ -0,0 +1,14 @@ +Matlab toolbox dependencies +=========================== + +The following Mathworks products are required: + +* `Matlab `_ (:ref:`compatibility-franka-matlab`) +* `Simulink `_ +* `Simulink Coder `_ + +Some of demos provided with the franka_matlab could potentially need either of the following toolboxes: + +* `Matlab Coder `_ (cartesian_impedance_control.slx, force_control.slx) +* `Stateflow `_ (grasp_object.slx) +* `Matlab Robotics Toolbox `_ with Matlab R2021b or greater (pick_and_place_with_RRT.mlx) \ No newline at end of file diff --git a/_sources/franka_matlab/simulink_library.rst.txt b/_sources/franka_matlab/simulink_library.rst.txt new file mode 100644 index 0000000..3b51167 --- /dev/null +++ b/_sources/franka_matlab/simulink_library.rst.txt @@ -0,0 +1,102 @@ +Simulink Library +================ + +.. hint:: + Regarding the inputs/outputs signals nomenclature datatypes and sizes the libfranka definitions + have been fully adopted. You can check the list of signals here --> + `Robot State Attributes `_. + Column-major format for the signal has been adopted as well. + +Apply Control +------------- + +.. figure:: _static/apply_control.png + :align: center + :figclass: align-center + + Apply Control Simulink Block. + +This is the main block of the franka simulink library and it is responsible for applying the desired parameters and +control signals to the robot. + +Additionally a set of parameters for the robot can be applied through this block main pane based mostly +on the desired control mode that is selected from the drop-down list `Control Mode`. + +.. figure:: _static/block_parameters_apply_control.png + :align: center + :figclass: align-center + :width: 530px + + Apply Control Simulink Block Settings. + +.. hint:: + If desirable, an initial robot configuration can be applied **!!before!!** the main execution of the control loop. + Namely the robot will move to the desirable configuration and only then the main execution of the Simulink model + will take place. You can define that in the `Initial Configuration` section of the block settings. + +Get Robot State +----------------------- + +.. figure:: _static/get_robot_state.png + :align: center + :figclass: align-center + + Get Robot State Simulink Block. + +You can write the signals that you wish to capture in free text form in the `Parameters` section. +For the set of available signals --> `Robot State Attributes `_ + +.. figure:: _static/get_robot_state_settings.png + :align: center + :figclass: align-center + + Get initial robot state Simulink Block Settings. + +Get Duration(Sample Time) +------------------------- + +.. figure:: _static/get_duration.png + :align: center + :figclass: align-center + + Get duration from last main callback(Sample Time) Simulink Block. + +This Simulink block outputs the duration from the last execution step in seconds. Ideally this should be always +0.001 seconds but due to lost packages during communication errors 0.002 secs or 0.003 secs could be seen. + +.. warning:: + The step count of the Simulink model **does not change** during these communication mishaps! + It just continues incrementally although an execution step in reality has been lost! + Special design considerations should be therefore demanded especially in the case of + sensitive position motion generators. + Have a look to e.g the generate_cartesian_pose_motion.slx demo to see how the + main "clock" of the application has been designed. + +Get Model +--------- + +.. figure:: _static/get_model.png + :align: center + :figclass: align-center + + Get robot model numeric values Simulink Block. + +This Simulink block will deliver the numerical values of the Model of the Franka robot. For an analytical +description of the available signals see --> +`Robot Model Signals `_ + +Get Gripper State +----------------- + +.. figure:: _static/get_gripper_state.png + :align: center + :figclass: align-center + + Get current gripper state Simulink Block. + +The get gripper state block will inform the application about the current gripper state. + +.. hint:: + Highly recommended to have a look at the + `GripperState Struct Reference `_ + for the list of available signals and the demo `grasp_objects.slx` which is provided for getting started. diff --git a/_sources/franka_matlab/troubleshooting.rst.txt b/_sources/franka_matlab/troubleshooting.rst.txt new file mode 100644 index 0000000..780ebf6 --- /dev/null +++ b/_sources/franka_matlab/troubleshooting.rst.txt @@ -0,0 +1,72 @@ +Troubleshooting +=============== + +.. hint:: + Checkout the `Franka Community `_ and the + `franka_matlab category `_ for relevant posts or for creating new ones! + +.. hint:: + Before proceeding with the Franka MATLAB Toolbox, it would be a good practice to execute a couple of the libfranka examples, under the build/examples folder, in order to ensure that the libfranka + installation has been succesful and that the system can operate under the Real-Time control constraints. + +control_modes.h: No such file or directory error. +------------------------------------------------- + +.. figure:: _static/simulink_model_apply_control_only_build_error.png + :align: center + :figclass: align-center + + The build error message in simulink when only the "apply control is present". + +This is a known current limitation of the system, as the build process will fail if only +the "apply control" block is present in a simulink model. + +.. figure:: _static/simulink_model_apply_control_only.png + :align: center + :figclass: align-center + + Example of a Simulink model with only "apply control". The build will fail. + +For fixing the issue just include any other block from the Franka Simulink Library, e.g +with the terminal if it will be left unused. + +.. figure:: _static/simulink_model_apply_control_only_fix.png + :align: center + :figclass: align-center + + Fixing the "control_modes.h: No such file or directory error." by including any other + block from the Franka Simulink Library. + +libfranka reference +------------------- +.. hint:: + Same error messages and advised troubleshooting applies as `libfranka `_. + +Issues with the graphics driver in Linux +---------------------------------------- + +NVIDIA's graphics driver's are not officially supported in Linux with Real-Time Kernel. This could cause issues in graphics renderings in Matlab +and Simulink, e.g with figures and scopes respectively. We would then recommend starting matlab with the `-softwareopengl` for avoiding these issues: + +.. code-block:: shell + + $ matlab -softwareopengl + +Issues with libstdc++.so and other system dynamic libraries +----------------------------------------------------------- + +Make sure that you have installed the `matlab-support package `_ for your system, in order for Matlab to reference the system dynamic libraries +instead of the precompiled ones that it ships with: + +.. code-block:: shell + + sudo apt install matlab-support + +Franka Simulink library number of block instances +------------------------------------------------- + +.. important:: + The Simulink library has been designed for rapid-prototyping of robot controllers with one-robot + in mind. Multiple instances for the Apply Control block are not encouraged as this has not been tested. + Multiple instances of all the other Simulink blocks, as long as they point to the same robot ip, can be + utilized. \ No newline at end of file diff --git a/_sources/franka_ros.rst.txt b/_sources/franka_ros.rst.txt new file mode 100644 index 0000000..bd77600 --- /dev/null +++ b/_sources/franka_ros.rst.txt @@ -0,0 +1,1254 @@ +franka_ros +========== +.. note:: + + ``franka_ros`` is not supported on Windows. + +Before continuing with this chapter, please :doc:`install or compile franka_ros `. + +.. figure:: _static/ros-architecture.png + :align: center + :figclass: align-center + + Schematic overview of the ``franka_ros`` packages. + +The ``franka_ros`` metapackage integrates ``libfranka`` into ROS and ROS control. +Here, we introduce its packages and +we also give a short how-to for :ref:`writing controllers `. + +All parameters passed to launch files in this section come with default values, so they +can be omitted if using the default network addresses and ROS namespaces. +Make sure the ``source`` command was called with the setup script from your workspace: + +.. code-block:: shell + + source /path/to/catkin_ws/devel/setup.sh + + +franka_description +------------------ + +This package contains the description of our robots and end effectors in terms of kinematics, joint +limits, visual surfaces and collision space. The collision space is a simplified version of the +visual description used to improve performance of collision checks. The descriptions are based on +the URDF format according to the `URDF XML documentation `_ . + +In case you want to simulate the Panda robot, you can pass a ``gazebo`` argument to the XACRO file. +Then the URDF will contain *estimated* inertias values, i.e. link masses and inertia tensors based +on the `Dynamic Identification of the Franka Robotics Panda Robot With Retrieval of Feasible Parameters Using +Penalty-Based Optimization `_. + +.. code-block:: shell + + xacro $(rospack find franka_description)/robots/panda/panda.urdf.xacro gazebo:=true + +The same works for FR3: + +.. code-block:: shell + + xacro $(rospack find franka_description)/robots/fr3/fr3.urdf.xacro gazebo:=true + + +Collisions Volumes +"""""""""""""""""" + +The URDF defines two types of collision types: + + * **Fine**: These collision volumes are made from convex meshes which are approximated and + drastically simplified versions of the visual meshes (.dae) of each link. The fine volumes + should be used for simulating robot collisions in :ref:`Gazebo ` + * **Coarse**: These collision volumes are simply capsules (a cylinder with two semispherical + end caps) attached to each link and inflated by a certain safety distance. These volumes + are more efficient to compute and are used internally in the robot for self-collision + avoidance. Use these geometries when you are planning motions e.g. with `MoveIt `_. + + +.. table:: + :widths: 1 1 1 + + +-------------------------------+---------------------------------------+-----------------------------------------+ + | Visual | Collision (Fine) | Collision (Coarse) | + +-------------------------------+---------------------------------------+-----------------------------------------+ + | .. image:: _static/visual.png | .. image:: _static/collision-fine.png | .. image:: _static/collision-coarse.png | + +-------------------------------+---------------------------------------+-----------------------------------------+ + +To distinguish between the two types of collision models artificial links are inserted in the URDF +with an ``*_sc`` suffix (for self-collision): + +| **panda_link0** +| ├─ **panda_link0_sc** +| └─ **panda_link1** +| ├─ **panda_link1_sc** +| └─ **panda_link2** +| ├─ **panda_link2_sc** +| └─ **panda_link3** +| ├─ **panda_link3_sc** +| └─ **panda_link4** +| ├─ **panda_link4_sc** +| └─ **panda_link5** +| ├─ **panda_link5_sc** +| └─ **panda_link6** +| ├─ **panda_link6_sc** +| └─ **panda_link7** +| ├─ **panda_link7_sc** +| └─ **panda_link8** +| ├─ **panda_link8_sc** +| └─ **panda_hand** +| ├─ **panda_leftfinger** +| ├─ **panda_rightfinger** +| ├─ **panda_hand_sc** +| └─ **panda_hand_tcp** + +You can control which collision model is loaded into your URDF via the ``gazebo`` XACRO argument: + + * ``xacro ... panda.urdf.xacro gazebo:=false``: This will use *both* the fine and coarse collision model. + This is also the default if you omit the arg entirely. Use this when you want to use MoveIt + * ``xacro ... panda.urdf.xacro gazebo:=true``: This will use *only* the fine collision model. Use + this when you want a simulatable URDF i.e. for Gazebo. When using the coarse collision model the robot + will of course be in constant collision with the capsules of the next link. + + +.. _franka_gripper: + +franka_gripper +-------------- +This package implements the ``franka_gripper_node`` for interfacing a gripper from ROS. +The node publishes the state of the gripper and offers the following `actions servers`: + + * ``franka_gripper::MoveAction(width, speed)``: moves to a target ``width`` with the defined + ``speed``. + * ``franka_gripper::GraspAction(width, epsilon_inner, epsilon_outer, speed, force)``: tries to + grasp at the desired ``width`` with a desired ``force`` while closing with the given ``speed``. The + operation is successful if the distance :math:`d` between the gripper fingers is: + :math:`\text{width} - \epsilon_\text{inner} < d < \text{width} + \epsilon_\text{outer}`. + * ``franka_gripper::HomingAction()``: homes the gripper and updates the maximum width given the + mounted fingers. + * ``franka_gripper::StopAction()``: aborts a running action. This can be used to stop applying + forces after grasping. + * ``control_msgs::GripperCommandAction(width, max_effort)``: A standard gripper action + recognized by MoveIt!. If the argument ``max_effort`` is greater than zero, the gripper + will try to grasp an object of the desired ``width``. On the other hand, if ``max_effort`` is + zero (:math:`\text{max_effort} < 1^{-4}`), the gripper will move to the desired ``width``. + + .. note:: + + Use the argument ``max_effort`` only when grasping an object, otherwise, the gripper will + close ignoring the ``width`` argument. + + +You can launch the ``franka_gripper_node`` with: + +.. code-block:: shell + + roslaunch franka_gripper franka_gripper.launch robot_ip:= + +.. hint:: + + Starting with ``franka_ros`` 0.6.0, specifying ``load_gripper:=true`` for + ``roslaunch franka_control franka_control.launch`` will start a ``franka_gripper_node`` as well. + + +.. _franka_hw: + +franka_hw +--------- +This package contains the hardware abstraction of the robot for the ROS control framework +based on the ``libfranka`` API. The hardware class ``franka_hw::FrankaHW`` is implemented in this +package offering the following interfaces to controllers: + ++-------------------------------------------------+----------------------------------------------+ +| Interface | Function | ++=================================================+==============================================+ +| ``hardware_interface::JointStateInterface`` | Reads joint states. | ++-------------------------------------------------+----------------------------------------------+ +| ``hardware_interface::VelocityJointInterface`` | Commands joint velocities and reads joint | +| | states. | ++-------------------------------------------------+----------------------------------------------+ +| ``hardware_interface::PositionJointInterface`` | Commands joint positions and reads joint | +| | states. | ++-------------------------------------------------+----------------------------------------------+ +| ``hardware_interface::EffortJointInterface`` | Commands joint-level torques and reads | +| | joint states. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaStateInterface`` | Reads the full robot state. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaPoseCartesianInterface`` | Commands Cartesian poses and reads the full | +| | robot state. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaVelocityCartesianInterface`` | Commands Cartesian velocities and reads the | +| | full robot state. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaModelInterface`` | Reads the dynamic and kinematic model of the | +| | robot. | ++-------------------------------------------------+----------------------------------------------+ + +To use ROS control interfaces, you have to retrieve resource handles by name: + ++-------------------------------------------------+----------------------------------------+ +| Interface | Resource handle name | ++=================================================+========================================+ +| ``hardware_interface::JointStateInterface`` | "_joint1" to "_joint7" | ++-------------------------------------------------+----------------------------------------+ +| ``hardware_interface::VelocityJointInterface`` | "_joint1" to "_joint7" | ++-------------------------------------------------+----------------------------------------+ +| ``hardware_interface::PositionJointInterface`` | "_joint1" to "_joint7" | ++-------------------------------------------------+----------------------------------------+ +| ``hardware_interface::EffortJointInterface`` | "_joint1" to "_joint7" | ++-------------------------------------------------+----------------------------------------+ +| ``franka_hw::FrankaStateInterface`` | "_robot" | ++-------------------------------------------------+----------------------------------------+ +| ``franka_hw::FrankaPoseCartesianInterface`` | "_robot" | ++-------------------------------------------------+----------------------------------------+ +| ``franka_hw::FrankaVelocityCartesianInterface`` | "_robot" | ++-------------------------------------------------+----------------------------------------+ +| ``franka_hw::FrankaModelInterface`` | "_robot" | ++-------------------------------------------------+----------------------------------------+ + +.. hint:: + + By default, is set to "panda". + +The ``franka_hw::FrankaHW`` class also implements the starting, stopping and switching of +controllers. + +The ``FrankaHW`` class also serves as base class for ``FrankaCombinableHW``, a hardware class that +can be combined with others to control multiple robots from a single controller. The combination of +an arbitrary number of Panda robots (number configured by parameters) based on ``FrankaCombinableHW`` +for the ROS control framework ``_ is implemented +in ``FrankaCombinedHW``. The key-difference between ``FrankaHW`` and ``FrankaCombinedHW`` is +that the latter supports torque control only. + +.. important:: + + The ``FrankaCombinableHW`` class is available from version 0.7.0 and allows torque/effort control only. + +The ROS parameter server is used to determine at runtime which robots are loaded in the combined +class. For an example on how to configure the ``FrankaCombinedHW`` in the according hardware node, +see :ref:`franka_control `. + +.. note:: + + The approach of ``FrankaHW`` is optimal for controlling single robots. Thus we recommend using + the ``FrankaCombinableHW``/``FrankaCombinedHW`` classes only for controlling multiple robots. + +The interfaces offered by the ``FrankaCombinableHW``/``FrankaCombinedHW`` classes are the following: + ++-------------------------------------------------+----------------------------------------------+ +| Interface | Function | ++=================================================+==============================================+ +| ``hardware_interface::EffortJointInterface`` | Commands joint-level torques and reads | +| | joint states. | ++-------------------------------------------------+----------------------------------------------+ +| ``hardware_interface::JointStateInterface`` | Reads joint states. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaStateInterface`` | Reads the full robot state. | ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaModelInterface`` | Reads the dynamic and kinematic model of the | +| | robot. | ++-------------------------------------------------+----------------------------------------------+ + +The only admissible command interface claim is the ``EffortJointInterface`` which can be combined +with any set of read-only-interfaces (``FrankaModelInterface``, ``JointStateInterface``, +``FrankaStateInterface``). The resource handles offered by all interfaces are claimed by name and +follow the same naming conventions as described for `FrankaHW`. Every instance of +``FrankaCombinableHW`` offers the complete set of service and action interfaces +(see :ref:`franka_control `). + +.. note:: + + The ``FrankaCombinedHW`` class offers an additional action server in the control node namespace + to recover all robots. If a reflex or error occurs on any of the robots, the control loop of all + robots stops until they are recovered. + +.. important:: + + ``FrankaHW`` makes use of the ROS `joint_limits_interface `_ + to `enforce position, velocity and effort safety limits + `_. + The utilized interfaces are listed below: + + * joint_limits_interface::PositionJointSoftLimitsInterface + * joint_limits_interface::VelocityJointSoftLimitsInterface + * joint_limits_interface::EffortJointSoftLimitsInterface + + Approaching the limits will result in the (unannounced) modification of the commands. + +.. _franka_control: + +franka_control +-------------- + +The ROS nodes ``franka_control_node`` and ``franka_combined_control_node`` are hardware nodes +for ROS control that use according hardware classes from ``franka_hw``. They provide a variety +of ROS services to expose the full ``libfranka`` API in the ROS ecosystem. The following services +are provided: + + * ``franka_msgs::SetJointImpedance`` specifies joint stiffness for the internal controller + (damping is automatically derived from the stiffness). + * ``franka_msgs::SetCartesianImpedance`` specifies Cartesian stiffness for the internal + controller (damping is automatically derived from the stiffness). + * ``franka_msgs::SetEEFrame`` specifies the transformation from _EE (end effector) to + _NE (nominal end effector) frame. The transformation from flange to end effector frame + is split into two transformations: _EE to _NE frame and _NE to + _link8 frame. The transformation from _NE to _link8 frame can only be + set through the administrator's interface. + * ``franka_msgs::SetKFrame`` specifies the transformation from _K to _EE frame. + * ``franka_msgs::SetForceTorqueCollisionBehavior`` sets thresholds for external Cartesian + wrenches to configure the collision reflex. + * ``franka_msgs::SetFullCollisionBehavior`` sets thresholds for external forces on Cartesian + and joint level to configure the collision reflex. + * ``franka_msgs::SetLoad`` sets an external load to compensate (e.g. of a grasped object). + * ``std_srvs::Trigger`` services allow to connect and disconnect your hardware node + (available from 0.8.0). When no active (commanding) controller is running, you can disconnect + the hardware node, freeing the respective robots for non-fci applications like e.g. Desk-based + operations. Once you want to resume fci operations you can call connect and start your + ros_control based controllers again. + +.. important:: + + The _EE frame denotes the part of the + configurable end effector frame which can be adjusted during run time through `franka_ros`. The + _K frame marks the center of the internal + Cartesian impedance. It also serves as a reference frame for external wrenches. *Neither the + _EE nor the _K are contained in the URDF as they can be changed at run time*. + By default, is set to "panda". + + .. figure:: _static/frames.svg + :align: center + :figclass: align-center + + Overview of the end-effector frames. + + +To recover from errors and reflexes when the robot is in reflex mode, you can utilize the +``franka_msgs::ErrorRecoveryAction``. This can be achieved through either an action client or by publishing on the +action goal topic. + +.. code-block:: shell + + rostopic pub -1 /franka_control/error_recovery/goal franka_msgs/ErrorRecoveryActionGoal "{}" + + +After recovery, the ``franka_control_node`` restarts the controllers that were running. That is +possible as the node does not die when robot reflexes are triggered or when errors have occurred. +All of these functionalities are provided by the ``franka_control_node`` which can be launched +with the following command: + +.. code-block:: shell + + roslaunch franka_control franka_control.launch \ + robot_ip:= # mandatory \ + load_gripper:= # default: true \ + robot:= # default: panda + + +Besides loading the ``franka_control_node``, the launch file also starts a +``franka_control::FrankaStateController`` for reading and publishing the robot states, including +external wrenches, configurable transforms and the joint states required for visualization with +rviz. For visualization purposes, a ``robot_state_publisher`` is started. + +This package also implements the ``franka_combined_control_node``, a hardware node for ``ros_control`` based +on the ``franka_hw::FrankaCombinedHW`` class. The set of robots loaded are configured via the ROS parameter +server. These parameters have to be in the hardware node's namespace (see `franka_combined_control_node.yaml +`__ +as a reference) and look like this: + +.. code-block:: yaml + + robot_hardware: + - panda_1 + - panda_2 + # (...) + + panda_1: + type: franka_hw/FrankaCombinableHW + arm_id: panda_1 + joint_names: + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + # Configure the threshold angle for printing joint limit warnings. + joint_limit_warning_threshold: 0.1 # [rad] + # Activate rate limiter? [true|false] + rate_limiting: true + # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate. + cutoff_frequency: 1000 + # Internal controller for motion generators [joint_impedance|cartesian_impedance] + internal_controller: joint_impedance + # Configure the initial defaults for the collision behavior reflexes. + collision_config: + lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + + panda_2: + type: franka_hw/FrankaCombinableHW + arm_id: panda_2 + joint_names: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 + # Configure the threshold angle for printing joint limit warnings. + joint_limit_warning_threshold: 0.1 # [rad] + # Activate rate limiter? [true|false] + rate_limiting: true + # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate. + cutoff_frequency: 1000 + # Internal controller for motion generators [joint_impedance|cartesian_impedance] + internal_controller: joint_impedance + # Configure the initial defaults for the collision behavior reflexes. + collision_config: + lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0] # [Nm] + lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0] # [N, N, N, Nm, Nm, Nm] + + # (+ more robots ...) + +.. note:: + + Be sure to choose unique and consistent ``arm_id`` parameters. The IDs must match the prefixes + in the joint names and should be according to the robot description loaded to the control + node's namespace. + +For more information on the parameter based loading of hardware classes, please refer to the +official documentation of ``combined_robot_hw::CombinedRobotHW`` from +``_. + +A second important parameter file +(see franka_ros/franka_control/config/default_combined_controllers.yaml as a reference) configures +a set of default controllers that can be started with the hardware node. The controllers have to match +the launched hardware. The provided default parameterization (here for 2 robots) looks like: + +.. code-block:: yaml + + panda_1_state_controller: + type: franka_control/FrankaStateController + arm_id: panda_1 + joint_names: + - panda_1_joint1 + - panda_1_joint2 + - panda_1_joint3 + - panda_1_joint4 + - panda_1_joint5 + - panda_1_joint6 + - panda_1_joint7 + publish_rate: 30 # [Hz] + + panda_2_state_controller: + type: franka_control/FrankaStateController + arm_id: panda_2 + joint_names: + - panda_2_joint1 + - panda_2_joint2 + - panda_2_joint3 + - panda_2_joint4 + - panda_2_joint5 + - panda_2_joint6 + - panda_2_joint7 + publish_rate: 30 # [Hz] + + # (+ more controllers ...) + +We provide a launch file to run the ``franka_combined_control_node`` with user specified configuration +files for hardware and controllers which default to a configuration with 2 robots. Launch it with: + +.. code-block:: shell + + roslaunch franka_control franka_combined_control.launch \ + robot_ips:= # mandatory + robot:= \ + args:= \ # if needed + robot_id:= \ + hw_config_file:=\ # includes the robot ips! + controllers_file:=\ + controllers_to_start:=\ + joint_states_source_list:= + +This launch file can be parameterized to run an arbitrary number of robots. +To do so just write your own configuration files in the style of +franka_control/config/franka_combined_control_node.yaml and +franka_ros/franka_control/config/default_combined_controllers.yaml. + +.. important:: + + Be sure to pass the correct IPs of your robots to `franka_combined_control.launch` as a map. + This looks like: `{/robot_ip: , /robot_ip: , ...}` + + + +.. _ros_visualization: + +franka_visualization +-------------------- +This package contains publishers that connect to a robot and publish the robot and +gripper joint states for visualization in RViz. To run this package launch: + +.. code-block:: shell + + roslaunch franka_visualization franka_visualization.launch robot_ip:= \ + load_gripper:= robot:= + + +This is purely for visualization - no commands are sent to the robot. It can be useful to check the +connection to the robot. + +.. important:: + + Only one instance of a ``franka::Robot`` can connect to the robot. This means, that for example + the ``franka_joint_state_publisher`` cannot run in parallel to the ``franka_control_node``. + This also implies that you cannot execute the visualization example alongside a separate + program running a controller. + + +.. _example_controllers: + +franka_example_controllers +-------------------------- +In this package a set of example controllers for controlling the robot via ROS are implemented. +The controllers depict the variety of interfaces offered by the ``franka_hw::FrankaHW`` class and +the according usage. Each example comes with a separate stand-alone launch file that starts the +controller on the robot and visualizes it. + +To launch the joint impedance example, execute the following command: + +.. code-block:: shell + + roslaunch franka_example_controllers joint_impedance_example_controller.launch \ + robot_ip:= load_gripper:= robot:= + +Other single Panda examples are started in the same way. + +The ``dual_arm_cartesian_impedance_example_controller`` showcases the control of two Panda robots +based on ``FrankaCombinedHW`` using one realtime controller for fulfilling Cartesian tasks with +an impedance-based control approach. The example controller can be launched with + +.. code-block:: shell + + roslaunch franka_example_controllers \ + dual_arm_cartesian_impedance_example_controller.launch \ + robot_id:= \ + robot_ips:= \ + rviz:= rqt:= + +The example assumes a robot configuration according to `dual_panda_example.urdf.xacro` where two +Pandas are mounted at 1 meter distance on top of a box. Feel free to replace this robot description +with one that matches your setup. +The option `rviz` allows to choose whether a visualization should be launched. With `rqt` the user +can choose to launch an rqt-gui which allows an online adaption of the rendered end-effector +impedances at runtime via dynamic reconfigure. + + +.. _franka_gazebo: + +franka_gazebo +------------- +This package allows you to simulate our robot in `Gazebo `_. This is possible +because Gazebo is able to integrate into the ROS control framework with the +`gazebo_ros `_ package. + +.. important:: This package is available from 0.8.0 + +Pick & Place Example +"""""""""""""""""""""" +Let's dive in and simulate transporting a stone from A to B. Run the following command to start Gazebo with a Panda +and an example world. + +.. code-block:: shell + + roslaunch franka_gazebo panda.launch x:=-0.5 \ + world:=$(rospack find franka_gazebo)/world/stone.sdf \ + controller:=cartesian_impedance_example_controller \ + rviz:=true + +This will bring up the Gazebo GUI where you see the environment with the stone and RViz with which you can control +the end-effector pose of the robot. + + +.. figure:: _static/franka-gazebo-example.png + :align: center + :figclass: align-center + + Gazebo GUI (left) and RViz (right) of the pick and place example + +To open the gripper, simply send a goal to the ``move`` action, similar to how the real ``franka_gripper`` +works. Let's move the gripper to a width of :math:`8\:cm` between the fingers with :math:`10\:\frac{cm}{s}`: + +.. code-block:: shell + + rostopic pub --once /franka_gripper/move/goal franka_gripper/MoveActionGoal "goal: { width: 0.08, speed: 0.1 }" + + +Since we launched our robot with the Cartesian Impedance controller from +:ref:`franka_example_controllers`, we can move the end-effector around, just like in reality, +with the interactive marker gizmo in RViz. Move the robot such that the white stone is between the fingers of the +gripper ready to be picked up. + +.. note:: + + If the robot moves strangely with the elbow, this is because the default nullspace stiffness of the cartesian + impedance example controller is set to low. Launch `Dynamic Reconfigure `_ + and adjust ``panda`` > ``cartesian_impedance_example_controller`` > ``nullspace_stiffness`` if necessary. + +To pick up the object, we use the ``grasp`` action this time, since we want to excerpt a force after +the grasp to not drop the object. The stone is around :math:`3\:cm` wide and :math:`50\:g` heavy. +Let's grasp it with :math:`5\:N`: + +.. code-block:: shell + + rostopic pub --once /franka_gripper/grasp/goal \ + franka_gripper/GraspActionGoal \ + "goal: { width: 0.03, epsilon:{ inner: 0.005, outer: 0.005 }, speed: 0.1, force: 5.0}" +.. note:: + + In top menu of Gazebo go to **View** > **Contacts** to visualize contact points and forces + +If the grasp succeeded, the fingers will now hold the stone in place. If not, probably the goal tolerances (inner +and outer epsilon) were too small and the action failed. Now move the object gently over to the red dropoff area. + +.. figure:: _static/franka-gazebo-example-grasp.png + :align: center + :figclass: align-center + + Transport the stone from blue to red + +After you placed it gently on the red pad, stop the grasp with the ``stop`` action from the gripper: + +.. code-block:: shell + + rostopic pub --once /franka_gripper/stop/goal franka_gripper/StopActionGoal {} + +Note that the contact forces disappear now, since no force is applied anymore. Alternatively you can also use +the ``move`` action. + +Customization +"""""""""""""" + +The launch file from ``franka_gazebo`` takes a lot of arguments with which you can customize the behavior +of the simulation. For example to spawn two pandas in one simulation you can use the following: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +.. note:: + + To see which arguments are supported use: ``roslaunch franka_gazebo panda.launch --ros-args`` + +FrankaHWSim +""""""""""" + +By default Gazebo ROS can only simulate joints with "standard" hardware interfaces like `Joint State Interfaces` +and `Joint Command Interfaces`. However our robot is quite different from this architecture! Next to +these joint-specific interfaces it also supports **robot-specific** interfaces like the ``FrankaModelInterface`` (see +:ref:`franka_hw `). Naturally gazebo does not understand these custom hardware interfaces by default. +This is where the ``FrankaHWSim`` plugin comes in. + +To make your robot capable of supporting Franka interfaces, simply declare a custom ``robotSimType`` in your URDF: + +.. code-block:: xml + + + + ${arm_id} + 0.001 + franka_gazebo/FrankaHWSim + + true + + + +When you spawn this robot with the `model spawner +`_ this plugin +will be loaded into the gazebo node. It will scan your URDF and try to find supported hardware interfaces. Up to now +only some of the interfaces provided by :ref:`franka_hw ` are supported: + + ++---+-------------------------------------------------+----------------------------------------------+ +| | Interface | Function | ++===+=================================================+==============================================+ +| ✔ | ``hardware_interface::JointStateInterface`` | Reads joint states. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✔ | ``hardware_interface::EffortJointInterface`` | Commands joint-level torques and reads | +| | | joint states. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✔ | ``hardware_interface::VelocityJointInterface`` | Commands joint velocities and reads joint | +| | | states. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✔ | ``hardware_interface::PositionJointInterface`` | Commands joint positions and reads joint | +| | | states. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✔ | ``franka_hw::FrankaStateInterface`` | Reads the full robot state. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✔ | ``franka_hw::FrankaModelInterface`` | Reads the dynamic and kinematic model of the | +| | | robot. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✘ | ``franka_hw::FrankaPoseCartesianInterface`` | Commands Cartesian poses and reads the full | +| | | robot state. | ++---+-------------------------------------------------+----------------------------------------------+ +| ✘ | ``franka_hw::FrankaVelocityCartesianInterface`` | Commands Cartesian velocities and reads the | +| | | full robot state. | ++---+-------------------------------------------------+----------------------------------------------+ + +.. important:: + + This implies that you can only simulate controllers, that claim these supported interfaces and none other! + For example the Cartesian Impedance Example Controller can be simulated, because it only requires the + ``EffortJoint``-, ``FrankaState``- and ``FrankaModelInterface``. However the Joint Impedance Example Controller + can't be simulated, because it requires the ``FrankaPoseCartesianInterface`` which is not supported yet. + +Next to the realtime hardware interfaces the ``FrankaHWSim`` plugin supports some of the non-realtime commands +that :ref:`franka_control ` supports: + + ++---+-------------------------------------------+--------------------------------------------------------------+ +| | Service / Type | Explanation | ++===+===========================================+==============================================================+ +| ✘ | ``set_joint_impedance`` / | Gazebo does not simulate an internal impedance | +| | `SetJointImpedance`_ | controller, but sets commanded torques directly | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✘ | ``set_cartesian_impedance`` / | Gazebo does not simulate an internal impedance | +| | `SetCartesianImpedance`_ | controller, but sets commanded torques directly | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✔ | ``set_EE_frame`` / | Sets the :math:`{}^{\mathrm{NE}}\mathbf{T}_{\mathrm{EE}}` | +| | `SetEEFrame`_ | i.e. the homogenous transformation from nominal end-effector | +| | | to end-effector. You can also initialize this by setting the | +| | | ROS parameter ``//NE_T_EE``. Normally you would set | +| | | :math:`{}^{\mathrm{F}}\mathbf{T}_{\mathrm{NE}}` in Desk, but | +| | | in ``franka_gazebo`` it's assumed as identity if no gripper | +| | | was specified or defines a rotation around Z by :math:`45\:°`| +| | | and an offset by :math:`10.34\:cm` (same as Desk for the | +| | | hand). You can always overwrite this value by setting the ROS| +| | | parameter ``//NE_T_EE`` manually. | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✔ | ``set_K_frame`` / | Sets the :math:`{}^{\mathrm{EE}}\mathbf{T}_{\mathrm{K}}` i.e.| +| | `SetKFrame`_ | the homogenous transformation from end-effector to stiffness | +| | | frame. | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✔ | ``set_force_torque_collision_behavior`` / | Sets thresholds above which external wrenches are treated as | +| | `SetForceTorqueCollisionBehavior`_ | contacts and collisions. | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✘ | ``set_full_collision_behavior`` / | Not yet implemented | +| | `SetFullCollisionBehavior`_ | | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✔ | ``set_load`` / | Sets an external load to compensate its gravity for, e.g. of | +| | `SetLoad`_ | a grasped object. You can also initialize this by setting | +| | | the ROS parameters ``//{m_load,I_load,F_x_load}`` | +| | | for mass, inertia tensor and center of mass for the load, | +| | | respectively. | ++---+-------------------------------------------+--------------------------------------------------------------+ +| ✔ | ``set_user_stop`` / | This is a special service only available in ``franka_gazebo``| +| | `std_srvs::SetBool`_ | to simulate the user stop. Pressing the user stop (a.k.a | +| | (since 0.9.1) | publishing a ``true`` via this service) will *disconnect* | +| | | all command signals from ROS controllers to be fed to the | +| | | joints. To connect them again call the ``error_recovery`` | +| | | action. | ++---+-------------------------------------------+--------------------------------------------------------------+ + +.. _SetJointImpedance: http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetJointImpedance.html +.. _SetCartesianImpedance: http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetCartesianImpedance.html +.. _SetEEFrame: http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetEEFrame.html +.. _SetKFrame: http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetKFrame.html +.. _SetForceTorqueCollisionBehavior: + http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetForceTorqueCollisionBehavior.html +.. _SetFullCollisionBehavior: + http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetFullCollisionBehavior.html +.. _SetLoad: http://docs.ros.org/en/noetic/api/franka_msgs/html/srv/SetLoad.html +.. _std_srvs::SetBool: http://docs.ros.org/en/noetic/api/std_srvs/html/srv/SetBool.html + +FrankaGripperSim +"""""""""""""""" + +This plugin simulates the :ref:`franka_gripper_node ` in Gazebo. This is done as a ROS controller for +the two finger joints with a position & force controller. It offers the same five actions like the real gripper node: + +* ``//franka_gripper/homing`` +* ``//franka_gripper/stop`` +* ``//franka_gripper/move`` +* ``//franka_gripper/grasp`` +* ``//franka_gripper/gripper_action`` + +.. important:: + The ``grasp`` action has a bug, that it will not succeed nor abort if the target width + lets the fingers **open**. This is because of missing the joint limits interface which + lets the finger oscillate at their limits. For now only use the ``grasp`` action to *close* + the fingers! + + +It is assumed that the URDF contains two finger joints which can be force controlled, i.e. have a corresponding +``EffortJointInterface`` transmission declared. This controller expects the following parameters in its namespace: + +* ``type`` (string, required): Should be ``franka_gazebo/FrankaGripperSim`` +* ``arm_id`` (string, required): The arm id of the panda, to infer the name of the finger joints +* ``finger1/gains/p`` (double, required): The proportional gain for the position-force controller of the first finger +* ``finger1/gains/i`` (double, default: 0): The integral gain for the position-force controller of the first finger +* ``finger1/gains/d`` (double, default: 0): The differential gain for the position-force controller of the first finger +* ``finger2/gains/p`` (double, required): The proportional gain for the position-force controller of the second finger +* ``finger2/gains/i`` (double, default: 0): The integral gain for the position-force controller of the second finger +* ``finger2/gains/d`` (double, default: 0): The differential gain for the position-force controller of the second finger +* ``move/width_tolerance`` (double, default :math:`5\:mm`): The move action succeeds, when the finger width becomes + below this threshold +* ``grasp/resting_threshold`` (double, default :math:`1\:\frac{mm}{s}`): Below which speed the target width should + be checked to abort or succeed the grasp action +* ``grasp/consecutive_samples`` (double, default: 3): How many times the speed has to be consecutively below + ``resting_threshold`` before the grasping will be evaluated +* ``gripper_action/width_tolerance`` (double, default :math:`5\:mm`): The gripper action succeeds, when the finger + width becomes below this threshold +* ``gripper_action/speed`` (double, default :math:`10\:\frac{cm}{s}`): The speed to use during the gripper action + + + + +JointStateInterface +""""""""""""""""""""" + +To be able to access the joint state interface from a ROS controller you only have to declare the corresponding +joint in any transmission tag in the URDF. Then a joint state interface will be automatically available. Usually +you declare transmission tags for command interfaces like the :ref:`EffortJointInterface `. + +.. note:: + For any joint named ``_jointN`` (with N as integer) FrankaHWSim will automatically compensate its gravity + to mimic the behavior of libfranka. + +.. _effort_joint_interface: + +EffortJointInterface +"""""""""""""""""""""" + +To be able to send effort commands from your controller to a joint, you simply declare a transmission tag for the +joint in your URDF with the corresponding hardware interface type: + +.. code-block:: xml + + + transmission_interface/SimpleTransmission + + hardware_interface/EffortJointInterface + + + ${transmission} + + + + + + true + + + +.. note:: + + If you want to be able to read external forces or torques, which come e.g. from collisions, make sure to set the + ```` tag to ``true``. + +FrankaStateInterface +"""""""""""""""""""""" + +This is a **robot-specific** interface and thus a bit different from the normal hardware interfaces. +To be able to access the franka state interface from your controller declare the following transmission tag with +all seven joints in your URDF: + +.. code-block:: xml + + + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + franka_hw/FrankaStateInterface + + +When your controller accesses the `robot state +`_ via the ``FrankaStateInterface`` it can +expect the following values to be simulated: + ++---+----------------------------------+------------------------------------------------------------------------+ +| | Field | Comment | ++===+==================================+========================================================================+ +| ✔ | ``O_T_EE`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``O_T_EE_d`` | Motion generation not yet supported, field will contain only zeros | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``F_T_EE`` | Can be configured via parameters ``F_T_NE``, ``NE_T_EE`` and/or | +| | | service calls to ``set_EE_frame`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``NE_T_EE`` | Can be configured via parameter ``NE_T_EE`` and/or service calls | +| | | to ``set_EE_frame`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``EE_T_K`` | Can be configured via parameter ``EE_T_K`` and/or service calls | +| | | to ``set_K_frame`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``m_ee`` | Will be set from the mass in the inertial tag of URDF, if a hand can | +| | | be found, otherwise zero. Can be overwritten by parameter ``m_ee`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``I_ee`` | Will be set from the inertia in the inertial tag of URDF, if a hand | +| | | be found, otherwise zero. Can be overwritten by parameter ``I_ee`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``F_x_Cee`` | Will be set from the origin in the inertial tag of URDF, if a hand can | +| | | be found, otherwise zero. Can be overwritten by parameter ``F_x_Cee`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``m_load`` | Can be configured via parameter ``m_load`` and/or service calls to | +| | | ``set_load`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``I_load`` | Can be configured via parameter ``I_load`` and/or service calls to | +| | | ``set_load`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``F_x_Cload`` | Can be configured via parameter ``F_x_Cload`` and/or service calls to | +| | | ``set_load`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``m_total`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``I_total`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``F_x_Ctotal`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``elbow`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``elbow_d`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``elbow_c`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``delbow_d`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``delbow_c`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``tau_J`` | Comes directly from Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``tau_J_d`` | The values send by your effort controller. Zero otherwise. | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``dtau_J`` | Numerical derivative of ``tau_J`` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``q`` | Comes directly from Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``q_d`` | The last commanded joint position when using the position interface. | +| | | Same as ``q`` when using the velocity interface. However, | +| | | the value will not be updated when using the effort interface. | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``dq`` | Comes directly from Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``dq_d`` | The last commanded joint velocity when using the velocity interface. | +| | | Same as ``dq`` when using the position interface. However, | +| | | the value will be zero when using the effort interface. | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``ddq_d`` | Current acceleration when using the position or velocity interface. | +| | | However, the value will be zero when using the effort interface. | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``joint_contact`` | :math:`\mid \hat{\tau}_{ext} \mid > \mathrm{thresh}_{lower}` where the | +| | | threshold can be set by calling ``set_force_torque_collision_behavior``| ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``joint_collision`` | :math:`\mid \hat{\tau}_{ext} \mid > \mathrm{thresh}_{upper}` where the | +| | | threshold can be set by calling ``set_force_torque_collision_behavior``| ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``cartesian_contact`` | :math:`\mid {}^K \hat{F}_{K,ext} \mid > \mathrm{thresh}_{lower}` where | +| | | threshold can be set by calling ``set_force_torque_collision_behavior``| ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``cartesian_collision`` | :math:`\mid {}^K \hat{F}_{K,ext} \mid > \mathrm{thresh}_{upper}` where | +| | | threshold can be set by calling ``set_force_torque_collision_behavior``| ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``tau_ext_hat_filtered`` | :math:`\hat{\tau}_{ext}` i.e. estimated external torques and forces at | +| | | the end-effector, filtered with a exponential moving average filter | +| | | (EMA). This filtering :math:`\alpha` can be configured via a ROS | +| | | parameter. This field does not contain any gravity, i.e. | +| | | :math:`\tau_{ext} = \tau_J - \tau_{J_d} - \tau_{gravity}` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``O_F_ext_hat_K`` | :math:`{}^O\hat{F}_{K,ext} = J_O^{\top +} \cdot \hat{\tau}_{ext}` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``K_F_ext_hat_K`` | :math:`{}^K\hat{F}_{K,ext} = J_K^{\top +} \cdot \hat{\tau}_{ext}` | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``O_dP_EE_d`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``O_ddP_0`` | Will be the same as the ``gravity_vector`` ROS parameter. | +| | | By default it is {0,0,-9.8} | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``O_T_EE_c`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``O_dP_EE_c`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``O_ddP_EE_c`` | | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``theta`` | Same as ``q``, since we don't simulate soft joints in Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``dtheta`` | Same as ``dq``, since we don't simulate soft joints in Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``current_errors`` | Will entirely be false, reflex system not yet implemented | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``last_motion_errors`` | Will entirely be false, reflex system not yet implemented | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``control_command_success_rate`` | Always 1.0 | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✘ | ``robot_mode`` | Robot mode switches and reflex system not yet implemented | ++---+----------------------------------+------------------------------------------------------------------------+ +| ✔ | ``time`` | Current ROS time in simulation, comes from Gazebo | ++---+----------------------------------+------------------------------------------------------------------------+ + + +FrankaModelInterface +"""""""""""""""""""""" + +This is a **robot-specific** interface and thus a bit different from the normal hardware interfaces. +To be able to access the franka model interface from your controller declare the following transmission tag with +the root (e.g. ``panda_joint1``) and the tip (e.g. ``panda_joint8``) of your kinematic chain in your URDF: + +.. code-block:: xml + + + franka_hw/FrankaModelInterface + + root + franka_hw/FrankaModelInterface + + + tip + franka_hw/FrankaModelInterface + + + franka_hw/FrankaModelInterface + franka_hw/FrankaModelInterface + + +The model functions themselve are implemented with `KDL `_. This takes the kinematic +structure and the inertial properties from the URDF to calculate model properties like the Jacobian or the mass matrix. + +Friction +""""""""" + +For objects to have proper friction between each other (like fingers and objects) you need to tune +some Gazebo parameters in your URDF. For the links ``panda_finger_joint1`` and ``panda_finger_joint2`` we recommend to +set the following parameters: + +.. code-block:: xml + + + + 10 + + + + + + + 0 + 0.003 + + + + + + 1.16 + 1.16 + + + + + + + +.. note:: + + Refer to `Gazebo Friction Documentation `_ + + +franka_msgs +----------- +This package contains message, service and action types that are primarily used the packages +``franka_hw`` and ``franka_control`` to publish robot states or to expose the libfranka API +in the ROS ecosystem. For more information about the services and actions offered in this +package, please refer to :ref:`franka_control `. + + +panda_moveit_config +-------------------- + +.. note:: + + This package was moved to the `ros_planning repos `_. + +For more details, documentation and tutorials, please have a look at the +`MoveIt! tutorials website `_. + + +.. _write_own_controller: + +Writing your own controller +---------------------------- +All example controllers from the :ref:`example controllers package` are +derived from the ``controller_interface::MultiInterfaceController`` class, which allows to claim +up to four interfaces in one controller instance. The declaration of your class then looks like: + +.. code-block:: c++ + + class NameOfYourControllerClass : controller_interface::MultiInterfaceController < + my_mandatory_first_interface, + my_possible_second_interface, + my_possible_third_interface, + my_possible_fourth_interface> { + bool init (hardware_interface::RobotHW* hw, ros::NodeHandle& nh); // mandatory + void update (const ros::Time& time, const ros::Duration& period); // mandatory + void starting (const ros::Time& time) // optional + void stopping (const ros::Time& time); // optional + ... + } + + +The available interfaces are described in Section :ref:`franka_hw `. + +.. important:: + + Note that the claimable combinations of commanding interfaces are restricted as it does not + make sense to e.g. command joint positions and Cartesian poses simultaneously. Read-only + interfaces like the *JointStateInterface*, the *FrankaStateInterface* or the + *FrankaModelInterface* can always be claimed and are not subject to restrictions. + + +Possible claims to command interfaces are: + ++-------------------------------------------------+----------------------------------------------+ +| ``franka_hw::FrankaHW`` | ``franka_combinable_hw::FrankaCombinableHW`` | ++=================================================+==============================================+ +| - all possible single interface claims | - ``EffortJointInterface`` | +| - ``EffortJointInterface`` + | - ``EffortJointInterface`` + | +| ``PositionJointInterface`` | ``FrankaCartesianPoseInterface`` | +| - ``EffortJointInterface`` + | - ``EffortJointInterface`` + | +| ``VelocityJointInterface`` | ``FrankaCartesianVelocityInterface`` | +| - ``EffortJointInterface`` + | | +| ``FrankaCartesianPoseInterface`` | | +| - ``EffortJointInterface`` + | | +| ``FrankaCartesianVelocityInterface`` | | ++-------------------------------------------------+----------------------------------------------+ + +The idea behind offering the *EffortJointInterface* in combination with a motion generator +interface is to expose the internal motion generators to the user. The calculated desired joint +pose corresponding to a motion generator command is available in the robot state one time step +later. One use case for this combination would be following a Cartesian trajectory using your own +joint-level torque controller. In this case you would claim the combination *EffortJointInterface* ++ *FrankaCartesianPoseInterface*, stream your trajectory into the *FrankaCartesianPoseInterface*, +and compute your joint-level torque commands based on the resulting desired joint pose (q_d) from +the robot state. This allows to use the robot's built-in inverse kinematics instead of having to +solve it on your own. + +To implement a fully functional controller you have to implement at least the inherited virtual +functions ``init`` and ``update``. Initializing - e.g. start poses - should be done in the +``starting`` function as ``starting`` is called when restarting the controller, while ``init`` is +called only once when loading the controller. The ``stopping`` method should contain shutdown +related functionality (if needed). + +.. important:: + + Always command a gentle slowdown before shutting down the controller. When using velocity + interfaces, do not simply command zero velocity in ``stopping``. Since it might be called + while the robot is still moving, it would be equivalent to commanding a jump in velocity + leading to very high resulting joint-level torques. In this case it would be better to keep the + same velocity and stop the controller than sending zeros and let the robot handle + the slowdown. + +Your controller class must be exported correctly with ``pluginlib`` which requires adding: + +.. code-block:: c++ + + #include + // Implementation .. + PLUGINLIB_EXPORT_CLASS(name_of_your_controller_package::NameOfYourControllerClass, + controller_interface::ControllerBase) + + +at the end of the ``.cpp`` file. In addition you need to define a ``plugin.xml`` file with the +following content: + +.. code-block:: xml + + + + + Some text to describe what your controller is doing + + + + + +which is exported by adding: + +.. code-block:: xml + + + + + + +to your package.xml. Further, you need to load at least a controller name in combination with a +controller type to the ROS parameter server. Additionally, you can include other parameters you +need. An exemplary configuration.yaml file can look like: + +.. code-block:: yaml + + your_custom_controller_name: + type: name_of_your_controller_package/NameOfYourControllerClass + additional_example_parameter: 0.0 + # .. + +Now you can start your controller using the ``controller_spawner`` node from ROS control or via the +service calls offered by the ``hardware_manager``. Just make sure that both the +``controller_spawner`` and the ``franka_control_node`` run in the same namespace. For more details +have a look at the controllers from the +:ref:`franka_example_controllers package` or the +`ROS control tutorials `_. diff --git a/_sources/franka_ros2.rst.txt b/_sources/franka_ros2.rst.txt new file mode 100644 index 0000000..00a0b54 --- /dev/null +++ b/_sources/franka_ros2.rst.txt @@ -0,0 +1,685 @@ +franka_ros2 +=========== +.. note:: + + ``franka_ros2`` is not supported on Windows. + +The `franka_ros2 repo `_ contains a ROS 2 integration of +:doc:`libfranka `. + +.. caution:: + franka_ros2 is in rapid development. Anticipate breaking changes. Report bugs on + `GitHub `_. + + +Prerequisites +------------- + +* A `ROS 2 Humble installation `_ + (ros-humble-desktop) or a VSCode IDE with DevContainer. +* A :ref:`PREEMPT_RT kernel ` (optional, but strongly recommended). +* For ``cartesian_pose``, ``joint_position`` and ``elbow_position`` command interfaces realtime-kernel is absolutely necessary. +* A system-wide :ref:`libfranka installation `. Minimum supported version of libfranka is 0.13.0. + Here is a minimal example: + +.. code-block:: shell + + sudo apt install -y libpoco-dev libeigen3-dev + git clone https://github.com/frankaemika/libfranka.git --recursive + cd libfranka + git switch 0.13.2 + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF .. + cmake --build . -j$(nproc) + cpack -G DEB + sudo dpkg -i libfranka-*.deb + +Optional .bashrc Settings +^^^^^^^^^^^^^^^^^^^^^^^^^ + +* To get colorized warn and error messages you can put + ``export RCUTILS_COLORIZED_OUTPUT=1`` into your ``.bashrc``. + +* If your system language is not set to American English you should put + ``export LC_NUMERIC=en_US.UTF-8`` into your ``.bashrc`` to avoid issues in RViz. + +Setup +------ + +Install From Source +^^^^^^^^^^^^^^^^^^^ + +1. Install requirements:: + + sudo apt install -y \ + ros-humble-hardware-interface \ + ros-humble-generate-parameter-library \ + ros-humble-ros2-control-test-assets \ + ros-humble-controller-manager \ + ros-humble-control-msgs \ + ros-humble-xacro \ + ros-humble-angles \ + ros-humble-ros2-control \ + ros-humble-realtime-tools \ + ros-humble-control-toolbox \ + ros-humble-moveit \ + ros-humble-ros2-controllers \ + ros-humble-joint-state-publisher \ + ros-humble-joint-state-publisher-gui \ + ros-humble-ament-cmake \ + ros-humble-ament-cmake-clang-format \ + python3-colcon-common-extensions + +2. Create a ROS 2 workspace:: + + mkdir -p ~/franka_ros2_ws/src + +3. Clone repo and build packages:: + + source /opt/ros/humble/setup.bash + cd ~/franka_ros2_ws + git clone https://github.com/frankaemika/franka_ros2.git src/franka_ros2 + colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release + source install/setup.sh + +Use VSCode DevContainer +^^^^^^^^^^^^^^^^^^^^^^^ +FrankaROS2 package comes with .devcontainer folder which enables developer to use FrankaROS2 packages without manually installing ROS2 or libfranka. +VSCode DevContainer working schematic is shown in the below image: + + .. figure:: _static/vscode.png + :align: center + :figclass: align-center + +1. Follow the setup guide from VSCode `devcontainer_setup `_. + +2. Create a ROS 2 workspace:: + + mkdir franka_ros2_ws + cd franka_ros2_ws + +3. Clone repo:: + + git clone https://github.com/frankaemika/franka_ros2.git src/franka_ros2 + +4. Move the .devcontainer folder to the franka_ros2_ws parent folder:: + + mv franka_ros2/.devcontainer . + +5. Open VSCode:: + + code . + +6. Open the current folder in DevContainer:: + + ctrl + shift + p + + Write in the command prompt bar:: + + Dev Containers: Rebuild and Reopen in Container + + and click this option in the search results + +7. Open up the terminal in VScode:: + + ctrl + ` + +8. Source the environment:: + + source /ros_entrypoint.sh + +9. Install the Franka ROS 2 packages:: + + colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release + source install/setup.sh + +MoveIt +------ + +To see if everything works, you can try to run the MoveIt example on the robot:: + + ros2 launch franka_moveit_config moveit.launch.py robot_ip:= + +Then activate the ``MotionPlanning`` display in RViz. + +If you do not have a robot you can still test your setup by running on a dummy hardware:: + + ros2 launch franka_moveit_config moveit.launch.py robot_ip:=dont-care use_fake_hardware:=true + + +Wait until you can see the green ``You can start planning now!`` message from MoveIt inside the +terminal. Then turn off the ``PlanningScene`` and turn it on again. After that turn on the ``MotionPlanning``. + + +Example Controllers +------------------- + +This repo comes with a few example controllers located in the ``franka_example_controllers`` package. + +The following launch files are executed with the gripper by default. If you +do not have the gripper attached you can disable the gripper in the launch file with ``load_gripper:=false``. + +Move-to-start +^^^^^^^^^^^^^ + +This controller moves the robot to its home configuration. + +.. code-block:: shell + + ros2 launch franka_bringup move_to_start_example_controller.launch.py robot_ip:= + +.. _gravity_example: + +Gravity Compensation +^^^^^^^^^^^^^^^^^^^^ + +This is the simplest controller that we have and is a good starting point to write your own. +It sends zero as torque command to all joints, which means that the robot only compensates its own weight. + +.. code-block:: shell + + ros2 launch franka_bringup gravity_compensation_example_controller.launch.py robot_ip:= + +Joint Impedance Example +^^^^^^^^^^^^^^^^^^^^^^^ + +The example moves joints 4 and 5 in a periodic movement that is very compliant. You can try to move the +joints while it is running. + +.. code-block:: shell + + ros2 launch franka_bringup joint_impedance_example_controller.launch.py robot_ip:= + +Joint Impedance With IK Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example uses the LMA-Orocos solver from MoveIt service to compute the joint positions for the desired pose. +The desired pose is to move the end-effector periodically in x and z directions. You can change the kinematic solver +in the franka_moveit_config package, kinematics.yaml file. + +.. code-block:: shell + + ros2 launch franka_bringup joint_impedance_with_ik_example_controller.launch.py robot_ip:= + + +Model Example Controller +^^^^^^^^^^^^^^^^^^^^^^^^ +This is a read-only controller which prints the coriolis force vector, gravity force vector, pose matrix of Joint4, +Joint4 body jacobian and end-effector jacobian with respect to the base frame. + +.. code-block:: shell + + ros2 launch franka_bringup model_example_controller.launch.py robot_ip:= + +Joint Position Example +^^^^^^^^^^^^^^^^^^^^^^ +This example sends periodic position commands to the robot. + +.. important:: + The position trajectory needs to start from the initial position of the robot. + +To read the start position of the robot, you can read claim the `initial_joint_position`. +state interface values before starting to send any commands. + +.. code-block:: cpp + + if (initialization_flag_) { + for (size_t i = 0; i < 7; ++i) { + initial_q_.at(i) = state_interface[i].get_value(); + } + initialization_flag_ = false; + } + + +.. code-block:: shell + + ros2 launch franka_bringup joint_position_example_controller.launch.py robot_ip:= + +Joint Velocity Example +^^^^^^^^^^^^^^^^^^^^^^ +This example sends periodic velocity commands to the 4th and 5th joint of the robot. + +.. code-block:: shell + + ros2 launch franka_bringup joint_velocity_example_controller.launch.py robot_ip:= + +Cartesian Pose Example +^^^^^^^^^^^^^^^^^^^^^^ +This example uses the CartesianPose interface to send periodic pose commands to the robot. + +.. code-block:: shell + + ros2 launch franka_bringup cartesian_pose_example_controller.launch.py robot_ip:= + +Cartesian Orientation Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This example uses CartesianOrientation interface to send periodic orientation commands around X axis of the end effector of the robot. + +.. code-block:: shell + + ros2 launch franka_bringup cartesian_orientation_example_controller.launch.py robot_ip:= + +Cartesian Pose Elbow Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This example sends periodic elbow commands while keeping the end effector pose constant. + +.. code-block:: shell + + ros2 launch franka_bringup cartesian_elbow_example_controller.launch.py robot_ip:= + +Cartesian Velocity Example +^^^^^^^^^^^^^^^^^^^^^^^^^^ +This example uses the CartesianVelocity interface to send periodic velocity commands to the robot. + +.. code-block:: shell + + ros2 launch franka_bringup cartesian_velocity_example_controller.launch.py robot_ip:= + +Cartesian Elbow Example +^^^^^^^^^^^^^^^^^^^^^^^ +This example uses the CartesianElbow interface to send periodic elbow commands to the robot while keeping the end effector velocity constant. + +.. code-block:: shell + + ros2 launch franka_bringup elbow_example_controller.launch.py robot_ip:= + + +Package Descriptions +-------------------- + +This section contains more detailed descriptions of what each package does. In general the package structure tries to +adhere to the structure that is proposed +`here `_. + + +franka_bringup +^^^^^^^^^^^^^^ + +This package contains the launch files for the examples as well as the basic ``franka.launch.py`` launch file, that +can be used to start the robot without any controllers. + +When you start the robot with:: + + ros2 launch franka_bringup franka.launch.py robot_ip:= use_rviz:=true + +There is no controller running apart from the ``joint_state_broadcaster``. However, a connection with the robot is still +established and the current robot pose is visualized in RViz. In this mode the robot can be guided when the user stop +button is pressed. However, once a controller that uses the ``effort_command_interface`` is started, the robot will be +using the torque interface from libfranka. For example it is possible to launch the +``gravity_compensation_example_controller`` by running:: + + ros2 control load_controller --set-state active gravity_compensation_example_controller + +This is the equivalent of running the ``gravity_compensation_example_controller.launch.py`` launch file mentioned in +:ref:`Gravity Compensation `. + +When the controller is stopped with:: + + ros2 control set_controller_state gravity_compensation_example_controller inactive + +the robot will stop the torque control and will only send its current state over the FCI. + +You can now choose to start the same controller again with:: + + ros2 control set_controller_state gravity_compensation_example_controller active + +or load and start a different one:: + + ros2 control load_controller --set-state active joint_impedance_example_controller + + +.. _franka_description: + +franka_description +^^^^^^^^^^^^^^^^^^ + +This package contains the xacro files and meshes that are used to visualize the robot. +Further, it contains a launch file that visualizes the robot model without access to a real robot:: + + ros2 launch franka_description visualize_franka.launch.py load_gripper:= + + +.. _franka_example_controllers: + +franka_example_controllers +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This package contains a few controllers that can be seen as example of how to write controllers in ROS 2. Currently, +a controller only has access to measured joint positions and joint velocities. Based on this information the controller +can send torque commands. It is currently not possible to use other interfaces like the joint position interface. + +franka_gripper +^^^^^^^^^^^^^^ + +This package contains the ``franka_gripper_node`` for interfacing with the ``Franka Hand``. + +The ``franka_gripper_node`` provides the following actions: + +* ``homing`` - homes the gripper and updates the maximum width given the mounted fingers. +* ``move`` - moves to a target width with the defined speed. +* ``grasp`` - tries to grasp at the desired width with the desired force while closing with the given speed. The operation is successful if the + distance ``d`` between the gripper fingers is ``width - epsilon.inner < d < width + epsilon.outer`` +* ``gripper_action`` - a special grasping action for MoveIt. + +Also, there is a ``stop`` service that aborts gripper actions and stops grasping. + + +Use the following launch file to start the gripper:: + + ros2 launch franka_gripper gripper.launch.py robot_ip:= + + +In a different tab you can now perform the homing and send a grasp command.:: + + + ros2 action send_goal /panda_gripper/homing franka_msgs/action/Homing {} + ros2 action send_goal -f /panda_gripper/grasp franka_msgs/action/Grasp "{width: 0.00, speed: 0.03, force: 50}" + +The inner and outer epsilon are 0.005 meter per default. You can also explicitly set the epsilon:: + + ros2 action send_goal -f /panda_gripper/grasp franka_msgs/action/Grasp "{width: 0.00, speed: 0.03, force: 50, epsilon: {inner: 0.01, outer: 0.01}}" + +To stop the grasping, you can use ``stop`` service.:: + + ros2 service call /panda_gripper/stop std_srvs/srv/Trigger {} + +.. _franka_hardware: + +franka_hardware +^^^^^^^^^^^^^^^ + +This package contains the ``franka_hardware`` plugin needed for `ros2_control `_. +The plugin is loaded from the URDF of the robot and passed to the controller manager via the robot description. +It provides for each joint: + +* a ``position state interface`` that contains the measured joint position. +* a ``velocity state interface`` that contains the measured joint velocity. +* an ``effort state interface`` that contains the measured link-side joint torques including gravity. +* an ``initial_position state interface`` that contains the initial joint position of the robot. +* an ``effort command interface`` that contains the desired joint torques without gravity. +* a ``position command interface`` that contains the desired joint position. +* a ``velocity command interface`` that contains the desired joint velocity. + +In addition + +* a ``franka_robot_state`` that contains the robot state information, `franka_robot_state `_. +* a ``franka_robot_model_interface`` that contains the pointer to the model object. + +.. important:: + ``franka_robot_state`` and ``franka_robot_model_interface`` state interfaces should not be used directly from hardware state interface. + Rather, they should be utilized by the :ref:`franka_semantic_components` interface. + +The IP of the robot is read over a parameter from the URDF. + +.. _franka_semantic_components: + +franka_semantic_components +^^^^^^^^^^^^^^^^^^^^^^^^^^ +This package contains franka_robot_model, franka_robot_state and cartesian command classes. +These classes are used to convert franka_robot_model object and franka_robot_state objects, +which are stored in the hardware_state_interface as a double pointer. + +For further reference on how to use these classes: +`Franka Robot State Broadcaster `_ +and +`Franka Example Controllers(model_example_controller) +`_ + +- Cartesian Pose Interface: + +This interface is used to send Cartesian pose commands to the robot by using the loaned command interfaces. +FrankaSemanticComponentInterface class is handling the loaned command interfaces and state interfaces. +While starting the cartesian pose interface, the user needs to pass a boolean flag to the constructor +to indicate whether the interface is for the elbow or not. + +.. code-block:: cpp + + auto is_elbow_active = false; + CartesianPoseInterface cartesian_pose_interface(is_elbow_active); + +This interface allows users to read the current pose command interface values set by the franka hardware interface. + +.. code-block:: cpp + + std::array pose; + pose = cartesian_pose_interface.getInitialPoseMatrix(); + +One could also read quaternion and translation values in Eigen format. + +.. code-block:: cpp + + Eigen::Quaterniond quaternion; + Eigen::Vector3d translation; + std::tie(quaternion, translation) = cartesian_pose_interface.getInitialOrientationAndTranslation(); + +After setting up the cartesian interface, you need to ``assign_loaned_command_interfaces`` and ``assign_loaned_state_interfaces`` in your controller. +This needs to be done in the on_activate() function of the controller. Examples can be found in the +`assign loaned comamand interface example +`_ + +.. code-block:: cpp + + cartesian_pose_interface.assign_loaned_command_interfaces(command_interfaces_); + cartesian_pose_interface.assign_loaned_state_interfaces(state_interfaces_); + +In the update function of the controller you can send pose commands to the robot. + +.. code-block:: cpp + + std::array pose; + pose = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.5, 0, 0.5, 1}; + cartesian_pose_interface.setCommanded(pose); + +Or you can send quaternion, translation values in Eigen format. + +.. code-block:: cpp + + Eigen::Quaterniond quaternion(1, 0, 0, 0); + Eigen::Vector3d translation(0.5, 0, 0.5); + cartesian_pose_interface.setCommand(quaternion, translation); + +- Cartesian Velocity Interface: + +This interface is used to send Cartesian velocity commands to the robot by using the loaned command interfaces. +FrankaSemanticComponentInterface class is handling the loaned command interfaces and state interfaces. + +.. code-block:: cpp + + auto is_elbow_active = false; + CartesianVelocityInterface cartesian_velocity_interface(is_elbow_active); + +To send the velocity command to the robot, you need to assign_loaned_command_interface in your custom controller. + +.. code-block:: cpp + + cartesian_velocity_interface.assign_loaned_command_interface(command_interfaces_); + +In the update function of the controller you can send cartesian velocity command to the robot. + +.. code-block:: cpp + + std::array cartesian_velocity; + cartesian_velocity = {0, 0, 0, 0, 0, 0.1}; + cartesian_velocity_interface.setCommand(cartesian_velocity); + +.. _franka_robot_state_broadcaster: + +franka_robot_state_broadcaster +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This package contains read-only franka_robot_state_broadcaster controller. +It publishes franka_robot_state topic to the topic named `/franka_robot_state_broadcaster/robot_state`. +This controller node is spawned by franka_launch.py in the franka_bringup. +Therefore, all the examples that include the franka_launch.py publishes the robot_state topic. + +.. _franka_moveit_config: + +franka_moveit_config +^^^^^^^^^^^^^^^^^^^^ + +This package contains the configuration for MoveIt2. There is a new move group called +``panda_manipulator`` that has its tip between the fingers of the gripper and has its Z-axis rotated by -45 degrees, so +that the X-axis is now facing forward, making it easier to use. The ``panda_arm`` move group is still available +for backward compatibility. New applications should use the new ``panda_manipulator`` move group instead. + +.. figure:: _static/move-groups.png + :align: center + :figclass: align-center + + Visualization of the old and the new move group + +franka_msgs +^^^^^^^^^^^ + +This package contains the definitions for the different gripper actions and robot state message. + + +joint_effort_trajectory_controller +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This package contains a modified joint_trajectory_controller that can use the effort interface of the +``franka_hardware::FrankaHardwareInterface``. It is based on this +`Pull request `_. + +.. note:: + This package will be soon deleted as the fix is available in + `ros2_controllers `_ master branch. + As soon as, it's backported to Humble, it will be deleted from franka_ros2 repository. + +Differences between franka_ros and franka_ros2 +---------------------------------------------- + +This section gives an overview of the fundamental changes between ``franka_ros`` and ``franka_ros2``. + +franka_gripper +^^^^^^^^^^^^^^ + +* All topics and actions were previously prefixed with ``franka_gripper``. This prefix was renamed to ``panda_gripper`` + to enable, in the future, a workflow where all prefixes are based on the ``arm_id`` + to effortlessly enable multi arm setups. + +* The ``stop`` action is now a service action as it is not preemptable. + +* All actions (apart from the ``gripper_action``) have the current gripper width as feedback. + +franka_gazebo +^^^^^^^^^^^^^ + +Currently, we do not offer a Gazebo integration with ``franka_ros2``. However, we provide one +with :doc:`franka_ros`. + +franka_visualization +^^^^^^^^^^^^^^^^^^^^ + +This package does not exist anymore. However, :ref:`franka_description` provides a launch file to visualize the robot +model without a connection to a robot. + +franka_control +^^^^^^^^^^^^^^ + +This package does not exist anymore. The connection to the robot is provided by the hardware plugin in +the :ref:`franka_hardware` package. The actions and services that it provided are currently +not offered in ``franka_ros2``. + + +Writing Controllers +^^^^^^^^^^^^^^^^^^^ + +Compared to ``franka_ros`` we currently offer a reduced set of controller interfaces: + +* Joint positions +* Joint velocities +* Measured torques +* Franka robot state +* Franka robot model + +.. important:: + Franka robot state is published through :ref:`franka_robot_state_broadcaster` + package to the topic named `/franka_robot_state_broadcaster/robot_state` + +.. important:: + Both Franka robot state and Franka robot model are advised to use through :ref:`franka_semantic_components` class. + They are stored in the state_interface as double pointers and casted back to their original objects inside the franka_semantic_component class. + + Example of using franka_model can be found in the franka_example_controllers package: + `model_example_controller `_. + + +You can base your own controller on one of the :ref:`franka_example_controllers`. To compute kinematic +and dynamic quantities of the robot you can use the joint states and the URDF of the robot in libraries like +`KDL `_ (of which there is also a ROS 2 package available). + +Non-realtime robot parameter setting +------------------------------------ + +Non-realtime robot parameter setting can be done via ROS 2 services. They are advertised after the robot hardware is initialized. + +Service names are given below:: + + * /service_server/set_cartesian_stiffness + * /service_server/set_force_torque_collision_behavior + * /service_server/set_full_collision_behavior + * /service_server/set_joint_stiffness + * /service_server/set_load + * /service_server/set_parameters + * /service_server/set_parameters_atomically + * /service_server/set_stiffness_frame + * /service_server/set_tcp_frame + +Service message descriptions are given below. + + * ``franka_msgs::srv::SetJointStiffness`` specifies joint stiffness for the internal controller + (damping is automatically derived from the stiffness). + * ``franka_msgs::srv::SetCartesianStiffness`` specifies Cartesian stiffness for the internal + controller (damping is automatically derived from the stiffness). + * ``franka_msgs::srv::SetTCPFrame`` specifies the transformation from _EE (end effector) to + _NE (nominal end effector) frame. The transformation from flange to end effector frame + is split into two transformations: _EE to _NE frame and _NE to + _link8 frame. The transformation from _NE to _link8 frame can only be + set through the administrator's interface. + * ``franka_msgs::srv::SetStiffnessFrame`` specifies the transformation from _K to _EE frame. + * ``franka_msgs::srv::SetForceTorqueCollisionBehavior`` sets thresholds for external Cartesian + wrenches to configure the collision reflex. + * ``franka_msgs::srv::SetFullCollisionBehavior`` sets thresholds for external forces on Cartesian + and joint level to configure the collision reflex. + * ``franka_msgs::srv::SetLoad`` sets an external load to compensate (e.g. of a grasped object). + +Launch franka_bringup/franka.launch.py file to initialize robot hardware:: + + ros2 launch franka_bringup franka.launch.py robot_ip:= + +Here is a minimal example: + +.. code-block:: shell + + ros2 service call /service_server/set_joint_stif + fness franka_msgs/srv/SetJointStiffness "{joint_stiffness: [1000.0, 1000.0, 10 + 00.0, 1000.0, 1000.0, 1000.0, 1000.0]}" + +.. important:: + + Non-realtime parameter setting can only be done when the robot hardware is in `idle` mode. + If a controller is active and claims command interface this will put the robot in the `move` mode. + In `move` mode non-realtime param setting is not possible. + +.. important:: + + The _EE frame denotes the part of the + configurable end effector frame which can be adjusted during run time through `franka_ros`. The + _K frame marks the center of the internal + Cartesian impedance. It also serves as a reference frame for external wrenches. *Neither the + _EE nor the _K are contained in the URDF as they can be changed at run time*. + By default, is set to "panda". + + .. figure:: _static/frames.svg + :align: center + :figclass: align-center + + Overview of the end-effector frames. + + + +Known Issues +------------ + +* When using the ``fake_hardware`` with MoveIt, it takes some time until the default position is applied. diff --git a/_sources/franka_ros2_changelog.rst.txt b/_sources/franka_ros2_changelog.rst.txt new file mode 100644 index 0000000..afe9fd1 --- /dev/null +++ b/_sources/franka_ros2_changelog.rst.txt @@ -0,0 +1,6 @@ +franka_ros2 changelog +===================== + +.. include:: ../franka_ros2/CHANGELOG.md + :parser: myst_parser.sphinx_ + :start-line: 2 diff --git a/_sources/franka_ros_changelog.rst.txt b/_sources/franka_ros_changelog.rst.txt new file mode 100644 index 0000000..7f23ecf --- /dev/null +++ b/_sources/franka_ros_changelog.rst.txt @@ -0,0 +1,6 @@ +franka_ros changelog +==================== + +.. include:: ../franka_ros/CHANGELOG.md + :parser: myst_parser.sphinx_ + :start-line: 2 diff --git a/_sources/getting_started.rst.txt b/_sources/getting_started.rst.txt new file mode 100644 index 0000000..73f998b --- /dev/null +++ b/_sources/getting_started.rst.txt @@ -0,0 +1,292 @@ +Getting started +=============== + +After setting up the required software for :doc:`Linux ` or +:doc:`Windows `, it is time to connect to the robot and test the whole setup +by using FCI to read the current robot state. + +Operating the robot +------------------- + +Before going further though, here are a few safety considerations. +Always check the following things before powering on the robot: + +1. Make sure that the Arm has been mounted on a stable base and cannot topple over, even + when performing fast motions or abrupt stops. + +.. caution:: + Only tabletop mounting is supported, i.e. the Arm must be mounted perpendicular to the + ground! Other mountings will **void your warranty** and **might cause damage + to the robot**! + +2. Ensure that the cable connecting Arm and Control is firmly attached on both sides. +3. Connect the external activation device to Arm's base and keep it next to you in order to be + able to stop the robot at any time. + +.. hint:: + Activating the external activation device will disconnect the Arm from Control. + The joint motor controllers will then hold their current position. + **The external activation device is not an emergency stop!** + +This list is non-exhaustive! The manual delivered with your robot contains a chapter dedicated +to safety. Please read it carefully and follow the instructions. The manual can also be found in +our `Franka World Hub `_. + +.. important:: + The workstation PC which commands your robot using the FCI must always be connected to the LAN + port of Control (shop floor network) and **not** to the LAN port of the Arm (robot network). + +Installing the FCI Feature +-------------------------- +In order to be able to operate the robot through the Franka Control Interface, the corresponding +Feature needs to be installed on your device. If it is already installed to the controller, it +will be listed under Desk --> Settings --> System --> Installed Features. +If FCI is not installed yet, you can synchronize it from your Franka World account to your +controller. Therefore, you must have an available FCI license in your company/university account and have +your controller registered in this account. The software (offline or online) synchronization +process is described in more detail in the +`Franka World user manual `_. + + +.. _setting-up-the-network: + +Setting up the network +---------------------- + +Good network performance is crucial when controlling the robot using FCI. +Therefore it is strongly recommended to use a direct connection between the +workstation PC and Panda's Control. This section describes how to configure your +network for this use case. + +.. figure:: _static/control.png + :align: center + :figclass: align-center + + Use Control's LAN port when controlling the robot through FCI. + Do not connect to the port in Arm's base. + +The Control and your workstation must be configured to appear on the same +network. Simplest way to achieve that is to use static IP addresses. Any two +addresses on the same network would work, but the following values will be used +for the purpose of this tutorial: + ++---------+----------------+------------+ +| | Workstation PC | Control | ++=========+================+============+ +| Address | 172.16.0.1 | 172.16.0.2 | ++---------+----------------+------------+ +| Netmask | 24 | 24 | ++---------+----------------+------------+ + +The Control's address (172.16.0.2) is called ```` in the following chapters. + +.. hint:: + With this network configuration, Desk can be accessed via ``https://``, although + you will see a certificate warning in your browser. + +The configuration process consists of two steps: + + * Configuring Control's network settings. + * Configuring your workstation's network settings. + +Control network configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For this step, the robot needs to be installed and tested. **Please read through +the documents shipped with your robot and follow the setup instructions before +continuing any further!** + +The Control's network can be configured in the administrator's interface. For +the duration of this step you can connect to the robot through the port in the +robot's base. For details, consult the `Connecting a user interface device` +section in the manual delivered with your robot. + +.. figure:: _static/accessing-admin.png + :align: center + :figclass: align-center + + Accessing the administrator's interface through Desk. + +To set up a static address, enter the following values in the `Network` section: + +.. figure:: _static/control-static-ip.png + :align: center + :figclass: align-center + + Setting a static IP for the Control's LAN port (Shop Floor network). + DHCP Client option is deselected. + +Press `Apply`. After the settings are successfully applied, connect your +workstation's LAN port to the robot's control unit. + +Linux workstation network configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section describes how to set up a static IP address on Ubuntu 16.04 +using the GUI. Follow the official Ubuntu guide_ if you prefer to use the +command line. + +.. _guide: https://help.ubuntu.com/lts/serverguide/network-configuration.html + +.. caution:: + The following steps will modify your network settings. If in doubt, + contact your network's administrator. + +First, go to Network Connection widget. Select the wired connection you +will be using and click edit. + +.. figure:: _static/edit-connections.png + :align: center + :figclass: align-center + + Edit the connection in the Ethernet section. + +Next, click on the IPv4 settings tab, set the method to Manual, and enter the +following values: + +.. figure:: _static/static-ip-ubuntu.png + :align: center + :figclass: align-center + + Setting a static IP for the Workstation PC. Method is set to Manual. + +.. hint:: + This step will disable DHCP, which means you will no longer obtain an address + when connecting to a DHCP server, like the one in Arm's base. When you no + longer use FCI, you can change the Method back to `Automatic (DHCP)`. + +Save the changes, and close the Network Connection window. Click on the +connection name from the drop down menu. It should now be possible to connect to +the robot from your workstation. To verify this, perform the +:ref:`network-bandwidth-delay-test`. From now on, you can also access Desk +through this address in your browser. + +Windows workstation network configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup a static IP address on the Windows workstation. Therefore, open **Control Panel** and go to +**Network and Internet** > **Network and Sharing Center** > **Change adapter settings**. +Right-click the network adapter and open **Properties**. Use the same example address and netmask +as in the Linux workstation network configuration. + +.. _preparing_robot_in_desk: + +Preparing the robot for FCI usage in Desk +----------------------------------------- + +In order to verify the connection, the robot's brakes need to be unlocked in Desk and the activation +device needs to be relased so that the robot is ready for execution indicated by blue LED mode. + +Enabling the FCI mode +^^^^^^^^^^^^^^^^^^^^^ + +Using a robot with system version >= 4.2.0 requires to enable the FCI mode. To do that open Desk, +expand the menu in the sidebar and press 'Activate FCI'. + +.. figure:: _static/activate_fci.png + :align: center + :figclass: align-center + + Enabling the FCI mode in the Desk sidebar menu + + +FCI mode in Panda +""""""""""""""""" + +After enabling the FCI mode, a pop-up as shown below is appearing. This pop-up indicates that the FCI mode +is currently active and that Desk interactions are not allowed while it is active. This pop-up needs to +remain open while working with FCI. Further information about Single Point of Control (SPoC) can be found +in the manual shipped with the robot which can also be found in our +`Franka World Hub `_. + +.. figure:: _static/pop_up_fci.png + :align: center + :figclass: align-center + + Pop-up when the FCI mode is enabled + +FCI mode in Franka Research 3 +""""""""""""""""""""""""""""" + +After enabling the FCI mode, the sidebar shows that FCI is on. + +.. figure:: _static/fci-on.png + :align: center + :figclass: align-center + + Sidebar when the FCI mode is enabled + +Verifying the connection +------------------------ + +The previous sections described how to specify the IP address of the Control's +LAN port. In the following sections that address is referred to as ````. + +In order to verify that everything is correctly set up, be sure :ref:`the robot is prepared for FCI +usage in Desk ` and run the ``echo_robot_state`` +example from ``libfranka``. If you decided to install ``franka_ros`` and ``libfranka`` from the ROS +repository, you can instead read the instructions for +:ref:`visualizing the robot in ros ` . + +Change to the build directory of ``libfranka`` and execute the example: + +*Linux*: + +.. code-block:: shell + + ./examples/echo_robot_state + +*Windows*: + +.. code-block:: shell + + cd /path/to/libfranka/build/examples/ + echo_robot_state.exe + +.. hint:: + Before executing libfranka programms, make sure that the executables are able to find their runtime libraries. + On Windows, the easiest way is to copy the needed libraries into the same directory as the executable. + +The program will print the current state of the robot to the console and terminate after a few +iterations. The fields are explained in the +`libfranka API documentation `_. + +Example output: + +.. code-block:: json + + { + "O_T_EE": [0.998578,0.0328747,-0.0417381,0,0.0335224,-0.999317,0.0149157,0,-0.04122,-0.016294, + -0.999017,0,0.305468,-0.00814133,0.483198,1], + "O_T_EE_d": [0.998582,0.0329548,-0.041575,0,0.0336027,-0.999313,0.0149824,0,-0.0410535, + -0.0163585,-0.999023,0,0.305444,-0.00810967,0.483251,1], + "F_T_EE": [0.7071,-0.7071,0,0,0.7071,0.7071,0,0,0,0,1,0,0,0,0.1034,1], + "EE_T_K": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], + "m_ee": 0.73, "F_x_Cee": [-0.01,0,0.03], "I_ee": [0.001,0,0,0,0.0025,0,0,0,0.0017], + "m_load": 0, "F_x_Cload": [0,0,0], "I_load": [0,0,0,0,0,0,0,0,0], + "m_total": 0.73, "F_x_Ctotal": [-0.01,0,0.03], "I_total": [0.001,0,0,0,0.0025,0,0,0,0.0017], + "elbow": [-0.0207622,-1], "elbow_d": [-0.0206678,-1], + "tau_J": [-0.00359774,-5.08582,0.105732,21.8135,0.63253,2.18121,-0.0481953], + "tau_J_d": [0,0,0,0,0,0,0], + "dtau_J": [-54.0161,-18.9808,-64.6899,-64.2609,14.1561,28.5654,-11.1858], + "q": [0.0167305,-0.762614,-0.0207622,-2.34352,-0.0305686,1.53975,0.753872], + "dq": [0.00785939,0.00189343,0.00932415,0.0135431,-0.00220327,-0.00492024,0.00213604], + "q_d": [0.0167347,-0.762775,-0.0206678,-2.34352,-0.0305677,1.53975,0.753862], + "dq_d": [0,0,0,0,0,0,0], + "joint_contact": [0,0,0,0,0,0,0], "cartesian_contact": [0,0,0,0,0,0], + "joint_collision": [0,0,0,0,0,0,0], "cartesian_collision": [0,0,0,0,0,0], + "tau_ext_hat_filtered": [0.00187271,-0.700316,0.386035,0.0914781,-0.117258,-0.00667777, + -0.0252562], + "O_F_ext_hat_K": [-2.06065,0.45889,-0.150951,-0.482791,-1.39347,0.109695], + "K_F_ext_hat_K": [-2.03638,-0.529916,0.228266,-0.275938,0.434583,0.0317351], + "theta": [0.01673,-0.763341,-0.0207471,-2.34041,-0.0304783,1.54006,0.753865], + "dtheta": [0,0,0,0,0,0,0], + "current_errors": [], "last_motion_errors": [], + "control_command_success_rate": 0, "robot_mode": "Idle", "time": 3781435 + } + +.. hint:: + + If an error occurs at this point, perform the + :ref:`ping test ` and ensure that the robot's fail-safe + safety locking system is opened. Further information are provided in the manual shipped with + the robot. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 0000000..cd1585f --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,63 @@ +Franka Control Interface Documentation +====================================== + +.. note:: + + The software and its documentation support two different robots, the + `Franka Research 3 (FR3) `_ and an older Franka Robotics Robot (FER or Panda). + +.. todolist:: + +The Franka Control Interface (FCI) can be accessed via several open source components which we +provide on `GitHub `_. We welcome contributions and suggestions +for improvements. + +These components are: + + * ``libfranka``, a C++ library that provides low-level control of Franka Robotics research robots. + Its source code is available at https://github.com/frankaemika/libfranka. API documentation is + available at https://frankaemika.github.io/libfranka. + * ``franka_ros``, our `ROS integration `_, including support for + ROS Control and MoveIt!. It also contains ``franka_description``, a collection of URDF models and + 3D meshes that can be useful outside of ROS. + The repository is available at https://github.com/frankaemika/franka_ros. + * ``franka_ros2`` is currently released only as beta software. + The repository is available at https://github.com/frankaemika/franka_ros2. + * ``Franka MATLAB®`` provides a Simulink and a Matlab API, together with helper functions and tools, + for rapid-prototyping in Real-Time on the Franka Robot" + Franka MATLAB® artifacts can be obtained at `Franka World `_. + +The source code of this documentation is also `available online +`_. + +.. important:: + Before you start using the FCI, please read through the documents shipped with the robot and + the :doc:`requirements` chapter. + +.. note:: + Using a robot with system version 4.2.0 or higher requires to enable the FCI mode. To do that + open Desk -> expand the menu in the sidebar -> press `'Activate FCI'`. Further information + about Single Point of Control (SPoC) can be found in the manual shipped with the robot. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + overview + requirements + compatibility + installation_linux + installation_windows + getting_started + libfranka + libfranka_changelog + franka_ros + franka_ros_changelog + franka_ros2 + franka_ros2_changelog + franka_matlab/index + franka_matlab/franka_matlab_changelog + control_parameters + fr3-certification-remarks + troubleshooting + faq diff --git a/_sources/installation_linux.rst.txt b/_sources/installation_linux.rst.txt new file mode 100644 index 0000000..1ddc859 --- /dev/null +++ b/_sources/installation_linux.rst.txt @@ -0,0 +1,330 @@ +Installation on Linux +===================== + +This chapter describes how to install ``libfranka`` and ``franka_ros``, either +as binary packages or by building from source, and how to install a real-time +Linux kernel. ``franka_ros`` is only required if you want to control your robot +using `ROS `_. + +.. note:: + + While ``libfranka`` and the ``franka_ros`` packages should work on different Linux distributions, + official support is currently only provided for: + + * Ubuntu 18.04 LTS `Bionic Beaver` and ROS `Melodic Morenia` (requires at least ``libfranka`` 0.6.0) + * Ubuntu 20.04 LTS `Focal Fossa` and ROS `Noetic Ninjemys` (requires at least ``libfranka`` 0.8.0) + + The following instructions are exemplary for Ubuntu 20.04 LTS system and ROS `Noetic Ninjemys`. + They only work in the supported environments. + +.. warning:: + We do not offer support for Ubuntu 16.04 LTS `Xenial Xerus` and ROS `Kinetic Kame` anymore, as they have reached their + end-of-life. + +Installing from the ROS repositories +------------------------------------ + +.. hint:: + + These packages might not always be up-to-date, as they are only synced at certain intervals. + Read the changelog at https://frankaemika.github.io to find out which ``libfranka`` version is required for + a particular robot software version. If this doesn't match the ``ros-noetic-libfranka`` version from the + repositories, you need to :ref:`build from source `. + +Binary packages for ``libfranka`` and ``franka_ros`` are available from the ROS repositories. +After `setting up ROS Noetic `__, execute:: + + sudo apt install ros-noetic-libfranka ros-noetic-franka-ros + +.. _installation-build-from-source: + +Building from source +-------------------- + +Before building from source, please uninstall existing installations of ``libfranka`` and +``franka_ros`` to avoid conflicts:: + + sudo apt remove "*libfranka*" + + + +.. _build-libfranka: + +Building libfranka +^^^^^^^^^^^^^^^^^^ + +To build ``libfranka``, install the following dependencies from Ubuntu's package manager:: + + sudo apt install build-essential cmake git libpoco-dev libeigen3-dev + +Then, download the source code by cloning ``libfranka`` from `GitHub `__. + +For Panda you need to clone: + +.. code-block:: shell + + git clone --recursive https://github.com/frankaemika/libfranka # only for panda + cd libfranka + +By default, this will check out the newest release of ``libfranka``. If you want to build a particular version of +``libfranka`` instead, check out the corresponding Git tag:: + + git checkout + git submodule update + + + +The above instructions for cloning libfranka only work for Panda. For Franka Research 3 you have to clone: + +.. code-block:: + + git clone --recursive https://github.com/frankaemika/libfranka --branch 0.10.0 # only for FR3 + cd libfranka + +In the source directory, create a build directory and run CMake: + +.. code-block:: shell + + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF .. + cmake --build . + +Optionally (but recommended), a ``libfranka`` Debian package can be built using the following command in the same directory: + +.. code-block:: shell + + cpack -G DEB + +This creates `libfranka--.deb`. This package can then be installed with: + +.. code-block:: shell + + sudo dpkg -i libfranka*.deb + +Building the ROS packages +^^^^^^^^^^^^^^^^^^^^^^^^^ + +After `setting up ROS Noetic `__, create a Catkin +workspace in a directory of your choice: + +.. code-block:: shell + + cd /path/to/desired/folder + mkdir -p catkin_ws/src + cd catkin_ws + source /opt/ros/noetic/setup.sh + catkin_init_workspace src + +Then clone the ``franka_ros`` repository from `GitHub `__:: + + git clone --recursive https://github.com/frankaemika/franka_ros src/franka_ros + +By default, this will check out the newest release of ``franka_ros``. If you want to build a particular version of +``franka_ros`` instead, check out the corresponding Git tag:: + + git checkout + +Install any missing dependencies and build the packages: + +.. code-block:: shell + + rosdep install --from-paths src --ignore-src --rosdistro noetic -y --skip-keys libfranka + catkin_make -DCMAKE_BUILD_TYPE=Release -DFranka_DIR:PATH=/path/to/libfranka/build + source devel/setup.sh + +.. warning:: + If you also installed ``ros-noetic-libfranka``, ``libfranka`` might be picked up from ``/opt/ros/noetic`` + instead of from your custom ``libfranka`` build! + +.. _preempt: + +Setting up the real-time kernel +------------------------------- + +In order to control your robot using ``libfranka``, the controller program on +the workstation PC must run with `real-time priority` under a ``PREEMPT_RT`` +kernel. This section describes the procedure of patching a kernel to support +``PREEMPT_RT`` and creating an installation package. + +.. note:: + + NVIDIA binary drivers are not supported on ``PREEMPT_RT`` kernels. + +First, install the necessary dependencies:: + + sudo apt-get install build-essential bc curl ca-certificates gnupg2 libssl-dev lsb-release libelf-dev bison flex dwarves zstd libncurses-dev + +Then, you have to decide which kernel version to use. To find the one you are +using currently, use ``uname -r``. Real-time patches are only available for +select kernel versions, see +https://www.kernel.org/pub/linux/kernel/projects/rt/. We recommend choosing the +version closest to the one you currently use. If you choose a +different version, simply substitute the numbers. Having decided on a version, +use ``curl`` to download the source files: + +.. note:: + For Ubuntu 16.04 tested with the kernel version 4.14.12: + + .. code:: + + curl -SLO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.sign + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.sign + +.. note:: + For Ubuntu 18.04 tested with the kernel version 5.4.19: + + .. code:: + + curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.sign + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.sign + +.. note:: + For Ubuntu 20.04 tested with the kernel version 5.9.1: + + .. code:: + + curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.9.1.tar.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.9.1.tar.sign + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.9/patch-5.9.1-rt20.patch.xz + curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.9/patch-5.9.1-rt20.patch.sign + +And decompress them with:: + + xz -d *.xz + +Verifying file integrity +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: + This step is optional but recommended! + +The ``.sign`` files can be used to verify that the downloaded files were not +corrupted or tampered with. The steps shown here are adapted from the +`Linux Kernel Archive `_ , see the +linked page for more details about the process. + +You can use ``gpg2`` to verify the ``.tar`` archives:: + + gpg2 --verify linux-*.tar.sign + gpg2 --verify patch-*.patch.sign + +If your output is similar to the following:: + + $ gpg2 --verify linux-*.tar.sign + gpg: assuming signed data in 'linux-4.14.12.tar' + gpg: Signature made Fr 05 Jan 2018 06:49:11 PST using RSA key ID 6092693E + gpg: Can't check signature: No public key + +You have to first download the public key of the person who signed the above +file. As you can see from the above output, it has the ID ``6092693E``. You can +obtain it from the key server:: + + gpg2 --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 6092693E + +Similarly for the patch:: + + gpg2 --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2872E4CC + +Note that keys for other kernel version might have different IDs, you will have to +adapt accordingly. + +Having downloaded the keys, you can now verify the sources. Here is an example of +a correct output:: + + $ gpg2 --verify linux-*.tar.sign + gpg: assuming signed data in 'linux-4.14.12.tar' + gpg: Signature made Fr 05 Jan 2018 06:49:11 PST using RSA key ID 6092693E + gpg: Good signature from "Greg Kroah-Hartman " [unknown] + gpg: aka "Greg Kroah-Hartman " [unknown] + gpg: aka "Greg Kroah-Hartman (Linux kernel stable release signing key) " [unknown] + gpg: WARNING: This key is not certified with a trusted signature! + gpg: There is no indication that the signature belongs to the owner. + Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E + +See `Linux Kernel Archive `_ +for more information about the warning. + + + +Compiling the kernel +^^^^^^^^^^^^^^^^^^^^ + +Once you are sure the files were downloaded properly, you can extract the source +code and apply the patch:: + + tar xf linux-*.tar + cd linux-*/ + patch -p1 < ../patch-*.patch + +Next copy your currently booted kernel configuration as the default config for the new real time kernel:: + + cp -v /boot/config-$(uname -r) .config + +Now you can use this config as the default to configure the build:: + + make olddefconfig + make menuconfig + +The second command brings up a terminal interface in which you can configure the preemption model. Navigate with the +arrow keys to *General Setup* > *Preemption Model* and select *Fully Preemptible Kernel (Real-Time)*. + +After that navigate to *Cryptographic API* > *Certificates for signature checking* +(at the very bottom of the list) > *Provide system-wide ring of trusted keys* > +*Additional X.509 keys for default system keyring* + +Remove the "debian/canonical-certs.pem" from the prompt and press Ok. Save this +configuration to ``.config`` and exit the TUI. + +.. note:: + If you prefer GUIs over TUIs use ``make xconfig`` instead of ``make menuconfig`` + +Afterwards, you are ready to compile the kernel. As this is a lengthy process, set the +multithreading option ``-j`` to the number of your CPU cores:: + + make -j$(nproc) deb-pkg + +Finally, you are ready to install the newly created package. The exact names +depend on your environment, but you are looking for ``headers`` and ``images`` +packages without the ``dbg`` suffix. To install:: + + sudo dpkg -i ../linux-headers-*.deb ../linux-image-*.deb + +Verifying the new kernel +^^^^^^^^^^^^^^^^^^^^^^^^ + +Restart your system. The Grub boot menu should now allow you to choose your +newly installed kernel. To see which one is currently being used, see the output +of the ``uname -a`` command. It should contain the string ``PREEMPT RT`` and the +version number you chose. Additionally, ``/sys/kernel/realtime`` should exist and +contain the the number ``1``. + +.. note:: + If you encounter errors that you fail to boot the new kernel see :ref:`troubleshooting_realtime_kernel` + +.. _installation-real-time: + +Allow a user to set real-time permissions for its processes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After the ``PREEMPT_RT`` kernel is installed and running, add a group named +`realtime` and add the user controlling your robot to this group:: + + sudo addgroup realtime + sudo usermod -a -G realtime $(whoami) + +Afterwards, add the following limits to the `realtime` group in +``/etc/security/limits.conf``:: + + @realtime soft rtprio 99 + @realtime soft priority 99 + @realtime soft memlock 102400 + @realtime hard rtprio 99 + @realtime hard priority 99 + @realtime hard memlock 102400 + +The limits will be applied after you log out and in again. diff --git a/_sources/installation_windows.rst.txt b/_sources/installation_windows.rst.txt new file mode 100644 index 0000000..2fd3138 --- /dev/null +++ b/_sources/installation_windows.rst.txt @@ -0,0 +1,81 @@ +Installation on Windows +======================= + +.. caution:: + Windows support is very experimental and only intended for experienced users. Since Windows is not real-time capable, + communication errors are very likely. Further, these instructions are may not up-to-date and adjustments might be necessary. + +Starting from ``libfranka`` >= 0.6.0, **experimental** Windows support is provided. +This chapter describes how to install ``libfranka`` on Windows. +``franka_ros`` is not supported on Windows. + +.. note:: + + Windows support has only been tested with Windows 10 and Visual Studio 2017. + + +Building from source +-------------------- + +To build ``libfranka``, install the following dependencies: + + * `Eigen3 `__ + * `Poco `__ + +Both can be easily installed with `vcpkg `__ via cmd prompt: + +.. code-block:: shell + + cd /path/to/vcpkg + vcpkg install eigen3 + vcpkg install poco + +Download the source code by cloning ``libfranka`` from `GitHub `__: + +.. code-block:: shell + + git clone --recursive https://github.com/frankaemika/libfranka + +By default, this will check out the newest release of ``libfranka``. If you want to build +a particular version of ``libfranka`` instead, check out the corresponding Git tag:: + + git checkout + git submodule update + +.. important:: + + Only ``libfranka`` >= 0.6.0 has Windows support! + +To build ``libfranka`` with Visual Studio open it as a CMake Project. +Choose **File** > **Open** > **CMake** > **C:\Path\To\libfranka\CMakeLists.txt**. + +Generate the ``CMakeSettings.json``, which contains the CMake project settings. +Select **CMake** > **Cache** > **Generate** > **CMakeSettings.json**. The file will be placed in +your main ``libfranka`` directory. + +The next step is to solve the build dependencies. +Make sure the compiler is able to find +the required dependencies. This can be done either by copying all needed .dll libries into +the chosen build root determined by ``CMakeSettings.json`` or using cmakeCommandArgs. +Open **CMake** > **Change CMake settings** > **libfranka** and add + +.. code-block:: json + + { + "cmakeCommandArgs": "-DPoco_DIR=C:\Path\To\Poco-Install\lib\cmake\Poco -DEigen3_DIR=C:\Path\To\Eigen-Install\lib\cmake\eigen3" + } + +Choose **CMake** > **Build** to build ``libfranka`` into the build directory, +determined in ``CMakeSettings.json`` + +.. hint:: + + Alternatively you can build libfranka using the `Developer Command Prompt for VS`: + + .. code-block:: shell + + cd /path/to/libfranka + mkdir build + cd build + cmake -DPoco_DIR=/Path/To/Poco/CMake/Config -DEigen3_DIR=/Path/To/Eigen/CMake/Config -G Ninja .. + ninja diff --git a/_sources/libfranka.rst.txt b/_sources/libfranka.rst.txt new file mode 100644 index 0000000..8800f56 --- /dev/null +++ b/_sources/libfranka.rst.txt @@ -0,0 +1,622 @@ +libfranka +========= + +Before continuing with this chapter, please install or compile libfranka for :doc:`Linux ` +or :doc:`Windows `. +API documentation for the latest version of ``libfranka`` is available at +https://frankaemika.github.io/libfranka. + +.. figure:: _static/libfranka-architecture.png + :align: center + :figclass: align-center + + Schematic overview + +``libfranka`` is the C++ implementation of the client side of the FCI. It handles the network +communication with Control and provides interfaces to easily: + + * execute **non-realtime commands** to control the Hand and configure Arm parameters. + * execute **realtime commands** to run your own 1 kHz control loops. + * read the **robot state** to get sensor data at 1 kHz. + * access the **model library** to compute your desired kinematic and dynamic parameters. + +During operation you might also encounter several **errors** that we detail at the end of +this section. + +Non-realtime commands +--------------------- + +Non-realtime commands are blocking, TCP/IP-based and always executed `outside` of any realtime +control loop. They encompass all of the Hand commands and some configuration-related commands +for the Arm. + +.. figure:: _static/fci-architecture-non-realtime.png + :align: center + :figclass: align-center + + Non-realtime commands for both Arm and Hand. + +The most relevant ones for the Hand are + + * ``homing`` which calibrates the maximum grasping width of the Hand. + * ``move``, ``grasp`` and ``stop``, to move or grasp with the Hand. + * ``readOnce``, which reads the Hand state. + +Concerning the Arm, some useful non-realtime commands are: + + * ``setCollisionBehavior`` which sets the contact and collision detection thresholds. + * ``setCartesianImpedance`` and ``setJointImpedance`` which set the impedance parameters + for the internal Cartesian impedance and internal joint impedance controllers. + * ``setEE`` sets the transformation *NE_T_EE* from nominal end effector to end effector + frame. The transformation from flange to end effector frame *F_T_EE* is split into two + transformations: *F_T_NE* and *NE_T_EE*. The transformation from flange to nominal end + effector frame *F_T_NE* can only be set through the administrator's interface. + * ``setK`` sets the transformation *EE_T_K* from end effector frame to stiffness frame. + * ``setLoad`` sets the dynamic parameters of a payload. + * ``automaticErrorRecovery`` that clears any command or control exception that previously + happened in the robot. + +For a complete and fully specified list check the API documentation for the +`Arm `__ +or the `Hand `__. + +All operations (non-realtime or realtime) on the Arm or the Hand are performed through the +``franka::Robot`` and ``franka::Gripper`` objects respectively. A connection to the Arm/Hand +will be established when the object is created: + +.. code-block:: c++ + + #include + #include + + ... + + franka::Gripper gripper(""); + franka::Robot robot(""); + +The address can be passed either as a hostname or an IP address. In case of any error, either due +to networking or conflicting library version, an exception of type ``franka::Exception`` will +be thrown. When using several robots at the same time, simply create several objects with +appropriate IP addresses. + +To run a specific command, simply call the corresponding method, e.g. + +.. code-block:: c++ + + gripper.homing(); + robot.automaticErrorRecovery(); + + +Realtime commands +----------------- + +Realtime commands are UDP based and require a 1 kHz connection to Control. +There are two types of realtime interfaces: + + * **Motion generators**, which define a robot motion in joint or Cartesian space. + * **Controllers**, which define the torques to be sent to the robot joints. + +There are 4 different types of external motion generators and 3 different types of controllers +(one external and 2 internal) as depicted in the following figure: + +.. figure:: _static/rt-interfaces.png + :align: center + :figclass: align-center + + Realtime interfaces: motion generators and controllers. + +You can either use a single interface or combine two different types. Specifically, you can +command: + + * *only a motion generator* and therefore use one of the two internal controllers to follow + the commanded motion. + * *only an external controller* and ignore any motion generator signals, i.e. torque control only. + * *a motion generator and an external controller* to use the inverse kinematics of Control in + your external controller. + +All realtime loops (motion generator or controller) are defined by a callback function that +receives the robot state and the duration of the last cycle (1 ms unless packet losses occur) +and returns the specific type of the interface. The ``control`` method of the ``franka::Robot`` +class will then run the control loop by executing the callback function at a 1 kHz frequency, +as shown in this example + +.. code-block:: c++ + + std::function + my_external_controller_callback; + // Define my_external_controller_callback + ... + + std::function + my_external_motion_generator_callback; + // Define my_external_motion_generator_callback + ... + + try { + franka::Robot robot(""); + // only a motion generator + robot.control(my_external_motion_generator_callback); + // only an external controller + robot.control(my_external_controller_callback); + // a motion generator and an external controller + robot.control(my_external_motion_generator_callback, my_external_controller_callback); + } catch (franka::Exception const& e) { + std::cout << e.what() << std::endl; + return -1; + } + return 0; + } + +All control loops are finished once the ``motion_finished`` flag of a realtime command is set +to ``true``. An excerpt of the ``generate_joint_velocity_motion`` example included +in the `libfranka examples `__ is shown here + +.. code-block:: c++ + + robot.control( + [=, &time](const franka::RobotState&, franka::Duration period) -> franka::JointVelocities { + time += period.toSec(); + + double cycle = std::floor(std::pow(-1.0, (time - std::fmod(time, time_max)) / time_max)); + double omega = cycle * omega_max / 2.0 * (1.0 - std::cos(2.0 * M_PI / time_max * time)); + + franka::JointVelocities velocities = {{0.0, 0.0, 0.0, omega, omega, omega, omega}}; + + if (time >= 2 * time_max) { + std::cout << std::endl << "Finished motion, shutting down example" << std::endl; + return franka::MotionFinished(velocities); + } + return velocities; + }); + +In this case, the callback function is defined directly in the call of the +``robot.control( ... )`` function. It uses the joint velocity motion generator interface, +as it returns a ``franka::JointVelocities`` object. It commands joint velocities to the last four +joints and move them by approx. +/-12 degrees. After ``2 * time_max`` seconds it will return a +``motion_finished`` flag by setting it to true with the ``franka::MotionFinished`` method and +the control loop will stop. + +Note that if you use only a motion generator, the default controller is the internal joint +impedance controller. You can however use the internal Cartesian impedance controller by +setting the optional argument of the control function, e.g. + +.. code-block:: c++ + + // Set joint impedance (optional) + robot.setJointImpedance({{3000, 3000, 3000, 3000, 3000, 3000, 3000}}); + // Runs my_external_motion_generator_callback with the default joint impedance controller + robot.control(my_external_motion_generator_callback); + // Identical to the previous line (default franka::ControllerMode::kJointImpedance) + robot.control(my_external_motion_generator_callback, franka::ControllerMode::kJointImpedance); + + // Set Cartesian impedance (optional) + robot.setCartesianImpedance({{2000, 2000, 2000, 100, 100, 100}}); + // Runs my_external_motion_generator_callback with the Cartesian impedance controller + robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance); + +For writing a controller, the ``franka::Robot::control`` function is used as well. The following +example shows a simple controller commanding zero torque for each joint. Gravity is +compensated by the robot. + +.. code-block:: c++ + + robot.control([&](const franka::RobotState&, franka::Duration) -> franka::Torques { + return {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}}; + }); + +You can find examples for all interfaces and combinations of control loops in the +`libfranka examples `__. Prior to running +the examples, verify that the robot has enough free space to move without colliding. Then, for +instance for the ``generate_joint_velocity_motion`` example execute the following command from +the ``libfranka`` build directory: + +.. code-block:: shell + + ./examples/generate_joint_velocity_motion + + +.. warning:: + + For writing your own motion generators or controllers it is crucial to deliver a smooth + signal to the robot. Nonsmooth signals can easily generate discontinuity errors or even + make the robot unstable. Check the :ref:`interface specifications + ` before starting. + +.. _signal-processing: + +Signal processing +******************* +To facilitate the control of the robot under non-ideal network connections, libfranka includes +signal processing functions that will modify the user-commanded values to make them conform +with the :ref:`limits of the interface`. +There are two *optional* functions included in all realtime control loops: + + * A first-order **low-pass filter** to smooth the user-commanded signal. + * A **rate limiter**, that saturates the time derivatives of the user-commanded values. + +* As of version ``0.5.0``, libfranka includes a **low-pass filter** for all realtime + interfaces **running by default** with a 100 Hz cutoff frequency. + The filter smooths commanded signals + to provide more stable robot motions but does not prevent the violation of the + :ref:`limits of the interface`. + +* As of version ``0.4.0``, **rate limiters** for all realtime interfaces are + **running by default**. `Rate limiters`, also called `safe controllers`, will limit the + rate of change of the signals sent by the user to prevent the violation of the + :ref:`limits of the interface`. For motion generators, it + will limit the acceleration and jerk, while, for an external controller, it will limit the + torque rate. Their main purpose is to increase the robustness of your control loop. + In case of packet losses, even when the signals that you send conform with the + interface limits, Control might detect a violation of velocity, acceleration or jerk limits. + Rate limiting will adapt your commands to make sure that this does not happen. + Check the :ref:`noncompliant errors section` for more details. + + .. caution:: + + Rate limiting will ensure no limits are violated except for the joint limits after + inverse kinematics, whose violation produces the family of errors starting with + ``cartesian_motion_generator_joint_*``. Check the + :ref:`noncompliant errors section` for more details. + + .. hint:: + + The limits used in the rate limiter are defined in ``franka/rate_limiting.h`` + and are set to the interface limits. If this produces a jerky or unstable behavior + you can set the limits to lower values, activate the low-pass filter or reduce its cutoff + frequency. + +To control the signal processing functions, all ``robot.control()`` function calls +have two additional optional parameters. The first one is a flag to activate or +deactivate the rate limiter while the second one +specifies the cutoff frequency of the first-order low-pass filter. If the cutoff frequency +``>=1000.0`` the filter will be deactivated. For instance + +.. code-block:: c++ + + // Set Cartesian impedance (optional) + robot.setCartesianImpedance({{2000, 2000, 2000, 100, 100, 100}}); + // Runs my_external_motion_generator_callback with the Cartesian impedance controller, + // rate limiters on and low-pass filter with 100 Hz cutoff + robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance); + // Identical to the previous line (default true, 100.0 Hz cutoff) + robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance, true, 100.0); + // Runs my_external_motion_generator_callback with the Cartesian impedance controller, + // rate limiters off and low-pass filter off + robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance, false, 1000.0); + +Or similarly for an external controller + +.. code-block:: c++ + + // With rate limiting and filter + robot.control(my_external_controller); + // Identical to the previous line (default true, 100.0 Hz cutoff) + robot.control(my_external_controller, true, 100.0); + // Without rate limiting but with low-pass filter (100.0 Hz) + robot.control(my_external_controller, false); + // Without rate limiting and without low-pass filter + robot.control(my_external_controller, false, 1000.0); + +.. danger:: + + The low-pass filter and the rate limiter are robustness features against packet losses + to be used **after** you have already designed a smooth motion generator or controller. + For the first tests of a new control loop we strongly recommend to deactivate these + features. + Filtering and limiting the rate of a nonsmooth signal can yield instabilities or + unexpected behavior. Too many packet losses can also generate unstable behavior. + Check your communication quality by monitoring the ``control_command_success_rate`` + signal of the robot state. + +.. _control-side: + +Under the hood +******************** +Until now we have covered details of the interface running on the client side, i.e your own +workstation PC. The behavior of the full control loop including the Control side of the +realtime interface is shown in the following figure + +.. figure:: _static/rt-loop.png + :align: center + :figclass: align-center + + Realtime loop: from control commands to the robot desired joint torques. + +**Motion generators**: all motion generator commands sent by the user have the subscript `c` +which stands for 'commanded'. When a motion generator is sent, the `Robot Kinematics completion` +block will compute the forward/inverse kinematics of the user-commanded signal yielding the +'desired' signals, subscript `d`. If an internal controller is used, it will generate the +necessary torques :math:`\tau_{d}` to track the corresponding computed `d` signals (the internal +joint impedance controller will follow the joint signals :math:`q_{d}, \dot{q}_{d}` and the +internal Cartesian impedance controller the Cartesian ones +:math:`{}^OT_{EE,d}, {}^O\dot{P}_{EE,d}`) and send them to the robot joints. +All variables on the Control side of the figure, i.e. the last received `c` values +(after the low pass filter and the extrapolation due to packet losses, +read below for an explanation), the computed `d` values +and their time derivatives are sent back to the user in the robot state. This way you can +take advantage of the inverse kinematics in your own external controller and, at the same time, +it will offer you `full transparency`: you will always know the exact values +and derivatives that the robot received and tracked in the last sample. + +.. hint:: + + When you are using a *joint* motion generator, the `Robot kinematics completion` block will + not modify the commanded *joint* values and therefore :math:`q_d, \dot{q}_d, \ddot{q}_d` and + :math:`q_c, \dot{q}_c, \ddot{q}_c` are equivalent. Note that you will only find the + `d` signals in the robot state. If you use a *Cartesian* motion generator, the `Robot + kinematics completion` block might modify the user-commanded values to avoid singularities + and therefore the desired signals :math:`{}^OT_{EE,d}, {}^O\dot{P}_{EE,d}` and the commanded + signals :math:`{}^OT_{EE,c}, {}^O\dot{P}_{EE,c}, {}^O\ddot{P}_{EE,c}` might differ. + You will find both the `d` and the `c` signals in the robot state. + +**External controller**: if an external controller is sent, the desired joint torques commanded +by the user :math:`\tau_{d}` are directly fed to the robot joints with the additional compensation +of gravity and motor friction, resulting in the following equation: + + :math:`\tau_{c} = \tau_{d} + \tau_{f} + \tau_{g}` + +Where: + +* :math:`\tau_{d}` is the desired torque given as input by the libfranka user, +* :math:`\tau_{c}` is the torque effectively commanded to the joint, +* :math:`\tau_{f}` is the torque to compensate the motor friction, +* :math:`\tau_{g}` is the torque required for compensating the gravity of the whole kinematic chain. + +Note that, on the Control side, there are two things that could modify your signals: + +* `Packet losses`, which may occur if you: + + * don't have a very good connection due to the performance of your PC + network card. + * your control loop is taking too long to compute (you have, depending on you network card and + PC configuration, approx. < 300 :math:`\mu s` for your own control loop). + + In this case, Control assumes a constant acceleration model or a constant torque to extrapolate + your signals. If ``>=20`` packets are lost in a row the control loop is stopped with the + ``communication_constraints_violation`` exception. + +.. hint:: + + If you are not sure if your signals are being filtered or extrapolated, you can always check the + last commanded values that you sent and compare them with the values you receive on the robot + state in the next sample. You will also find these values after an exception occurs in the + ``franka::ControlException::log`` member of the exception. + + +Robot state +----------------------- +The robot state delivers the robot sensor readings and estimated values at a 1 kHz rate. +It provides: + + * *Joint level signals*: motor and estimated joint angles and their derivatives, + joint torque and derivatives, estimated external torque, joint collision/contacts. + * *Cartesian level signals*: Cartesian pose, configured endeffector and load parameters, + external wrench acting on the endeffector, Cartesian collision + * *Interface signals*: the last commanded and desired values and their derivatives, + as explained in the previous subsection. + +For a complete list check the API of the ``franka::RobotState`` +`here `__. +As shown in the the previous subsection, the robot state is always an input of all callback +functions for control loops. However, if you wish to only read the robot state without controlling +it, the functions ``read`` or ``readOnce`` can be used to gather it, e.g. for +logging or visualization purposes. + +With a valid connection, *a single sample of the robot state* can be read using the ``readOnce`` +function: + +.. code-block:: c++ + + franka::RobotState state = robot.readOnce(); + +The next example shows how to continuously read the robot state using the ``read`` function and a +callback. Returning ``false`` in the callback stops the loop. In the following, an excerpt of the +``echo_robot_state`` example is shown: + +.. code-block:: c++ + + size_t count = 0; + robot.read([&count](const franka::RobotState& robot_state) { + // Printing to std::cout adds a delay. This is acceptable for a read loop such as this, + // but should not be done in a control loop. + std::cout << robot_state << std::endl; + return count++ < 100; + }); + + +Model library +-------------------- +The robot model library provides + + - The forward kinematics of all robot joints. + - The body and zero jacobian matrices of all robot joints. + - Dynamic parameters: inertia matrix, Coriolis and centrifugal vector and gravity vector. + +Note that after you load the model library, you can compute kinematic and dynamic parameters for +an arbitrary robot state, not just the current one. You can also use the model library in a non +realtime fashion, e.g. in an optimization loop. The libfranka examples include exemplary code +`printing joint poses +`_ +or `computing jacobians and dynamic parameters +`_. + +.. _control-errors: + +Errors +------- + +Using the FCI you will encounter several errors that happen either due to noncompliant +commands sent by the user, due to communication problems or due to the robot behavior. +The most relevant ones are detailed in the following subsections. +For a complete list please check the `API documentation +`_. + +.. hint:: + + Note that, after an error occurs, you can automatically clear it and continue running your + program with the ``franka::Robot::automaticErrorRecovery()`` command without user intervention. + Check the exception string before continuing to make sure that the error is not a critical + one. + + Some errors can also be cleared manually by toggling the external activation device or by + using the error recovery button in Desk. + +.. _noncompliant-errors: + +Errors due to noncompliant commanded values +******************************************** +If the :ref:`commanded values` sent by the user +do not comply with the :ref:`interface requirements`, +one of the following errors will occur: + +* Errors due to **wrong initial values of a motion generator**: + + - ``joint_motion_generator_start_pose_invalid`` + - ``cartesian_position_motion_generator_start_pose_invalid`` + - ``cartesian_motion_generator_start_elbow_invalid`` + - ``cartesian_motion_generator_elbow_sign_inconsistent`` + + These errors indicate a discrepancy between the current robot values and the initial values sent + by the user. To fix these errors, make sure that your control loop starts with the last commanded + value observed in the robot state. For instance, for the joint position interface + + .. code-block:: c++ + + double time{0.0}; + robot.control( + [=, &time](const franka::RobotState& robot_state, franka::Duration period) -> franka::JointPositions { + time += period.toSec(); + if (time == 0) { + // Send the last commanded q_c as the initial value + return franka::JointPositions(robot_state.q_c); + } else { + // The rest of your control loop + ... + } + }); + + +* Errors due to a **position limit** violation using a joint position/velocity motion generator, + which will produce a ``joint_motion_generator_position_limits_violation``. Solving this error + should be simple: make sure that the values that you send are in the + :ref:`limits`. Cartesian + interfaces also have limits on the joint signals that result after the inverse kinematics: the + ``cartesian_motion_generator_joint_position_limits_violation`` will be triggered if the inverse + kinematics solver of Control yields a joint configuration out of the limits. + +* Errors due to **velocity** limits violation and **discontinuity errors**, which refer to + **acceleration** and/or **jerk** limits violation. If you use a joint motion generator the + possible errors are + + - ``joint_motion_generator_velocity_limits_violation`` + - ``joint_motion_generator_velocity_discontinuity`` (acceleration limit violation) + - ``joint_motion_generator_acceleration_discontinuity`` (jerk limit violation) + + If you use a Cartesian one, the possible errors are + + - Cartesian limits: + + - ``cartesian_motion_generator_velocity_limits_violation`` + - ``cartesian_motion_generator_velocity_discontinuity`` (acceleration limit violation) + - ``cartesian_motion_generator_acceleration_discontinuity`` (jerk limit violation) + + - Joint limits after the inverse kinematics + + - ``cartesian_motion_generator_joint_velocity_limits_violation`` + - ``cartesian_motion_generator_joint_velocity_discontinuity`` + (acceleration limit violation) + - ``cartesian_motion_generator_joint_acceleration_discontinuity`` (jerk limit violation) + + To mitigate velocity violations or discontinuity errors, make sure that the signals that + you command do not violate the :ref:`limits`. For every + motion generator, Control differentiates the signals sent by the user with backwards Euler. + For instance, if, using a joint position motion generator, at time :math:`k` the user sends + the command :math:`q_{c,k}`, the resulting velocity, acceleration and jerk will be + + - Velocity :math:`\dot{q}_{c,k} = \frac{q_{c,k} - q_{c,k-1}}{\Delta t}` + - Acceleration :math:`\ddot{q}_{c,k} = \frac{\dot{q}_{c,k} - \dot{q}_{c,k-1}}{\Delta t}` + - Jerk :math:`\dddot{q}_{c,k} = \frac{\ddot{q}_{c,k} - \ddot{q}_{c,k-1}}{\Delta t}` , + + where :math:`\Delta t = 0.001`. Note that :math:`q_{c,k-1}, \dot{q}_{c,k-1}` and + :math:`\ddot{q}_{c,k-1}` are always sent back + to the user in the robot state as :math:`q_{d}, \dot{q}_{d}` and + :math:`\ddot{q}_{d}` so you will be able to + compute the resulting derivatives in advance, even in case of packet losses. Check the + :ref:`section about the details of the Control side of the interface` + for more details. + + Finally, for the torque interface a **torque rate** limit violation triggers the error + + - ``controller_torque_discontinuity`` + + Control also computes the torque rate with backwards Euler, i.e. + :math:`\dot{\tau}_{d,k} = \frac{\tau_{d,k} - \tau_{d,k-1}}{\Delta t}`. The previous desired + torque commanded by the user is also sent back in the robot state as :math:`\tau_d` + so you will also be able to compute the resulting torque rate in advance, + even in case of packet losses. + +.. hint:: + + The rate limiters included in ``libfranka`` since version ``0.4.0`` modify the signals + sent by the user to make them conform with all these limits except for the joint limits + after the inverse kinematics. You can check the ``include/franka/rate_limiting.h`` and + ``src/rate_limiting.cpp`` for exemplary code on how to compute resulting velocity, + acceleration and jerk for all interfaces. We emphasize again that using rate limiting on a + discontinuous signal can easily yield to unstable behavior, so please make sure that + your signal is smooth enough before enabling this *robustness* feature. + + +Errors due to communication problems +************************************ +If during a realtime loop Control does not receive any packets during 20 cycles, i.e. 20 ms, you +will receive a ``communication_constraints_violation`` error. +Note that if your connection has intermittent packet drops, it might not stop, but it could +trigger `discontinuity` errors even when your source signals conform with the interface +specification. +In that case, check our :ref:`troubleshooting section ` +and consider enabling the :ref:`signal processing functions ` +to increase the robustness of your control loop. + + +Behavioral errors +****************** +.. warning:: + + These monitoring features are by no means conform with any safety norm and do not + guarantee any safety to the user. They only aim for helping researchers during the + development and testing of their control algorithms. + +* **Reflex errors**. If the estimated external torques :math:`\hat{\tau}_{ext}` or forces + :math:`{}^O\hat{F}_{ext}` surpass the configured thresholds, a ``cartesian_reflex`` or + ``joint_reflex`` error will be triggered respectively. You can configure the thresholds + with the ``franka::Robot::setCollisionBehavior`` non realtime command. + + .. hint:: + + If you wish the robot to have contacts with the environment you must set the + collision thresholds to higher values. Otherwise, once you grasp an object or push + against a surface, a reflex will be triggered. Also, very fast or abrupt motions + without contacts could + trigger a reflex if thresholds are low; the external torques and forces are + only *estimated* values that could be innacurate depending on the robot + configuration, especially during high acceleration phases. You can monitor + their values observing :math:`\hat{\tau}_{ext}` and :math:`{}^O\hat{F}_{ext}` + in the robot state. + +* **Self-collision avoidance**. If the robot reaches a configuration which is close to a + self-collision, it will trigger a ``self_collision_avoidance_violation`` error. + + .. warning:: + This error does not guarantee that the robot will prevent a self collision at any + configuration and speed. If, using the torque interface, you drive the robot at + full speed against itself the robot might self-collide. + + +* If the **torque sensor limit** is reached, a ``tau_j_range_violation`` + will be triggered. This does not guarantee that the sensor will not be damaged after any + high-torque interactions or motions but aims for preventing some of it. + +* If the **maximum allowed power** is reached, the ``power_limit_violation`` will be triggered. + It will prevent the robot from moving and continuing the control loop. + +* If you reach the joint or the Cartesian limits you will get + a ``joint_velocity_violation`` or a ``cartesian_velocity_violation`` error respectively. diff --git a/_sources/libfranka_changelog.rst.txt b/_sources/libfranka_changelog.rst.txt new file mode 100644 index 0000000..3554ae5 --- /dev/null +++ b/_sources/libfranka_changelog.rst.txt @@ -0,0 +1,6 @@ +libfranka changelog +=================== + +.. include:: ../libfranka/CHANGELOG.md + :parser: myst_parser.sphinx_ + :start-line: 2 diff --git a/_sources/overview.rst.txt b/_sources/overview.rst.txt new file mode 100644 index 0000000..3b4c8e5 --- /dev/null +++ b/_sources/overview.rst.txt @@ -0,0 +1,48 @@ +Overview +======== + +.. figure:: _static/fci-architecture.png + :align: center + :figclass: align-center + + + Schematic overview. + +The Franka Control Interface (FCI) allows a fast and direct low-level bidirectional connection +to the Arm and Hand. It provides the current status of the robot and enables its direct control +with an external workstation PC connected via Ethernet. +By using ``libfranka``, our open source C++ interface, you can send real-time control values +at 1 kHz with 5 different interfaces: + + * Gravity & friction compensated joint level torque commands. + * Joint position or velocity commands. + * Cartesian pose or velocity commands. + +At the same time, you get access to 1 kHz measurements of: + + * Measured joint data, such as the position, velocity and link side torque sensor signals. + * Estimation of externally applied torques and forces. + * Various collision and contact information. + +You also get access to the robot model library which provides: + + * Forward kinematics of all robot joints. + * Jacobian matrix of all robot joints. + * Dynamics: inertia matrix, Coriolis and centrifugal vector and gravity vector. + +In addition, ``franka_ros`` connects Franka Robotics research robots with the entire ROS ecosystem. +It integrates ``libfranka`` into `ROS Control `_. +Additionally, it includes `URDF `_ models and detailed 3D meshes of our +robots and end effectors, which allows visualization (e.g. RViz) and kinematic simulations. +`MoveIt! `_ integration makes it easy to move the robot and control +the gripper, and the provided examples show you how to control your robot using ROS. + +.. important:: + + Data is sent over the network with a frequency of 1 kHz. Therefore, a good network connection + is required! + +.. important:: + + While the FCI is active you have full, exclusive control of the Arm and Hand. This means that + you `cannot` use Desk or Apps at the same time as the FCI. diff --git a/_sources/requirements.rst.txt b/_sources/requirements.rst.txt new file mode 100644 index 0000000..0bd7f1f --- /dev/null +++ b/_sources/requirements.rst.txt @@ -0,0 +1,66 @@ +Minimum system and network requirements +======================================= + +This page only specifies the requirements for running the Franka Control Interface (FCI). +Additional requirements are specified in the documents that you have received with your robot. + +Workstation PC +-------------- + ++---------------------------------------------------------+ +| Minimum System Requirements | ++===================+=====================================+ +| Operating System | Linux with PREEMPT_RT patched kernel| +| | **OR** Windows 10 (experimental) | ++-------------------+-------------------------------------+ +| Network card | 100BASE-TX | ++-------------------+-------------------------------------+ + +Since the robot sends data at 1 kHz frequency, it is important that the workstation PC is configured +to minimize latencies. For example, we recommend to +:ref:`disable CPU frequency scaling `. Other possible optimizations +will depend on your particular system. + +.. _requirement-network: + +Network +------- +If possible, directly connect your workstation PC to the LAN port of Control, i.e. avoid any +intermediate devices such as switches. + +.. important:: + The workstation PC which commands your robot using the FCI must always be connected to the LAN + port of Control (shop floor network) and **not** to the LAN port of the Arm (robot network). + +Having relays in between could lead to delay, jitter or packet loss. This will decrease the +performance of your controller or make it unusable. + +.. hint:: + The best performance can be achieved when connecting directly to the LAN port of Control. + This requires setting up a static IP for the shop floor network in the administrator's interface + beforehand. See :ref:`setting-up-the-network`. + +To control the robot, it must be guaranteed that the sum of the following time measurements is +less than 1 ms: + + * Round trip time (RTT) between the workstation PC and FCI. + * Execution time of your motion generator or control loop. + * Time needed by the robot to process your data and step the internal controller. + +.. caution:: + If the **<1 ms constraint** is violated for a cycle, the received packet is dropped by + FCI. After 20 consecutively dropped packets, your robot `will stop` with the + ``communication_constraints_violation`` error. Current measure of communication quality + can be read from the ``RobotState::control_command_success_rate`` field. + +If a **motion generator command packet is dropped**, the robot takes the previous waypoints and +performs a linear extrapolation (keep acceleration constant and integrate) for the missed +time step. If more than 20 packets are lost or dropped in a row, your robot `will stop`. + +If a **controller command packet is dropped**, FCI will reuse the torques of the last successful +received packet. Again, more than 20 consecutive lost or dropped packets will cause your robot to +`stop`. + +.. hint:: + Measure the performance of your network (see :ref:`network-bandwidth-delay-test`) and the + control or motion generator loop beforehand. diff --git a/_sources/troubleshooting.rst.txt b/_sources/troubleshooting.rst.txt new file mode 100644 index 0000000..1c5c47f --- /dev/null +++ b/_sources/troubleshooting.rst.txt @@ -0,0 +1,249 @@ +Troubleshooting +=============== +This section lists solutions to a set of possible errors which can happen when using the FCI. + + .. hint:: + + Further help is provided in the troubleshooting page of the manual shipped with your robot. + +.. _troubleshooting_realtime_kernel: + +Cannot boot realtime kernel because of "Invalid Signature" +---------------------------------------------------------- + +When you have successfully installed the real-time kernel and try to boot it you might encounter +that Linux is not booting at all. This can happen when you have installed Ubuntu alongside with +Windows (e.g. Dual Boot). Usually then the UEFI boot loader will have *Secure Boot* activated, +which will not allow the unsigned real-time kernel to load. + +The easiest solution is to **disable "Secure Boot"** in your boot loader. This highly depends on your +system, but usually you can enter the boot loader by pressing F2, F3, F12 or DEL key during boot. + + +.. _troubleshooting_connection_timeout: + +Running a libfranka executable fails with "Connection timeout" +---------------------------------------------------------------- + +This error occurs if ``libfranka`` cannot connect to the robot at all. Please check that: + + * Using a robot with system version 4.2.0 or higher requires to enable the FCI mode. To do that + open Desk -> expand the menu in the sidebar -> press `'Activate FCI'`. Further information + about Single Point of Control (SPoC) can be found in the manual shipped with the robot. + * Your workstation is directly connected to Control, not the LAN port of the Arm (see + :ref:`requirement-network`). + * The robot can be reached from your workstation (see :ref:`troubleshooting_robot_not_reachable`). + * The FCI feature file is installed on the robot (see "Settings -> System -> Installed Features"). + If you do not have this feature, please install it. Please reach out to `support@franka.de` with + your serial number if you need to obtain the feature. + +.. _motion-stopped-due-to-discontinuities: + +Motion stopped due to discontinuities or ``communication_constraints_violation`` +-------------------------------------------------------------------------------- + +If the difference between commanded values in subsequent time steps is too large, then the motion is +stopped with a discontinuity error such as ``joint_motion_generator_velocity_discontinuity``. See if +the commanded values do not exceed the :ref:`limits `. + +Discontinuities can occur if your code commands actual jumps to the robot, but also because of +network packet losses. This is also the reason for ``communication_constraints_violation`` errors. +If the issue occurs even when using the provided examples, the problem is most likely related to +overall communication quality. To ensure best performance, please check the following: + + * All source code is compiled with optimizations (``-DCMAKE_BUILD_TYPE=Release``). If you installed + ``libfranka`` and ``franka_ros`` from the ROS repositories, this is already the case for these + projects. However, your own source code needs to be compiled with optimizations as well. + * Connect your PC directly to Control, without using any intermediate switches. The + :ref:`network setup instructions ` describe how to do this. + * Verify your network connection by executing the `network bandwidth, delay and jitter test`_. + * ``franka::Robot`` is instantiated with ``RealtimeConfig::kEnforce``. This is the default if no + ``RealtimeConfig`` is explicitly specified in the constructor. + * Power saving features are disabled (cpu frequency scaling, power saving mode, + laptop on battery, BIOS power saving features, etc.). See `Disabling CPU frequency scaling`_. + +.. _disable_cpu_frequency_scaling: + +Disabling CPU frequency scaling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CPUs are often configured to use a lower operating frequency when under a light load in order to +conserve power. We recommend to disable this feature as it leads to higher latencies when using +``libfranka``. To check and modify the power saving mode, install the ``cpufrequtils`` package: + +.. code-block:: shell + + sudo apt install cpufrequtils + +Run ``cpufreq-info`` to see available "governors" and the current CPU frequency. Here is an example +output: + +.. code-block:: shell + + $ cpufreq-info + ... + analyzing CPU 0: + driver: intel_pstate + CPUs which run at the same hardware frequency: 0 + CPUs which need to have their frequency coordinated by software: 0 + maximum transition latency: 0.97 ms. + hardware limits: 400 MHz - 3.00 GHz + available cpufreq governors: performance, powersave + current policy: frequency should be within 400 MHz and 3.00 GHz. + The governor "powersave" may decide which speed to use + within this range. + current CPU frequency is 500 MHz. + ... + +In this example, the maximum frequency is 3 GHz, but the current one is 500 Mhz due to the +``powersave`` policy. In this case we can benefit by setting the governor to ``performance``. + +To change this setting using the Ubuntu GUI, install the ``indicator-cpufreq`` package. A widget in +the top bar of the Unity user interface should allow you to set the current policy. + +To change this setting using the terminal, execute the following commands: + +.. code-block:: shell + + sudo systemctl disable ondemand + sudo systemctl enable cpufrequtils + sudo sh -c 'echo "GOVERNOR=performance" > /etc/default/cpufrequtils' + sudo systemctl daemon-reload && sudo systemctl restart cpufrequtils + +They will disable the ``ondemand`` CPU scaling daemon, create a ``/etc/default/cpufrequtils`` +configuration file, and then restart the ``cpufrequtils`` service. + +After enabling the ``performance`` governor, the ``cpufreq-info`` results are: + +.. code-block:: shell + + $ cpufreq-info + ... + analyzing CPU 0: + driver: intel_pstate + CPUs which run at the same hardware frequency: 0 + CPUs which need to have their frequency coordinated by software: 0 + maximum transition latency: 0.97 ms. + hardware limits: 400 MHz - 3.00 GHz + available cpufreq governors: performance, powersave + current policy: frequency should be within 400 MHz and 3.00 GHz. + The governor "performance" may decide which speed to use + within this range. + current CPU frequency is 2.83 GHz. + ... + +Now the example output shows a CPU frequency close to the maximum one. You can +also directly verify the current governor using the ``cpufreq-info -p`` command. + +.. _troubleshooting_robot_not_reachable: + +Robot is not reachable +---------------------- + +Try to ping the robot using the following command: + +.. code-block:: shell + + ping + +If this command fails, the robot is not properly connected to the network, or the IP address +is not correctly assigned during the setup phase. Please set up the network according to the +documents sent with your robot. + +.. _troubleshooting_udp_timeout: + +Running a libfranka executable fails with "UDP receive: Timeout" +---------------------------------------------------------------- + +This error occurs if the robot state can not be received by ``libfranka``. Please check that +your workstation's firewall does not block incoming UDP packets (see ``sudo iptables -L``). + +.. _network-bandwidth-delay-test: + +Network bandwidth, delay and jitter test +---------------------------------------- + +In order to evaluate the network performance of your (possible) control PC we've developed two +tests. The first, a ping test, can be executed without the need of libfranka or franka_ros +installed on your system. If your system passes the first ping test, you can run the +advanced UDP network performance analysis. + +.. _network-ping-test: + +Simple ping tests +^^^^^^^^^^^^^^^^^ + +The following command will simulate a network load which is equivalent to a scenario where the +robot is controlled by the FCI: + +.. code-block:: shell + + sudo ping -i 0.001 -D -c 10000 -s 1200 + +Example output: + +.. code-block:: shell + + PING 1200(1228) bytes of data. + [1500982522.977579] 1208 bytes from : icmp_seq=1 ttl=64 time=0.279 ms + [1500982522.978423] 1208 bytes from : icmp_seq=2 ttl=64 time=0.224 ms + [1500982522.979434] 1208 bytes from : icmp_seq=3 ttl=64 time=0.196 ms + [1500982522.980482] 1208 bytes from : icmp_seq=4 ttl=64 time=0.243 ms + .... + [1500982533.034267] 1208 bytes from : icmp_seq=9999 ttl=64 time=0.236 ms + [1500982533.035211] 1208 bytes from : icmp_seq=10000 ttl=64 time=0.203 ms + + --- ping statistics --- + 10000 packets transmitted, 10000 received, 0% packet loss, time 10057ms + rtt min/avg/max/mdev = 0.147/0.240/0.502/0.038 ms + + +The example result shows an average round-trip time of 0.24 ms and a maximum round-trip time of 0.5 +ms. The standard deviation `mdev` is around 0.04 ms. As explained in the +:ref:`network requirements section` it must be guaranteed that the sum of the +round-trip time and the execution time of the motion generator or control loop is +**less than 1 ms**. If this constraint is violated for a cycle, the received packet is dropped by +the FCI. + +If the round-trip time is long even with a direct connection, consider +purchasing a separate, high performance PCI-Express network card for your +workstation PC. See if there are dedicated drivers for your network card, +these usually offer better performance. Lastly, the CPU can also be a limiting +factor for network performance. + + +Advanced network performance analysis +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``communication_test`` executable, which is part of the ``libfranka`` examples since +version 0.5, executes a control loop and provides feedback about the lost robot +states as well as the maximum, minimum and average control command success rate. + +If you installed the ``ros-noetic-libfranka`` package or installed ``libfranka`` from source, you can execute the test with: + +.. code-block:: shell + + source /opt/ros/noetic/setup.sh # only needed when installed with ROS + communication_test + +If you do not want to install ``libfranka`` system-wide you can execute the following command inside your build folder after compiling ``libfranka`` +from source: + +.. code-block:: shell + + ./examples/communication_test + +Running a libfranka executable fails with "Incompatible Library Version" +------------------------------------------------------------------------ + +This happens when your version of libfranka is incompatible with the system version of your robot. +The error contains the server version of the robot. You can use that number to +:ref:`choose the correct libfranka version for your robot`. + +Running a libfranka executable fails with "command rejected due to activated safety function!" or "command preempted due to activated safety function!" +------------------------------------------------------------------------------------------------------------------------------------------------------- + +This error occurs when a safety function defined in Watchman (the safety configurator area of the Desk webpage) is active. +For example, there could be an active safety function limiting the robot's speed to 0.2 m/s. As this cannot be guaranteed when using FCI, the +robot will not move. However, you can still read the robot state. In order to command movements to the robot again, +you either need to disable the safety function or delete the corresponding safety rule in Watchman. diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8549469 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,134 @@ +/* + * _sphinx_javascript_frameworks_compat.js + * ~~~~~~~~~~ + * + * Compatability shim for jQuery and underscores.js. + * + * WILL BE REMOVED IN Sphinx 6.0 + * xref RemovedInSphinx60Warning + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/accessing-admin.png b/_static/accessing-admin.png new file mode 100644 index 0000000..7a3ab23 Binary files /dev/null and b/_static/accessing-admin.png differ diff --git a/_static/activate_fci.png b/_static/activate_fci.png new file mode 100644 index 0000000..f4f2fa3 Binary files /dev/null and b/_static/activate_fci.png differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..eeb0519 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,899 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} +dl.field-list > dt:after { + content: ":"; +} + + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/collision-coarse.png b/_static/collision-coarse.png new file mode 100644 index 0000000..31379e4 Binary files /dev/null and b/_static/collision-coarse.png differ diff --git a/_static/collision-fine.png b/_static/collision-fine.png new file mode 100644 index 0000000..f6d4c98 Binary files /dev/null and b/_static/collision-fine.png differ diff --git a/_static/control-static-ip.png b/_static/control-static-ip.png new file mode 100644 index 0000000..ee4cc79 Binary files /dev/null and b/_static/control-static-ip.png differ diff --git a/_static/control.png b/_static/control.png new file mode 100644 index 0000000..c4e1f1b Binary files /dev/null and b/_static/control.png differ diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000..e380325 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000..0d9ae7e --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/dh-diagram.png b/_static/dh-diagram.png new file mode 100644 index 0000000..34bfab4 Binary files /dev/null and b/_static/dh-diagram.png differ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..c3db08d --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,264 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.highlightSearchWords(); + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords: () => { + const highlight = + new URLSearchParams(window.location.search).get("highlight") || ""; + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + const url = new URL(window.location); + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + const blacklistedElements = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", + ]); + document.addEventListener("keydown", (event) => { + if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements + if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + case "Escape": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.hideSearchWords(); + event.preventDefault(); + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..b57ae3b --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/edit-connections.png b/_static/edit-connections.png new file mode 100644 index 0000000..8b063d4 Binary files /dev/null and b/_static/edit-connections.png differ diff --git a/_static/favicon.png b/_static/favicon.png new file mode 100644 index 0000000..b3cf4b5 Binary files /dev/null and b/_static/favicon.png differ diff --git a/_static/fci-architecture-non-realtime.png b/_static/fci-architecture-non-realtime.png new file mode 100644 index 0000000..cf37d8e Binary files /dev/null and b/_static/fci-architecture-non-realtime.png differ diff --git a/_static/fci-architecture.png b/_static/fci-architecture.png new file mode 100644 index 0000000..695ec18 Binary files /dev/null and b/_static/fci-architecture.png differ diff --git a/_static/fci-on.png b/_static/fci-on.png new file mode 100644 index 0000000..6f4b7e6 Binary files /dev/null and b/_static/fci-on.png differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/frames.svg b/_static/frames.svg new file mode 100644 index 0000000..151096c --- /dev/null +++ b/_static/frames.svg @@ -0,0 +1,449 @@ + + + + + + + + + + image/svg+xml + + + + + + + + G + + + + panda_link8 + + <arm_id>_link8 + Flange Frame + + + + panda_hand + + <arm_id>_hand + <arm_id>link8 rotated by -45 degree around Z-axis + + + + panda_link8->panda_hand + + + transform defined in URDF + + + + panda_NE + + <arm_id>_NE + Nominal End-Effector Frame + + + + panda_link8->panda_NE + + + transform defined in Desk + + + + panda_hand_tcp + + <arm_id>_hand_tcp + Frame between the fingers of the hand + Is equal to <arm_id>_NE if the end-effector + was set to 'Franka Hand' inside Desk + + (can be changed by passing 'tcp_xyz' & 'tcp_rpy') + + + + panda_hand->panda_hand_tcp + + + transform defined in URDF + + + + panda_EE + + <arm_id>_EE + End-Effector Frame + Used for Cartesian Pose interface and inverse kinematics + Equal to <arm_id>_NE by default + + + + panda_NE->panda_EE + + +  transform defined via set_EE_frame + + + + panda_K + + <arm_id>_K + Stiffness frame K + Used for measuring forces + Equal to <arm_id>_EE by default + + + + panda_EE->panda_K + + +  transform defined via set_K_frame + + + diff --git a/_static/franka-gazebo-example-grasp.png b/_static/franka-gazebo-example-grasp.png new file mode 100644 index 0000000..5dd2ae8 Binary files /dev/null and b/_static/franka-gazebo-example-grasp.png differ diff --git a/_static/franka-gazebo-example.png b/_static/franka-gazebo-example.png new file mode 100644 index 0000000..172dfd5 Binary files /dev/null and b/_static/franka-gazebo-example.png differ diff --git a/_static/jquery-3.6.0.js b/_static/jquery-3.6.0.js new file mode 100644 index 0000000..fc6c299 --- /dev/null +++ b/_static/jquery-3.6.0.js @@ -0,0 +1,10881 @@ +/*! + * jQuery JavaScript Library v3.6.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2021-03-02T17:08Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.6.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.6 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2021-02-16 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. If + // that handler calls `.remove()` on the element, the data is cleared, + // leaving `result` undefined. We need to guard against this. + return result && result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + // Suppress native focus or blur as it's already being fired + // in leverageNative. + _default: function() { + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Compatible versions

+
+

Compatibility with libfranka

+

Different versions of open source components compatible. +The following table provides an overview. It is recommended to always work with up-to-date versions. +The >= indicates that it is not tested against newer robot system versions, meaning +compatibility is not guaranteed (i.e. libfranka 0.2.0 is not compatible with the robot +system version 4.0.0).

+

The Robot system versions 2.x.x are not listed in the table below, +but are included as compatible with the Robot system version >= 1.3.0. Therefore, these are +compatible with the libfranka version 0.4.0 and 0.5.0.

+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Robot system version

libfranka version

Robot / Gripper +Server version

franka_ros version

Ubuntu / ROS

>= 5.2.0 (FR3)

>= 0.10.0

6 / 3

>= 0.10.0

20.04 / noetic

>= 4.2.1 (FER)

>= 0.9.1 < 0.10.0

5 / 3

>= 0.8.0

20.04 / noetic

>= 4.0.0 (FER)

>= 0.8.0

4 / 3

>= 0.8.0

20.04 / noetic

>= 3.0.0 (FER)

0.7.1

3 / 3

0.7.0

18.04 / melodic

>= 1.3.0 (FER)

0.5.0

3 / 2

0.6.0

16.04 / kinetic

>= 1.2.0 (FER)

0.3.0

2 / 2

0.4.0

16.04 / kinetic

>= 1.1.0 (FER)

0.2.0

2 / 1

+

Robot version line 17 +and Gripper version line 17 +are part of libfranka-common repository, a submodule of libfranka repository.

+

Franka MATLAB® compatible versions are located here.

+
+

Caution

+

Franka Robotics currently does not provide any support for Windows or Arm

+
+
+
+

Compatibility with the kernel

+

There are different kernels, which are compatible with different Ubuntu system versions. +The following table gives an overview of recommended Kernels.

+ ++++ + + + + + + + + + + + + + + + + +

Kernel version

Ubuntu

5.9.1

20.04 (Focal Fossa)

5.4.19

18.04 (Bionic)

4.14.12

16.04 (Xenial Xerus)

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/control_parameters.html b/control_parameters.html new file mode 100644 index 0000000..0572f22 --- /dev/null +++ b/control_parameters.html @@ -0,0 +1,748 @@ + + + + + + + Robot and interface specifications — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Robot and interface specifications

+

Realtime control commands sent to the robot should fulfill recommended and necessary +conditions. Recommended conditions should be fulfilled to ensure optimal operation of the +robot. If necessary conditions are not met then the motion will be aborted.

+

The final robot trajectory is the result of processing the user-specified trajectory ensuring +that recommended conditions are fulfilled. As long as necessary conditions are met, the robot +will try to follow the user-provided trajectory but it will only match the final trajectory +if it also fulfills recommended conditions. If the necessary conditions are violated, an error +will abort the motion: if, for instance, the first point of the user defined joint trajectory +is very different from robot start position (\(q(t=0) \neq q_c(t=0)\)) a start_pose_invalid error +will abort the motion.

+

Values for the constants used in the equations below are shown in the Limits for Panda and Limits for Franka Research 3 section.

+
+

Joint trajectory requirements

+
+

Necessary conditions

+
    +
  • \(q_{min} < q_c < q_{max}\)

  • +
  • \(-\dot{q}_{max} < \dot{q}_c < \dot{q}_{max}\)

  • +
  • \(-\ddot{q}_{max} < \ddot{q}_c < \ddot{q}_{max}\)

  • +
  • \(-\dddot{q}_{max} < \dddot{q}_c < \dddot{q}_{max}\)

  • +
+
+ +
+
+

Cartesian trajectory requirements

+
+

Necessary conditions

+
    +
  • \(T\) is proper transformation matrix

  • +
  • \(-\dot{p}_{max} < \dot{p_c} < \dot{p}_{max}\) (Cartesian velocity)

  • +
  • \(-\ddot{p}_{max} < \ddot{p_c} < \ddot{p}_{max}\) (Cartesian acceleration)

  • +
  • \(-\dddot{p}_{max} < \dddot{p_c} < \dddot{p}_{max}\) (Cartesian jerk)

  • +
+

Conditions derived from inverse kinematics:

+
    +
  • \(q_{min} < q_c < q_{max}\)

  • +
  • \(-\dot{q}_{max} < \dot{q_c} < \dot{q}_{max}\)

  • +
  • \(-\ddot{q}_{max} < \ddot{q_c} < \ddot{q}_{max}\)

  • +
+
+
+

Recommended conditions

+

Conditions derived from inverse kinematics:

+
    +
  • \(-{\tau_j}_{max} < {\tau_j}_d < {\tau_j}_{max}\)

  • +
  • \(-\dot{\tau_j}_{max} < \dot{{\tau_j}_d} < \dot{\tau_j}_{max}\)

  • +
+

At the beginning of the trajectory, the following conditions should be fulfilled:

+
    +
  • \({}^OT_{EE} = {{}^OT_{EE}}_c\)

  • +
  • \(\dot{p}_{c} = 0\) (Cartesian velocity)

  • +
  • \(\ddot{p}_{c} = 0\) (Cartesian acceleration)

  • +
+

At the end of the trajectory, the following conditions should be fulfilled:

+
    +
  • \(\dot{p}_{c} = 0\) (Cartesian velocity)

  • +
  • \(\ddot{p}_{c} = 0\) (Cartesian acceleration)

  • +
+
+
+
+

Controller requirements

+
+

Necessary conditions

+
    +
  • \(-\dot{\tau_j}_{max} < \dot{{\tau_j}_d} < \dot{\tau_j}_{max}\)

  • +
+
+
+

Recommended conditions

+
    +
  • \(-{\tau_j}_{max} < {\tau_j}_d < {\tau_j}_{max}\)

  • +
+

At the beginning of the trajectory, the following conditions should be fulfilled:

+
    +
  • \({\tau_j}_{d} = 0\)

  • +
+
+
+
+

Limits for Panda

+

Limits in the Cartesian space are as follows:

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Translation

Rotation

Elbow

\(\dot{p}_{max}\)

1.7 \(\frac{\text{m}}{\text{s}}\)

2.5 \(\frac{\text{rad}}{\text{s}}\)

2.1750 \(\frac{rad}{\text{s}}\)

\(\ddot{p}_{max}\)

13.0 \(\frac{\text{m}}{\text{s}^2}\)

25.0 \(\frac{\text{rad}}{\text{s}^2}\)

10.0 \(\;\frac{rad}{\text{s}^2}\)

\(\dddot{p}_{max}\)

6500.0 \(\frac{\text{m}}{\text{s}^3}\)

12500.0 \(\frac{\text{rad}}{\text{s}^3}\)

5000.0 \(\;\frac{rad}{\text{s}^3}\)

+

Joint space limits are:

+ +++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Joint 1

Joint 2

Joint 3

Joint 4

Joint 5

Joint 6

Joint 7

Unit

\(q_{max}\)

2.8973

1.7628

2.8973

-0.0698

2.8973

3.7525

2.8973

\(\text{rad}\)

\(q_{min}\)

-2.8973

-1.7628

-2.8973

-3.0718

-2.8973

-0.0175

-2.8973

\(\text{rad}\)

\(\dot{q}_{max}\)

2.1750

2.1750

2.1750

2.1750

2.6100

2.6100

2.6100

\(\frac{\text{rad}}{\text{s}}\)

\(\ddot{q}_{max}\)

15

7.5

10

12.5

15

20

20

\(\frac{\text{rad}}{\text{s}^2}\)

\(\dddot{q}_{max}\)

7500

3750

5000

6250

7500

10000

10000

\(\frac{\text{rad}}{\text{s}^3}\)

\({\tau_j}_{max}\)

87

87

87

87

12

12

12

\(\text{Nm}\)

\(\dot{\tau_j}_{max}\)

1000

1000

1000

1000

1000

1000

1000

\(\frac{\text{Nm}}{\text{s}}\)

+

The arm can reach its maximum extension when joint 4 has angle \(q_{elbow-flip}\), where \(q_{elbow-flip} = -0.467002423653011\:rad\). +This parameter is used to determine the flip direction of the elbow.

+
+
+

Limits for Franka Research 3

+

Limits in the Cartesian space are as follows:

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Translation

Rotation

Elbow

\(\dot{p}_{max}\)

3.0 \(\frac{\text{m}}{\text{s}}\)

2.5 \(\frac{\text{rad}}{\text{s}}\)

2.620 \(\frac{rad}{\text{s}}\)

\(\ddot{p}_{max}\)

9.0 \(\frac{\text{m}}{\text{s}^2}\)

17.0 \(\frac{\text{rad}}{\text{s}^2}\)

10.0 \(\;\frac{rad}{\text{s}^2}\)

\(\dddot{p}_{max}\)

4500.0 \(\frac{\text{m}}{\text{s}^3}\)

8500.0 \(\frac{\text{rad}}{\text{s}^3}\)

5000.0 \(\;\frac{rad}{\text{s}^3}\)

+

Joint space limits are:

+ +++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Joint 1

Joint 2

Joint 3

Joint 4

Joint 5

Joint 6

Joint 7

Unit

\(q_{max}\)

2.7437

1.7837

2.9007

-0.1518

2.8065

4.5169

3.0159

\(\text{rad}\)

\(q_{min}\)

-2.7437

-1.7837

-2.9007

-3.0421

-2.8065

0.5445

-3.0159

\(\text{rad}\)

\(\dot{q}_{max}\)

2.62

2.62

2.62

2.62

5.26

4.18

5.26

\(\frac{\text{rad}}{\text{s}}\)

\(\ddot{q}_{max}\)

10

10

10

10

10

10

10

\(\frac{\text{rad}}{\text{s}^2}\)

\(\dddot{q}_{max}\)

5000

5000

5000

5000

5000

5000

5000

\(\frac{\text{rad}}{\text{s}^3}\)

\({\tau_j}_{max}\)

87

87

87

87

12

12

12

\(\text{Nm}\)

\(\dot{\tau_j}_{max}\)

1000

1000

1000

1000

1000

1000

1000

\(\frac{\text{Nm}}{\text{s}}\)

+

The arm can reach its maximum extension when joint 4 has angle \(q_{elbow-flip}\), where \(q_{elbow-flip} = -0.467002423653011\:rad\). +This parameter is used to determine the flip direction of the elbow.

+
+

Important

+

Note that the maximum joint velocity depends on the joint position. The maximum and minimum joint velocities at a certain joint position are calculated as:

+ +++ + + + + + + +
+_images/pbv_equations_max.svg
+

Maximum velocities

+
+
+
+_images/pbv_equations_min.svg
+

Minimum velocities

+
+
+
+
+

As most motion planners cannot deal with those functions for describing the velocity limits of each joint but they only deal with +fixed velocity limits (rectangular limits), we are providing here a suggestion on which values to use for them.

+

In the figures below the system velocity limits are visualized by the red and blue thresholds while the suggested +“position-velocity rectangular limits” are visualized in black.

+ + ++++ + + + + + + + + + + + + + + +
Visualization of the joint limits of FR3
+_images/pbv_limits_j1.svg
+

Velocity limits of Joint 1

+
+
+
+_images/pbv_limits_j2.svg
+

Velocity limits of Joint 2

+
+
+
+_images/pbv_limits_j3.svg
+

Velocity limits of Joint 3

+
+
+
+_images/pbv_limits_j4.svg
+

Velocity limits of Joint 4

+
+
+
+_images/pbv_limits_j5.svg
+

Velocity limits of Joint 5

+
+
+
+_images/pbv_limits_j6.svg
+

Velocity limits of Joint 6

+
+
+
+_images/pbv_limits_j7.svg
+

Velocity limits of Joint 7

+
+
+
+

Here are the parameters describing the suggested position-velocity rectangular limits:

+ +++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Joint 1

Joint 2

Joint 3

Joint 4

Joint 5

Joint 6

Joint 7

Unit

\(q_{max}\)

2.3093

1.5133

2.4937

-0.4461

2.4800

4.2094

2.6895

\(\text{rad}\)

\(q_{min}\)

-2.3093

-1.5133

-2.4937

-2.7478

-2.4800

0.8521

-2.6895

\(\text{rad}\)

\(\dot{q}_{max}\)

2

1

1.5

1.25

3

1.5

3

\(\frac{\text{rad}}{\text{s}}\)

+
+

Important

+

These limits are the values that are used by default in the rate limiter and in the URDF inside franka_ros. +However, these are only a suggestion, you are free to define your own rectangles within the specification accordingly to your needs.

+

Since FR3 does not inherently implement any restriction to the system limits (red and blue line in the plots above), you are also free +to implement your own motion generator to exploit the HW capabilities of FR3 beyond the rectangular limits imposed by existing motion generators.

+
+
+
+

Denavit–Hartenberg parameters

+

The Denavit–Hartenberg parameters for the Panda’s kinematic chain are derived following Craig’s convention and are as follows:

+
+_images/dh-diagram.png +
+

Panda’s kinematic chain.

+
+
+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Joint

\(a\;(\text{m})\)

\(d\;(\text{m})\)

\(\alpha\;(\text{rad})\)

\(\theta\;(\text{rad})\)

Joint 1

0

0.333

0

\(\theta_1\)

Joint 2

0

0

\(-\frac{\pi}{2}\)

\(\theta_2\)

Joint 3

0

0.316

\(\frac{\pi}{2}\)

\(\theta_3\)

Joint 4

0.0825

0

\(\frac{\pi}{2}\)

\(\theta_4\)

Joint 5

-0.0825

0.384

\(-\frac{\pi}{2}\)

\(\theta_5\)

Joint 6

0

0

\(\frac{\pi}{2}\)

\(\theta_6\)

Joint 7

0.088

0

\(\frac{\pi}{2}\)

\(\theta_7\)

Flange

0

0.107

0

0

+
+

Note

+

\({}^0T_{1}\) is the transformation matrix which describes the position and orientation of +frame 1 in frame 0. A kinematic chain can be calculated like the following: +\({}^0T_{2} = {}^0T_{1} * {}^1T_{2}\)

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/faq.html b/faq.html new file mode 100644 index 0000000..4407aa7 --- /dev/null +++ b/faq.html @@ -0,0 +1,151 @@ + + + + + + + FAQ — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

FAQ

+
+

How can I use libfranka in my own CMake-based project?

+

Add the following to your CMakeLists.txt:

+
find_package(Franka REQUIRED)
+
+# ...
+
+target_link_libraries(<target-name> Franka::Franka)
+
+
+

To build against a custom libfranka location, compile +your project with:

+
# Build against libfranka build directory
+cmake -DFranka_DIR=/path/to/libfranka/build
+
+# Build against custom libfranka installation
+cmake -DFranka_DIR=/path/to/usr/lib/cmake/Franka
+
+
+
+

Important

+

Don’t forget to build your project in release mode +(cmake -DCMAKE_BUILD_TYPE=Release)!

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/fr3-certification-remarks.html b/fr3-certification-remarks.html new file mode 100644 index 0000000..69d034e --- /dev/null +++ b/fr3-certification-remarks.html @@ -0,0 +1,515 @@ + + + + + + + FR3 certification remarks — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

FR3 certification remarks

+

Congratulations on your new Franka Research 3. +As part of the package you received the documentation including Product Manual Franka Production 3 (110010/1.5/EN). +Even though its title indicates it is for the other Franka robot arm - FP3, +it is in general also valid for Franka Research 3. +We recommend reading this product manual before using the robot. Please be aware that especially +when using the FCI and in general use cases in research applications there might be some deviations from this manual. +Therefore, please consult the following table, where the applicability is stated and check the +referred documentation like the datasheet.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Chapter

Content

Applicable for FR3

Comment

1

ABOUT Franka Robotics

fully

2

RIGHTS OF USE AND PROPERTY RIGHTS

fully

2.1

General

fully

2.2

Identification

fully

3

DECLARATION OF INCORPORATION AND CERTIFICATES

3.1

Declaration of Incorporation

not applicable

Research use not in scope according to article 1 2. (h)

3.2

Certificates

fully

3.3

Further Statements

not applicable

Due to intended use partially applicable as stated in RoHS directive 2011/65/EU article 2 4 (j). Content regarding Battery Directive applies fully.

3.4

Labeling on the Equipment

fully

4

SAFETY

4.1

Safety Instructions and General Indications

fully

4.2

Notice of Liability

fully

4.3

Intended Use

fully with remark

With the extension of use in research applications

4.4

Misuse

fully

4.5

General Possible Dangers and Safety Measures when Working with Robots

fully

4.6

Application Related Possible Dangers and Safety Measures

fully

4.7

Installation of Safety Peripherals

fully

4.8

Fail-safe Locking System

fully

4.9

Manually Moving the Arm

fully

4.10

Safety Concept

fully

4.11

Safety Functionalities

with limitations for FCI Usage

As mentioned in Datasheet Franka Research 3 (120020)

4.12

Safety Settings and Watchman

fully

5

ROLES AND PERSONAE

fully

6

SCOPE OF DELIVERY AND ACCESSORIES

fully

6.1

Equipment Overview

fully

6.2

Scope of Delivery and Additional Equipment

fully with remark

As mentioned in FCI reference pager (120070)

6.3

Available Spare Parts and Accessories

fully

7

TECHNICAL SPECIFICATIONS

partly applicable

Datasheet ( pages) replaced by Franka Research 3 Datasheet - Safety notice still valid

8

TRANSPORT AND HANDLING

fully

8.1

Ambient Conditions for Delivery and Transport

fully

9

MOUNTING AND INSTALLATION

fully

9.1

Unpacking the Equipment

fully

9.2

Correct Installation Site

fully

9.3

Preparing the Installation Site

fully

9.4

Mounting the Arm

fully

9.5

Positioning the Control

fully

9.6

Wiring and Electrical Installation

fully

9.7

Mounting End Effectors

fully

9.8

Practical Tips for Usage and Positioning of Franka Production 3

fully

9.9

Re-packing the Arm

fully

10

OPERATION

fully with remark

As mentioned in FCI documentation.

10.1

Switching On

fully

10.2

Safety Relevant Tests of Franka Production 3

fully with remark

As mentioned in FCI documentation.

10.3

Connecting a User Interface Device

fully

10.4

Software Setup

fully with remark

As mentioned in FCI documentation.

10.5

Switching Off and Restarting

fully

11

WORKING WITH FRANKA PRODUCTION 3

fully with remark

As mentioned in FCI documentation.

11.1

Robotic Basics

fully

11.2

Franka UI

fully with remark

As mentioned in FCI documentation.

11.3

Apps

fully

11.4

Operating Modes

fully with remark

As mentioned in FCI documentation.

11.5

Single Point of Control

fully with remark

As mentioned in FCI documentation.

11.6

Teach a Task

fully

11.7

Test & Jog

fully

11.8

Work

fully with remark

As mentioned in FCI documentation.

11.09

Troubleshooting

fully with remark

As mentioned in FCI documentation.

12

MANAGING FRANKA PRODUCTION 3

fully

12.1

Franka World

fully

12.2

Managing Apps and Updates

fully

12.3

Hub

fully

12.4

Updates

fully

13

MAINTENANCE AND DISPOSAL

fully

13.1

Maintenance

fully

13.2

Cleaning

fully

13.3

System Lifetime

fully

13.4

Disposal

fully

14

SERVICE AND SUPPORT

fully

15

APPENDIX

fully

15.1

Stopping Times and Distances

fully

15.2

Response times

fully

15.3

Safe Position Accuracy

fully

16

GLOSSARY

fully

17

INDEX

fully

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/CHANGELOG.html b/franka_matlab/CHANGELOG.html new file mode 100644 index 0000000..56dc88d --- /dev/null +++ b/franka_matlab/CHANGELOG.html @@ -0,0 +1,204 @@ + + + + + + + Changelog: — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Changelog:

+
+

1.0.0 (11-03-2024)

+
    +
  • BREAKING Robot Settings standardization with Matlab OOP.

  • +
  • BREAKING Adding the option to set the rate limiter and the cutoff frequency in the apply control simulink block.

  • +
  • BREAKING Removing the get initial robot state block from the simulink library.

  • +
  • BREAKING Enhanced modular building structure for the Franka Simulink Library. Easy incorporation to larger projects and Harware Support Packages.

  • +
  • BREAKING New Matlab object oriented API expressed based on the new FrankaRobot() class. Incorporation of existing Franka MATLAB functions as methods of the new API Class.

  • +
  • BREAKING Removing all the “Panda” naming conventions. Replaced with “Franka Robot”.

  • +
  • BREAKING Franka MATLAB is now distributed as a Toolbox Matlab Add-On. No installation script needed.

  • +
  • Fixing collision threshold setting bug.

  • +
  • Oldest supported Matlab version is now the R2021a.

  • +
  • Adding the option to set the Nominal End-Effector to End-Effector frame NE_T_EE in the Simulink Block “Apply Control”.

  • +
  • Expansion of the Matlab API with the new methods gripper_state(), gripper_homing(), gripper_grasp(), gripper_move() and gripper_stop() for controlling the Franka Gripper.

  • +
  • Expansion of the Matlab API with the new method joint_trajectory_motion() for following precomputed joint trajectories.

  • +
  • Creation of the new demo pick_and_place_with_RRT.mlx showcasing a workflow approach for the new Matlab API.

  • +
+
+
+

0.3.1 (23-03-2023)

+
    +
  • Bugfix. Properly setting the collision threshold values in Simulink.

  • +
+
+
+

0.3.0 (20-09-2022)

+
    +
  • Windows 10 support (Experimental mainly due to the non-Real-Time nature of the default Windows system).

  • +
  • Project now relies on the leaner “Generic Real-Time” .tlc (grt.tlc) target framework.

  • +
  • Support for XCP communication protocol (vehicle network communication). Data inspector is now enabled!

  • +
  • Support for “Run on custom Hardware” Simulink App for controlling the “Build-deploy-execute-connect” workflow.

  • +
  • Project back-end overal simplification with modified rt_main.cpp for handling the external mode as a seperate thread.

  • +
  • BREAKING all the Franka MATLAB functions are starting with the “franka_” prefix.

  • +
  • Expansion of Matlab library with the functions franka_communication_test(), franka_joint_poses(), franka_robot_state() and franka_joint_point_to_point_motion().

  • +
  • Addition of the Simulink demo, “joint_impedance_control.slx”.

  • +
  • Fixing the bug when utilizing the Control Modes “Torque Control - X`.

  • +
+
+
+

0.2.1 (29-04-2022)

+
    +
  • Adding supoort for all versions from Matlab2019a till Matlab2021a with libfranka 0.9.0.

  • +
+
+
+

0.2.0 (31-05-2021)

+
    +
  • franka_matlab upgrade, supports Matlab2019a, Matlab2019b, Matlab2020a, Matlab2020b, Matlab2021a & libfranka 0.8.0.

  • +
+
+
+

0.1.1 (01-07-2020)

+
    +
  • Any dependences that lead to source code linking for the Simulink Franka Library during mexing removed. That fixes the memory corruption +bug when shutting down Matlab.

  • +
  • Simulink Franka Library sFunctions C++ and tlc implementations decoupled from src code necessary for automatic code gen. SRC Folder can be treated seperately as a C++ project.

  • +
+
+
+

0.1.0 (21-01-2020)

+
    +
  • Features:

    +
      +
    • Simulink Library for Franka Robot, includes the following blocks:

      +
        +
      • Franka Simulink Iterface for applying the desired control, plus additional parameters.

      • +
      • Read Initial Robot State for reading initial values during the first execution step for any desirable signal. The set of the desired signals can be set through the mask in free text form.

      • +
      • Read Robot State for reading the values of any desirable signal during execution. The set of the desired signals can be set through the mask in free text form.

      • +
      • Franka Model for reading the values of all Model parameters of the Franka Robot during execution.

      • +
      • Duration Period for reading the current step, sample time. If communication is not secured during the 1ms, the block will return the value of 2ms or 3ms etc.

      • +
      • Gripper Read State for reading the current values out of the Franka Gripper.

      • +
      +
    • +
    • franka_robot.tlc & franka_robot_shrlib.tlc custom linux targets, based on ert, that offer ext mode that is real time capable(package drop in case of main step frame loss).

    • +
    • Matlab Library(Experimental, limited support), includes the following command:

      +
        +
      • automatic_error_recovery(robot_ip). Execute through a matlab command line for getting automatically out of an error state.

      • +
      +
    • +
    • Simulink Library misc(Experimental, limited support) that includes a set of UI buttons with callback scripts with the potential to automate some of the dev. workflow.

    • +
    +
  • +
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Franka Robotics GmbH.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/compatibility.html b/franka_matlab/compatibility.html new file mode 100644 index 0000000..d3012a2 --- /dev/null +++ b/franka_matlab/compatibility.html @@ -0,0 +1,195 @@ + + + + + + + Compatible versions — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Compatible versions

+
+

Compatibility with Matlab & libfranka

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Franka MATLAB version

libfranka version

Matlab Version

1.0.0

0.9.x & 0.13.x

\(\geq\) R2021a

0.3.1

0.9.x & 0.10.x

\(\geq\) R2019a

0.3.0

0.9.x & 0.10.x

\(\geq\) R2019a

0.2.1

0.9.x

R2019a to R2021a

0.2.0

0.8.0

R2019a to R2021a

0.1.1

0.7.1

R2019a

0.1.0

0.7.1

R2019a

+

libfranka and robot system compatibility

+
+

Important

+

The pick_and_place_with_RRT.mlx Matlab demo is compatible with Matlab \(\geq\) R2021b, as the required libraries from the Matlab Robotics Toolbox have only introduced since then.

+
+
+

Important

+

Make sure that you’ve installed and set up Matlab with a compatible compiler version! You can find the list of Matlab +compatible compilers in this link.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/franka_matlab_changelog.html b/franka_matlab/franka_matlab_changelog.html new file mode 100644 index 0000000..600ea50 --- /dev/null +++ b/franka_matlab/franka_matlab_changelog.html @@ -0,0 +1,218 @@ + + + + + + + Franka MATLAB Toolbox changelog — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Franka MATLAB Toolbox changelog

+
+

1.0.0 (11-03-2024)

+
    +
  • BREAKING Robot Settings standardization with Matlab OOP.

  • +
  • BREAKING Adding the option to set the rate limiter and the cutoff frequency in the apply control simulink block.

  • +
  • BREAKING Removing the get initial robot state block from the simulink library.

  • +
  • BREAKING Enhanced modular building structure for the Franka Simulink Library. Easy incorporation to larger projects and Harware Support Packages.

  • +
  • BREAKING New Matlab object oriented API expressed based on the new FrankaRobot() class. Incorporation of existing Franka MATLAB functions as methods of the new API Class.

  • +
  • BREAKING Removing all the “Panda” naming conventions. Replaced with “Franka Robot”.

  • +
  • BREAKING Franka MATLAB is now distributed as a Toolbox Matlab Add-On. No installation script needed.

  • +
  • Fixing collision threshold setting bug.

  • +
  • Oldest supported Matlab version is now the R2021a.

  • +
  • Adding the option to set the Nominal End-Effector to End-Effector frame NE_T_EE in the Simulink Block “Apply Control”.

  • +
  • Expansion of the Matlab API with the new methods gripper_state(), gripper_homing(), gripper_grasp(), gripper_move() and gripper_stop() for controlling the Franka Gripper.

  • +
  • Expansion of the Matlab API with the new method joint_trajectory_motion() for following precomputed joint trajectories.

  • +
  • Creation of the new demo pick_and_place_with_RRT.mlx showcasing a workflow approach for the new Matlab API.

  • +
+
+
+

0.3.1 (23-03-2023)

+
    +
  • Bugfix. Properly setting the collision threshold values in Simulink.

  • +
+
+
+

0.3.0 (20-09-2022)

+
    +
  • Windows 10 support (Experimental mainly due to the non-Real-Time nature of the default Windows system).

  • +
  • Project now relies on the leaner “Generic Real-Time” .tlc (grt.tlc) target framework.

  • +
  • Support for XCP communication protocol (vehicle network communication). Data inspector is now enabled!

  • +
  • Support for “Run on custom Hardware” Simulink App for controlling the “Build-deploy-execute-connect” workflow.

  • +
  • Project back-end overal simplification with modified rt_main.cpp for handling the external mode as a seperate thread.

  • +
  • BREAKING all the Franka MATLAB functions are starting with the “franka_” prefix.

  • +
  • Expansion of Matlab library with the functions franka_communication_test(), franka_joint_poses(), franka_robot_state() and franka_joint_point_to_point_motion().

  • +
  • Addition of the Simulink demo, “joint_impedance_control.slx”.

  • +
  • Fixing the bug when utilizing the Control Modes “Torque Control - X`.

  • +
+
+
+

0.2.1 (29-04-2022)

+
    +
  • Adding supoort for all versions from Matlab2019a till Matlab2021a with libfranka 0.9.0.

  • +
+
+
+

0.2.0 (31-05-2021)

+
    +
  • franka_matlab upgrade, supports Matlab2019a, Matlab2019b, Matlab2020a, Matlab2020b, Matlab2021a & libfranka 0.8.0.

  • +
+
+
+

0.1.1 (01-07-2020)

+
    +
  • Any dependences that lead to source code linking for the Simulink Franka Library during mexing removed. That fixes the memory corruption +bug when shutting down Matlab.

  • +
  • Simulink Franka Library sFunctions C++ and tlc implementations decoupled from src code necessary for automatic code gen. SRC Folder can be treated seperately as a C++ project.

  • +
+
+
+

0.1.0 (21-01-2020)

+
    +
  • Features:

    +
      +
    • Simulink Library for Franka Robot, includes the following blocks:

      +
        +
      • Franka Simulink Iterface for applying the desired control, plus additional parameters.

      • +
      • Read Initial Robot State for reading initial values during the first execution step for any desirable signal. The set of the desired signals can be set through the mask in free text form.

      • +
      • Read Robot State for reading the values of any desirable signal during execution. The set of the desired signals can be set through the mask in free text form.

      • +
      • Franka Model for reading the values of all Model parameters of the Franka Robot during execution.

      • +
      • Duration Period for reading the current step, sample time. If communication is not secured during the 1ms, the block will return the value of 2ms or 3ms etc.

      • +
      • Gripper Read State for reading the current values out of the Franka Gripper.

      • +
      +
    • +
    • franka_robot.tlc & franka_robot_shrlib.tlc custom linux targets, based on ert, that offer ext mode that is real time capable(package drop in case of main step frame loss).

    • +
    • Matlab Library(Experimental, limited support), includes the following command:

      +
        +
      • automatic_error_recovery(robot_ip). Execute through a matlab command line for getting automatically out of an error state.

      • +
      +
    • +
    • Simulink Library misc(Experimental, limited support) that includes a set of UI buttons with callback scripts with the potential to automate some of the dev. workflow.

    • +
    +
  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/getting_started.html b/franka_matlab/getting_started.html new file mode 100644 index 0000000..c8eee55 --- /dev/null +++ b/franka_matlab/getting_started.html @@ -0,0 +1,233 @@ + + + + + + + Getting started — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting started

+
+

Overview

+

A set of Simulink & Matlab examples is included within the Franka MATLAB Toolbox. Feel free to experiment, adjust +them and expand them to fit your project needs!

+

You can navigate through the examples by typing:

+
>> franka_matlab_toolbox_examples();
+
+
+
+../_images/franka_matlab_toolbox_examples.png +
+

Franka MATLAB Toolbox Examples Navigator.

+
+
+
+
+

Initialization

+

After opening, double clicking on any of the simulink models the robot settings will be loaded automatically in the +workspace, in the form of the object frs.

+
+

Hint

+

The Simulink models are delivered in R2021a version. They will convert automatically to your Matlab version +when you try to save the model.

+
+
+../_images/workspace_parameters.png +
+

The Franka Robot Settings object.

+
+
+

The robot_ip is set to 172.16.0.2. Make sure that the robot_ip, as well as all the other parameters matches your +setup for your intended purposes.

+
>> frs.robot_ip = <your robot ip string>
+
+
+

You can modify the default settings for the FrankaRobotSettings with

+
>> edit FrankaRobotSettings.m
+
+
+
+
+

Execution

+

Let’s start by selecting the Run on custom hardware App from the Apps pane in Simulink.

+
+../_images/cartesian_impedance_control_apps.png +
+

“Run on custom hardware” Simulink App.

+
+
+
+

Important

+

Before executing make sure that the brakes of the robot are disengaged, the FCI mode is activated +in Desk and that the robot is in execution mode(user button is released)!

+
+

You can then select from the Hardware tab either Monitor & Tune in case monitoring through the external mode is +desired or Build, Deploy & Start for just executing the application without monitoring.

+
+../_images/cartesian_impedance_control_hardware.png +
+

Hardware Simulink App.

+
+
+
+

Caution

+

The robot will move! Make sure that you are monitoring the situation, ready to take action if necessary!

+
+

Alternatively you can run the auto-generated executable located in the current working space manually from a terminal:

+

In case of Linux:

+
$ ./<simulink_model_name>
+
+
+

or in case of Windows:

+
> <simulink_model_name>.exe
+
+
+
+
+

Automatic error recovery

+

If the robot encounters an error state and transitions to reflex mode, +you may attempt a recovery by executing the automatic error recovery command in Matlab.

+
>> fr = FrankaRobot(<robot ip as string>);
+>> fr.automatic_error_recovery();
+
+
+

In case the command fails and the robot remains in the erroneous state try using the guiding mode to manually bring +back the robot to a valid configuration.

+
+

Hint

+

Checkout the matlab library for a set of helper +functions that can help to optimize your workflow.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/index.html b/franka_matlab/index.html new file mode 100644 index 0000000..79ff1ce --- /dev/null +++ b/franka_matlab/index.html @@ -0,0 +1,191 @@ + + + + + + + Franka MATLAB Toolbox — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Franka MATLAB Toolbox

+

Franka MATLAB Toolbox contains a set of libraries and tools for exposing the Franka Robot to the Mathworks ecosystem with the:

+
    +
  • Simulink Franka Library, a set of simulink blocks interfacing the Franka Robot through automatic C++ code gen with Simulink Coder, for rapid-prototyping of robot control algorithms.

  • +
+
+../_images/simulink_library_browser.png +
+

Simulink Library for rapid-prototyping of controllers for the Franka robot.

+
+
+
    +
  • Matlab Franka Library, a set of matlab functions for reading the current robot state and applying some simple open loop motion commands.

  • +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/installation.html b/franka_matlab/installation.html new file mode 100644 index 0000000..16215b8 --- /dev/null +++ b/franka_matlab/installation.html @@ -0,0 +1,221 @@ + + + + + + + Installation — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation

+
+

Hint

+

The Franka MATLAB Toolbox is based on the Franka Control Interface (FCI) and +the libfranka C++ interface. +All the same +system and network requirements do therefore apply.

+
+
+

Linux System Setup

+
+

Important

+

For Linux system we higly recommend installing the matlab-support package:

+
sudo apt install matlab-support
+
+
+
+

Make sure that the following dependencies are installed:

+
+
sudo apt install build-essential cmake git libpoco-dev libeigen3-dev
+
+
+
+

You can either let the Franka MATLAB Toolbox auto-install the libfranka locally or you can proceed with +a system-wide libfranka.*.deb installation. We recommend the former.

+
+

Important

+

Make sure that the Real-Time Kernel is properly installed as described in the +libfranka documentation.

+
+
+
+

Windows System Setup

+
+

Warning

+

Support for Windows is still experimental. Issues could arise due to lack of hard Real-Time scheduling capabilities of the generic Windows distributions.

+
+

Please make sure first to install the Visual Studio 2017 Community Edition (English Version) on a Windows 10 PC.

+

Additionally the following software components must be installed for windows:

+
    +
  • git

  • +
  • cmake

  • +
  • vcpkg

  • +
  • ninja

  • +
+

Make sure that the vcpkg & ninja paths are exposed through the PATH environment variable. +You can modify the PATH environment variable in Windows 10:

+
    +
  1. Open the Start Search, type in “env”, and choose “Edit the system environment variables”.

  2. +
  3. Click the “Environment Variables” button.

  4. +
  5. Under the “System Variables” section, find the row with “Path” in the first column, and click edit.

  6. +
  7. Add the vcpkg & ninja paths, like e.g C:\Users\{user name}\vcpkg & C:\Users\{user name}\ninja

  8. +
  9. Verify by opening a terminal and evaluating the vcpkg and ninja commands.

  10. +
+

You can then install the 64bit versions of eigen3 and poco packages:

+
vcpkg install eigen3:x64-windows
+vcpkg install poco[netssl]:x64-windows
+
+
+
+
+

Franka MATLAB Toolbox Add-On Installation & License Management

+

For installing the Franka MATLAB Toolbox either drag-and-drop the franka_matlab.mltbx +to the current Matlab Command Window or you can use the Matlab Add-On manager.

+

After this process is complete simply follow the instructions in the Getting Started guided +which should have been opened after the Franka MATLAB Toolbox Add-on installation.

+

In short you will need to generate a unique identifier for you PC by executing:

+
franka_matlab_toolbox_uid_gen();
+
+
+

Please then send this unique identifier to Franka Robotics for receiving a License Number for +the Franka MATLAB Toolbox.

+

You can then proceed with the final installation step, by executing:

+
franka_matlab_toolbox_install('franka matlab toolbox license number as a string',['fr3' or 'fer']);
+
+
+

That’s it the Franka MATLAB Toolbox should be ready.

+

Get a glimpse of what the capabilities of the Toolbox are by navigating through the examples provided with the Toolbox:

+
franka_matlab_toolbox_examples();
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/matlab_library.html b/franka_matlab/matlab_library.html new file mode 100644 index 0000000..1be039f --- /dev/null +++ b/franka_matlab/matlab_library.html @@ -0,0 +1,247 @@ + + + + + + + Matlab Library — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Matlab Library

+
+

Matlab Demo Pick & Place with RRT

+

For familiarization with the Matlab API for the Franka Robot the pick_and_place_with_RRT.mlx demo is a good starting point.

+
+../_images/matlab_pick_and_place_with_RRT_demo.png +
+

Live Matlab Script and Figure of the pick-and-place demo.

+
+
+
+

Hint

+

The pick-and-place demo requires the Matlab Robotics Toolbox with Matlab \(\geq\) R2021b.

+
+
+
+

FrankaRobot Class

+
>> fr = FrankaRobot(<robot ip string>);
+
+
+
+

Automatic Error Recovery

+
>> fr.automatic_error_recovery();
+
+
+

Will attempt an automatic error recovery .

+
+
+

Get Joint Poses

+
>> jp = fr.joint_poses();
+
+
+

Will return a 7 element cell array with the current robot joint poses.

+
+
+

Get Robot State

+
>> rs = fr.robot_state();
+
+
+

Will return a struct will the current robot state.

+
+
+

Joint Point to Point Motion

+
>> fr.joint_point_to_point_motion(<7 element double array with target configuration>, <0 to 1 scalar speed factor>);
+
+
+

Will move the robot into a desired joint configuration.

+
+
+

Joint Trajectory Motion

+
>> fr.joint_trajectory_motion(<7xn double array with desired joint trajectory>);
+
+
+

Will move the robot based on the given desired joint trajectory.

+
+

Warning

+

Make sure that the current configuration of the robot maches the initial trajectory element q(1:7,1) that is passed in the function! Additionally make sure that +the given trajectory is sufficiently smooth and continuous.

+
+
+
+

Gripper State

+
>> gs = fr.gripper_state();
+
+
+

Will return a struct with the current gripper state.

+
+
+

Gripper Homing

+
>> fr.gripper_homing();
+
+
+

Will perform a homing operation to the gripper and will return 1 if succesful.

+
+
+

Gripper Grasp

+
>> fr.ripper_grasp(width, speed, force, epsilon_inner, epsilon_outer);
+
+
+

Will attempt a grasp and will return 1 if the object is grasped, 0 otherwise.

+
+
+

Gripper Move

+
>> fr.gripper_move(width,speed);
+
+
+

Will move the gripper to a desired width position. Will return 1 if succesful, 0 otherwise.

+
+
+

Gripper Stop

+
>> fr.gripper_stop();
+
+
+

Will stop the current gripper operation. Will return 1 if succesful, 0 otherwise.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/matlab_toolbox_dependencies.html b/franka_matlab/matlab_toolbox_dependencies.html new file mode 100644 index 0000000..fd669aa --- /dev/null +++ b/franka_matlab/matlab_toolbox_dependencies.html @@ -0,0 +1,147 @@ + + + + + + + Matlab toolbox dependencies — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Matlab toolbox dependencies

+

The following Mathworks products are required:

+ +

Some of demos provided with the franka_matlab could potentially need either of the following toolboxes:

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/simulink_library.html b/franka_matlab/simulink_library.html new file mode 100644 index 0000000..725af82 --- /dev/null +++ b/franka_matlab/simulink_library.html @@ -0,0 +1,239 @@ + + + + + + + Simulink Library — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_matlab/troubleshooting.html b/franka_matlab/troubleshooting.html new file mode 100644 index 0000000..b4ec528 --- /dev/null +++ b/franka_matlab/troubleshooting.html @@ -0,0 +1,211 @@ + + + + + + + Troubleshooting — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Troubleshooting

+
+

Hint

+

Checkout the Franka Community and the +franka_matlab category for relevant posts or for creating new ones!

+
+
+

Hint

+

Before proceeding with the Franka MATLAB Toolbox, it would be a good practice to execute a couple of the libfranka examples, under the build/examples folder, in order to ensure that the libfranka +installation has been succesful and that the system can operate under the Real-Time control constraints.

+
+
+

control_modes.h: No such file or directory error.

+
+../_images/simulink_model_apply_control_only_build_error.png +
+

The build error message in simulink when only the “apply control is present”.

+
+
+

This is a known current limitation of the system, as the build process will fail if only +the “apply control” block is present in a simulink model.

+
+../_images/simulink_model_apply_control_only.png +
+

Example of a Simulink model with only “apply control”. The build will fail.

+
+
+

For fixing the issue just include any other block from the Franka Simulink Library, e.g +with the terminal if it will be left unused.

+
+../_images/simulink_model_apply_control_only_fix.png +
+

Fixing the “control_modes.h: No such file or directory error.” by including any other +block from the Franka Simulink Library.

+
+
+
+
+

libfranka reference

+
+

Hint

+

Same error messages and advised troubleshooting applies as libfranka.

+
+
+
+

Issues with the graphics driver in Linux

+

NVIDIA’s graphics driver’s are not officially supported in Linux with Real-Time Kernel. This could cause issues in graphics renderings in Matlab +and Simulink, e.g with figures and scopes respectively. We would then recommend starting matlab with the -softwareopengl for avoiding these issues:

+
$ matlab -softwareopengl
+
+
+
+
+

Issues with libstdc++.so and other system dynamic libraries

+

Make sure that you have installed the matlab-support package for your system, in order for Matlab to reference the system dynamic libraries +instead of the precompiled ones that it ships with:

+
sudo apt install matlab-support
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_ros.html b/franka_ros.html new file mode 100644 index 0000000..c4b30c0 --- /dev/null +++ b/franka_ros.html @@ -0,0 +1,1537 @@ + + + + + + + franka_ros — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

franka_ros

+
+

Note

+

franka_ros is not supported on Windows.

+
+

Before continuing with this chapter, please install or compile franka_ros.

+
+_images/ros-architecture.png +
+

Schematic overview of the franka_ros packages.

+
+
+

The franka_ros metapackage integrates libfranka into ROS and ROS control. +Here, we introduce its packages and +we also give a short how-to for writing controllers.

+

All parameters passed to launch files in this section come with default values, so they +can be omitted if using the default network addresses and ROS namespaces. +Make sure the source command was called with the setup script from your workspace:

+
source /path/to/catkin_ws/devel/setup.sh
+
+
+
+

franka_description

+

This package contains the description of our robots and end effectors in terms of kinematics, joint +limits, visual surfaces and collision space. The collision space is a simplified version of the +visual description used to improve performance of collision checks. The descriptions are based on +the URDF format according to the URDF XML documentation .

+

In case you want to simulate the Panda robot, you can pass a gazebo argument to the XACRO file. +Then the URDF will contain estimated inertias values, i.e. link masses and inertia tensors based +on the Dynamic Identification of the Franka Robotics Panda Robot With Retrieval of Feasible Parameters Using +Penalty-Based Optimization.

+
xacro $(rospack find franka_description)/robots/panda/panda.urdf.xacro gazebo:=true
+
+
+

The same works for FR3:

+
xacro $(rospack find franka_description)/robots/fr3/fr3.urdf.xacro gazebo:=true
+
+
+
+

Collisions Volumes

+

The URDF defines two types of collision types:

+
+
    +
  • Fine: These collision volumes are made from convex meshes which are approximated and +drastically simplified versions of the visual meshes (.dae) of each link. The fine volumes +should be used for simulating robot collisions in Gazebo

  • +
  • Coarse: These collision volumes are simply capsules (a cylinder with two semispherical +end caps) attached to each link and inflated by a certain safety distance. These volumes +are more efficient to compute and are used internally in the robot for self-collision +avoidance. Use these geometries when you are planning motions e.g. with MoveIt.

  • +
+
+ +++++ + + + + + + + + + + +

Visual

Collision (Fine)

Collision (Coarse)

_images/visual.png +_images/collision-fine.png +_images/collision-coarse.png +
+

To distinguish between the two types of collision models artificial links are inserted in the URDF +with an *_sc suffix (for self-collision):

+
+
panda_link0
+
├─ panda_link0_sc
+
└─ panda_link1
+
+
├─ panda_link1_sc
+
└─ panda_link2
+
+
├─ panda_link2_sc
+
└─ panda_link3
+
+
├─ panda_link3_sc
+
└─ panda_link4
+
+
├─ panda_link4_sc
+
└─ panda_link5
+
+
├─ panda_link5_sc
+
└─ panda_link6
+
+
├─ panda_link6_sc
+
└─ panda_link7
+
+
├─ panda_link7_sc
+
└─ panda_link8
+
+
├─ panda_link8_sc
+
└─ panda_hand
+
+
├─ panda_leftfinger
+
├─ panda_rightfinger
+
├─ panda_hand_sc
+
└─ panda_hand_tcp
+
+
+
+
+
+
+
+
+
+
+

You can control which collision model is loaded into your URDF via the gazebo XACRO argument:

+
+
    +
  • xacro ... panda.urdf.xacro gazebo:=false: This will use both the fine and coarse collision model. +This is also the default if you omit the arg entirely. Use this when you want to use MoveIt

  • +
  • xacro ... panda.urdf.xacro gazebo:=true: This will use only the fine collision model. Use +this when you want a simulatable URDF i.e. for Gazebo. When using the coarse collision model the robot +will of course be in constant collision with the capsules of the next link.

  • +
+
+
+
+
+

franka_gripper

+

This package implements the franka_gripper_node for interfacing a gripper from ROS. +The node publishes the state of the gripper and offers the following actions servers:

+
+
    +
  • franka_gripper::MoveAction(width, speed): moves to a target width with the defined +speed.

  • +
  • franka_gripper::GraspAction(width, epsilon_inner, epsilon_outer, speed, force): tries to +grasp at the desired width with a desired force while closing with the given speed. The +operation is successful if the distance \(d\) between the gripper fingers is: +\(\text{width} - \epsilon_\text{inner} < d < \text{width} + \epsilon_\text{outer}\).

  • +
  • franka_gripper::HomingAction(): homes the gripper and updates the maximum width given the +mounted fingers.

  • +
  • franka_gripper::StopAction(): aborts a running action. This can be used to stop applying +forces after grasping.

  • +
  • control_msgs::GripperCommandAction(width, max_effort): A standard gripper action +recognized by MoveIt!. If the argument max_effort is greater than zero, the gripper +will try to grasp an object of the desired width. On the other hand, if max_effort is +zero (\(\text{max_effort} < 1^{-4}\)), the gripper will move to the desired width.

  • +
+
+
+

Note

+

Use the argument max_effort only when grasping an object, otherwise, the gripper will +close ignoring the width argument.

+
+
+
+

You can launch the franka_gripper_node with:

+
roslaunch franka_gripper franka_gripper.launch robot_ip:=<fci-ip>
+
+
+
+

Hint

+

Starting with franka_ros 0.6.0, specifying load_gripper:=true for +roslaunch franka_control franka_control.launch will start a franka_gripper_node as well.

+
+
+
+

franka_hw

+

This package contains the hardware abstraction of the robot for the ROS control framework +based on the libfranka API. The hardware class franka_hw::FrankaHW is implemented in this +package offering the following interfaces to controllers:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Interface

Function

hardware_interface::JointStateInterface

Reads joint states.

hardware_interface::VelocityJointInterface

Commands joint velocities and reads joint +states.

hardware_interface::PositionJointInterface

Commands joint positions and reads joint +states.

hardware_interface::EffortJointInterface

Commands joint-level torques and reads +joint states.

franka_hw::FrankaStateInterface

Reads the full robot state.

franka_hw::FrankaPoseCartesianInterface

Commands Cartesian poses and reads the full +robot state.

franka_hw::FrankaVelocityCartesianInterface

Commands Cartesian velocities and reads the +full robot state.

franka_hw::FrankaModelInterface

Reads the dynamic and kinematic model of the +robot.

+

To use ROS control interfaces, you have to retrieve resource handles by name:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Interface

Resource handle name

hardware_interface::JointStateInterface

“<arm_id>_joint1” to “<arm_id>_joint7”

hardware_interface::VelocityJointInterface

“<arm_id>_joint1” to “<arm_id>_joint7”

hardware_interface::PositionJointInterface

“<arm_id>_joint1” to “<arm_id>_joint7”

hardware_interface::EffortJointInterface

“<arm_id>_joint1” to “<arm_id>_joint7”

franka_hw::FrankaStateInterface

“<arm_id>_robot”

franka_hw::FrankaPoseCartesianInterface

“<arm_id>_robot”

franka_hw::FrankaVelocityCartesianInterface

“<arm_id>_robot”

franka_hw::FrankaModelInterface

“<arm_id>_robot”

+
+

Hint

+

By default, <arm_id> is set to “panda”.

+
+

The franka_hw::FrankaHW class also implements the starting, stopping and switching of +controllers.

+

The FrankaHW class also serves as base class for FrankaCombinableHW, a hardware class that +can be combined with others to control multiple robots from a single controller. The combination of +an arbitrary number of Panda robots (number configured by parameters) based on FrankaCombinableHW +for the ROS control framework https://github.com/ros-controls/ros_control is implemented +in FrankaCombinedHW. The key-difference between FrankaHW and FrankaCombinedHW is +that the latter supports torque control only.

+
+

Important

+

The FrankaCombinableHW class is available from version 0.7.0 and allows torque/effort control only.

+
+

The ROS parameter server is used to determine at runtime which robots are loaded in the combined +class. For an example on how to configure the FrankaCombinedHW in the according hardware node, +see franka_control.

+
+

Note

+

The approach of FrankaHW is optimal for controlling single robots. Thus we recommend using +the FrankaCombinableHW/FrankaCombinedHW classes only for controlling multiple robots.

+
+

The interfaces offered by the FrankaCombinableHW/FrankaCombinedHW classes are the following:

+ ++++ + + + + + + + + + + + + + + + + + + + +

Interface

Function

hardware_interface::EffortJointInterface

Commands joint-level torques and reads +joint states.

hardware_interface::JointStateInterface

Reads joint states.

franka_hw::FrankaStateInterface

Reads the full robot state.

franka_hw::FrankaModelInterface

Reads the dynamic and kinematic model of the +robot.

+

The only admissible command interface claim is the EffortJointInterface which can be combined +with any set of read-only-interfaces (FrankaModelInterface, JointStateInterface, +FrankaStateInterface). The resource handles offered by all interfaces are claimed by name and +follow the same naming conventions as described for FrankaHW. Every instance of +FrankaCombinableHW offers the complete set of service and action interfaces +(see franka_control).

+
+

Note

+

The FrankaCombinedHW class offers an additional action server in the control node namespace +to recover all robots. If a reflex or error occurs on any of the robots, the control loop of all +robots stops until they are recovered.

+
+
+

Important

+

FrankaHW makes use of the ROS joint_limits_interface +to enforce position, velocity and effort safety limits. +The utilized interfaces are listed below:

+
+
    +
  • joint_limits_interface::PositionJointSoftLimitsInterface

  • +
  • joint_limits_interface::VelocityJointSoftLimitsInterface

  • +
  • joint_limits_interface::EffortJointSoftLimitsInterface

  • +
+
+

Approaching the limits will result in the (unannounced) modification of the commands.

+
+
+
+

franka_control

+

The ROS nodes franka_control_node and franka_combined_control_node are hardware nodes +for ROS control that use according hardware classes from franka_hw. They provide a variety +of ROS services to expose the full libfranka API in the ROS ecosystem. The following services +are provided:

+
+
    +
  • franka_msgs::SetJointImpedance specifies joint stiffness for the internal controller +(damping is automatically derived from the stiffness).

  • +
  • franka_msgs::SetCartesianImpedance specifies Cartesian stiffness for the internal +controller (damping is automatically derived from the stiffness).

  • +
  • franka_msgs::SetEEFrame specifies the transformation from <arm_id>_EE (end effector) to +<arm_id>_NE (nominal end effector) frame. The transformation from flange to end effector frame +is split into two transformations: <arm_id>_EE to <arm_id>_NE frame and <arm_id>_NE to +<arm_id>_link8 frame. The transformation from <arm_id>_NE to <arm_id>_link8 frame can only be +set through the administrator’s interface.

  • +
  • franka_msgs::SetKFrame specifies the transformation from <arm_id>_K to <arm_id>_EE frame.

  • +
  • franka_msgs::SetForceTorqueCollisionBehavior sets thresholds for external Cartesian +wrenches to configure the collision reflex.

  • +
  • franka_msgs::SetFullCollisionBehavior sets thresholds for external forces on Cartesian +and joint level to configure the collision reflex.

  • +
  • franka_msgs::SetLoad sets an external load to compensate (e.g. of a grasped object).

  • +
  • std_srvs::Trigger services allow to connect and disconnect your hardware node +(available from 0.8.0). When no active (commanding) controller is running, you can disconnect +the hardware node, freeing the respective robots for non-fci applications like e.g. Desk-based +operations. Once you want to resume fci operations you can call connect and start your +ros_control based controllers again.

  • +
+
+
+

Important

+

The <arm_id>_EE frame denotes the part of the +configurable end effector frame which can be adjusted during run time through franka_ros. The +<arm_id>_K frame marks the center of the internal +Cartesian impedance. It also serves as a reference frame for external wrenches. Neither the +<arm_id>_EE nor the <arm_id>_K are contained in the URDF as they can be changed at run time. +By default, <arm_id> is set to “panda”.

+
+_images/frames.svg
+

Overview of the end-effector frames.

+
+
+
+

To recover from errors and reflexes when the robot is in reflex mode, you can utilize the +franka_msgs::ErrorRecoveryAction. This can be achieved through either an action client or by publishing on the +action goal topic.

+
rostopic pub -1 /franka_control/error_recovery/goal franka_msgs/ErrorRecoveryActionGoal "{}"
+
+
+

After recovery, the franka_control_node restarts the controllers that were running. That is +possible as the node does not die when robot reflexes are triggered or when errors have occurred. +All of these functionalities are provided by the franka_control_node which can be launched +with the following command:

+
roslaunch franka_control franka_control.launch \
+robot_ip:=<fci-ip> # mandatory \
+load_gripper:=<true|false> # default: true \
+robot:=<panda|fr3> # default: panda
+
+
+

Besides loading the franka_control_node, the launch file also starts a +franka_control::FrankaStateController for reading and publishing the robot states, including +external wrenches, configurable transforms and the joint states required for visualization with +rviz. For visualization purposes, a robot_state_publisher is started.

+

This package also implements the franka_combined_control_node, a hardware node for ros_control based +on the franka_hw::FrankaCombinedHW class. The set of robots loaded are configured via the ROS parameter +server. These parameters have to be in the hardware node’s namespace (see franka_combined_control_node.yaml +as a reference) and look like this:

+
robot_hardware:
+  - panda_1
+  - panda_2
+  # (...)
+
+panda_1:
+  type: franka_hw/FrankaCombinableHW
+  arm_id: panda_1
+  joint_names:
+    - panda_1_joint1
+    - panda_1_joint2
+    - panda_1_joint3
+    - panda_1_joint4
+    - panda_1_joint5
+    - panda_1_joint6
+    - panda_1_joint7
+  # Configure the threshold angle for printing joint limit warnings.
+  joint_limit_warning_threshold: 0.1 # [rad]
+  # Activate rate limiter? [true|false]
+  rate_limiting: true
+  # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate.
+  cutoff_frequency: 1000
+  # Internal controller for motion generators [joint_impedance|cartesian_impedance]
+  internal_controller: joint_impedance
+  # Configure the initial defaults for the collision behavior reflexes.
+  collision_config:
+    lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+
+panda_2:
+  type: franka_hw/FrankaCombinableHW
+  arm_id: panda_2
+  joint_names:
+    - panda_2_joint1
+    - panda_2_joint2
+    - panda_2_joint3
+    - panda_2_joint4
+    - panda_2_joint5
+    - panda_2_joint6
+    - panda_2_joint7
+  # Configure the threshold angle for printing joint limit warnings.
+  joint_limit_warning_threshold: 0.1 # [rad]
+  # Activate rate limiter? [true|false]
+  rate_limiting: true
+  # Cutoff frequency of the low-pass filter. Set to >= 1000 to deactivate.
+  cutoff_frequency: 1000
+  # Internal controller for motion generators [joint_impedance|cartesian_impedance]
+  internal_controller: joint_impedance
+  # Configure the initial defaults for the collision behavior reflexes.
+  collision_config:
+    lower_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    upper_torque_thresholds_acceleration: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    lower_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    upper_torque_thresholds_nominal: [20.0, 20.0, 18.0, 18.0, 16.0, 14.0, 12.0]  # [Nm]
+    lower_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    upper_force_thresholds_acceleration: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    lower_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+    upper_force_thresholds_nominal: [20.0, 20.0, 20.0, 25.0, 25.0, 25.0]  # [N, N, N, Nm, Nm, Nm]
+
+# (+ more robots ...)
+
+
+
+

Note

+

Be sure to choose unique and consistent arm_id parameters. The IDs must match the prefixes +in the joint names and should be according to the robot description loaded to the control +node’s namespace.

+
+

For more information on the parameter based loading of hardware classes, please refer to the +official documentation of combined_robot_hw::CombinedRobotHW from +https://github.com/ros-controls/ros_control.

+

A second important parameter file +(see franka_ros/franka_control/config/default_combined_controllers.yaml as a reference) configures +a set of default controllers that can be started with the hardware node. The controllers have to match +the launched hardware. The provided default parameterization (here for 2 robots) looks like:

+
panda_1_state_controller:
+  type: franka_control/FrankaStateController
+  arm_id: panda_1
+  joint_names:
+    - panda_1_joint1
+    - panda_1_joint2
+    - panda_1_joint3
+    - panda_1_joint4
+    - panda_1_joint5
+    - panda_1_joint6
+    - panda_1_joint7
+  publish_rate: 30  # [Hz]
+
+panda_2_state_controller:
+  type: franka_control/FrankaStateController
+  arm_id: panda_2
+  joint_names:
+    - panda_2_joint1
+    - panda_2_joint2
+    - panda_2_joint3
+    - panda_2_joint4
+    - panda_2_joint5
+    - panda_2_joint6
+    - panda_2_joint7
+  publish_rate: 30  # [Hz]
+
+# (+ more controllers ...)
+
+
+

We provide a launch file to run the franka_combined_control_node with user specified configuration +files for hardware and controllers which default to a configuration with 2 robots. Launch it with:

+
roslaunch franka_control franka_combined_control.launch \
+    robot_ips:=<your_robot_ips_as_a_map>                 # mandatory
+    robot:=<path_to_your_robot_description> \
+    args:=<xacro_args_passed_to_the_robot_description> \ # if needed
+    robot_id:=<name_of_your_multi_robot_setup> \
+    hw_config_file:=<path_to_your_hw_config_file>\       # includes the robot ips!
+    controllers_file:=<path_to_your_default_controller_parameterization>\
+    controllers_to_start:=<list_of_default_controllers_to_start>\
+    joint_states_source_list:=<list_of_sources_to_fuse_a_complete_joint_states_topic>
+
+
+

This launch file can be parameterized to run an arbitrary number of robots. +To do so just write your own configuration files in the style of +franka_control/config/franka_combined_control_node.yaml and +franka_ros/franka_control/config/default_combined_controllers.yaml.

+
+

Important

+

Be sure to pass the correct IPs of your robots to franka_combined_control.launch as a map. +This looks like: {<arm_id_1>/robot_ip: <my_ip_1>, <arm_id_2>/robot_ip: <my_ip_2>, …}

+
+
+
+

franka_visualization

+

This package contains publishers that connect to a robot and publish the robot and +gripper joint states for visualization in RViz. To run this package launch:

+
roslaunch franka_visualization franka_visualization.launch robot_ip:=<fci-ip> \
+  load_gripper:=<true|false> robot:=<panda|fr3>
+
+
+

This is purely for visualization - no commands are sent to the robot. It can be useful to check the +connection to the robot.

+
+

Important

+

Only one instance of a franka::Robot can connect to the robot. This means, that for example +the franka_joint_state_publisher cannot run in parallel to the franka_control_node. +This also implies that you cannot execute the visualization example alongside a separate +program running a controller.

+
+
+
+

franka_example_controllers

+

In this package a set of example controllers for controlling the robot via ROS are implemented. +The controllers depict the variety of interfaces offered by the franka_hw::FrankaHW class and +the according usage. Each example comes with a separate stand-alone launch file that starts the +controller on the robot and visualizes it.

+

To launch the joint impedance example, execute the following command:

+
roslaunch franka_example_controllers joint_impedance_example_controller.launch \
+  robot_ip:=<fci-ip> load_gripper:=<true|false> robot:=<panda|fr3>
+
+
+

Other single Panda examples are started in the same way.

+

The dual_arm_cartesian_impedance_example_controller showcases the control of two Panda robots +based on FrankaCombinedHW using one realtime controller for fulfilling Cartesian tasks with +an impedance-based control approach. The example controller can be launched with

+
roslaunch franka_example_controllers \
+    dual_arm_cartesian_impedance_example_controller.launch \
+    robot_id:=<name_of_the_2_arm_setup> \
+    robot_ips:=<your_robot_ips_as_a_map> \
+    rviz:=<true/false> rqt:=<true/false>
+
+
+

The example assumes a robot configuration according to dual_panda_example.urdf.xacro where two +Pandas are mounted at 1 meter distance on top of a box. Feel free to replace this robot description +with one that matches your setup. +The option rviz allows to choose whether a visualization should be launched. With rqt the user +can choose to launch an rqt-gui which allows an online adaption of the rendered end-effector +impedances at runtime via dynamic reconfigure.

+
+
+

franka_gazebo

+

This package allows you to simulate our robot in Gazebo. This is possible +because Gazebo is able to integrate into the ROS control framework with the +gazebo_ros package.

+
+

Important

+

This package is available from 0.8.0

+
+
+

Pick & Place Example

+

Let’s dive in and simulate transporting a stone from A to B. Run the following command to start Gazebo with a Panda +and an example world.

+
roslaunch franka_gazebo panda.launch x:=-0.5 \
+    world:=$(rospack find franka_gazebo)/world/stone.sdf \
+    controller:=cartesian_impedance_example_controller \
+    rviz:=true
+
+
+

This will bring up the Gazebo GUI where you see the environment with the stone and RViz with which you can control +the end-effector pose of the robot.

+
+_images/franka-gazebo-example.png +
+

Gazebo GUI (left) and RViz (right) of the pick and place example

+
+
+

To open the gripper, simply send a goal to the move action, similar to how the real franka_gripper +works. Let’s move the gripper to a width of \(8\:cm\) between the fingers with \(10\:\frac{cm}{s}\):

+
rostopic pub --once /franka_gripper/move/goal franka_gripper/MoveActionGoal "goal: { width: 0.08, speed: 0.1 }"
+
+
+

Since we launched our robot with the Cartesian Impedance controller from +franka_example_controllers, we can move the end-effector around, just like in reality, +with the interactive marker gizmo in RViz. Move the robot such that the white stone is between the fingers of the +gripper ready to be picked up.

+
+

Note

+

If the robot moves strangely with the elbow, this is because the default nullspace stiffness of the cartesian +impedance example controller is set to low. Launch Dynamic Reconfigure +and adjust panda > cartesian_impedance_example_controller > nullspace_stiffness if necessary.

+
+

To pick up the object, we use the grasp action this time, since we want to excerpt a force after +the grasp to not drop the object. The stone is around \(3\:cm\) wide and \(50\:g\) heavy. +Let’s grasp it with \(5\:N\):

+
rostopic pub --once /franka_gripper/grasp/goal \
+             franka_gripper/GraspActionGoal \
+             "goal: { width: 0.03, epsilon:{ inner: 0.005, outer: 0.005 }, speed: 0.1, force: 5.0}"
+
+
+
+

Note

+

In top menu of Gazebo go to View > Contacts to visualize contact points and forces

+
+

If the grasp succeeded, the fingers will now hold the stone in place. If not, probably the goal tolerances (inner +and outer epsilon) were too small and the action failed. Now move the object gently over to the red dropoff area.

+
+_images/franka-gazebo-example-grasp.png +
+

Transport the stone from blue to red

+
+
+

After you placed it gently on the red pad, stop the grasp with the stop action from the gripper:

+
rostopic pub --once /franka_gripper/stop/goal franka_gripper/StopActionGoal {}
+
+
+

Note that the contact forces disappear now, since no force is applied anymore. Alternatively you can also use +the move action.

+
+
+

Customization

+

The launch file from franka_gazebo takes a lot of arguments with which you can customize the behavior +of the simulation. For example to spawn two pandas in one simulation you can use the following:

+
<?xml version="1.0"?>
+<launch>
+
+  <include file="$(find gazebo_ros)/launch/empty_world.launch" >
+    <!-- Start paused, simulation will be started, when Pandas were loaded -->
+    <arg name="paused" value="true"/>
+    <arg name="use_sim_time" value="true"/>
+  </include>
+
+  <group ns="panda_1">
+    <include file="$(find franka_gazebo)/launch/panda.launch">
+      <arg name="arm_id"     value="panda_1" />
+      <arg name="y"          value="-0.5" />
+      <arg name="controller" value="cartesian_impedance_example_controller" />
+      <arg name="rviz"       value="false" />
+      <arg name="gazebo"     value="false" />
+      <arg name="paused"     value="true" />
+    </include>
+  </group>
+
+  <group ns="panda_2">
+    <include file="$(find franka_gazebo)/launch/panda.launch">
+      <arg name="arm_id"     value="panda_2" />
+      <arg name="y"          value="0.5" />
+      <arg name="controller" value="force_example_controller" />
+      <arg name="rviz"       value="false" />
+      <arg name="gazebo"     value="false" />
+      <arg name="paused"     value="false" />
+    </include>
+  </group>
+
+</launch>
+
+
+
+

Note

+

To see which arguments are supported use: roslaunch franka_gazebo panda.launch --ros-args

+
+
+
+

FrankaHWSim

+

By default Gazebo ROS can only simulate joints with “standard” hardware interfaces like Joint State Interfaces +and Joint Command Interfaces. However our robot is quite different from this architecture! Next to +these joint-specific interfaces it also supports robot-specific interfaces like the FrankaModelInterface (see +franka_hw). Naturally gazebo does not understand these custom hardware interfaces by default. +This is where the FrankaHWSim plugin comes in.

+

To make your robot capable of supporting Franka interfaces, simply declare a custom robotSimType in your URDF:

+
<gazebo>
+  <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
+    <robotNamespace>${arm_id}</robotNamespace>
+    <controlPeriod>0.001</controlPeriod>
+    <robotSimType>franka_gazebo/FrankaHWSim</robotSimType>
+  </plugin>
+  <self_collide>true</self_collide>
+</gazebo>
+
+
+

When you spawn this robot with the model spawner this plugin +will be loaded into the gazebo node. It will scan your URDF and try to find supported hardware interfaces. Up to now +only some of the interfaces provided by franka_hw are supported:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Interface

Function

hardware_interface::JointStateInterface

Reads joint states.

hardware_interface::EffortJointInterface

Commands joint-level torques and reads +joint states.

hardware_interface::VelocityJointInterface

Commands joint velocities and reads joint +states.

hardware_interface::PositionJointInterface

Commands joint positions and reads joint +states.

franka_hw::FrankaStateInterface

Reads the full robot state.

franka_hw::FrankaModelInterface

Reads the dynamic and kinematic model of the +robot.

franka_hw::FrankaPoseCartesianInterface

Commands Cartesian poses and reads the full +robot state.

franka_hw::FrankaVelocityCartesianInterface

Commands Cartesian velocities and reads the +full robot state.

+
+

Important

+

This implies that you can only simulate controllers, that claim these supported interfaces and none other! +For example the Cartesian Impedance Example Controller can be simulated, because it only requires the +EffortJoint-, FrankaState- and FrankaModelInterface. However the Joint Impedance Example Controller +can’t be simulated, because it requires the FrankaPoseCartesianInterface which is not supported yet.

+
+

Next to the realtime hardware interfaces the FrankaHWSim plugin supports some of the non-realtime commands +that franka_control supports:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Service / Type

Explanation

set_joint_impedance / +SetJointImpedance

Gazebo does not simulate an internal impedance +controller, but sets commanded torques directly

set_cartesian_impedance / +SetCartesianImpedance

Gazebo does not simulate an internal impedance +controller, but sets commanded torques directly

set_EE_frame / +SetEEFrame

Sets the \({}^{\mathrm{NE}}\mathbf{T}_{\mathrm{EE}}\) +i.e. the homogenous transformation from nominal end-effector +to end-effector. You can also initialize this by setting the +ROS parameter /<arm_id>/NE_T_EE. Normally you would set +\({}^{\mathrm{F}}\mathbf{T}_{\mathrm{NE}}\) in Desk, but +in franka_gazebo it’s assumed as identity if no gripper +was specified or defines a rotation around Z by \(45\:°\) +and an offset by \(10.34\:cm\) (same as Desk for the +hand). You can always overwrite this value by setting the ROS +parameter /<arm_id>/NE_T_EE manually.

set_K_frame / +SetKFrame

Sets the \({}^{\mathrm{EE}}\mathbf{T}_{\mathrm{K}}\) i.e. +the homogenous transformation from end-effector to stiffness +frame.

set_force_torque_collision_behavior / +SetForceTorqueCollisionBehavior

Sets thresholds above which external wrenches are treated as +contacts and collisions.

set_full_collision_behavior / +SetFullCollisionBehavior

Not yet implemented

set_load / +SetLoad

Sets an external load to compensate its gravity for, e.g. of +a grasped object. You can also initialize this by setting +the ROS parameters /<arm_id>/{m_load,I_load,F_x_load} +for mass, inertia tensor and center of mass for the load, +respectively.

set_user_stop / +std_srvs::SetBool +(since 0.9.1)

This is a special service only available in franka_gazebo +to simulate the user stop. Pressing the user stop (a.k.a +publishing a true via this service) will disconnect +all command signals from ROS controllers to be fed to the +joints. To connect them again call the error_recovery +action.

+
+
+

FrankaGripperSim

+

This plugin simulates the franka_gripper_node in Gazebo. This is done as a ROS controller for +the two finger joints with a position & force controller. It offers the same five actions like the real gripper node:

+
    +
  • /<arm_id>/franka_gripper/homing

  • +
  • /<arm_id>/franka_gripper/stop

  • +
  • /<arm_id>/franka_gripper/move

  • +
  • /<arm_id>/franka_gripper/grasp

  • +
  • /<arm_id>/franka_gripper/gripper_action

  • +
+
+

Important

+

The grasp action has a bug, that it will not succeed nor abort if the target width +lets the fingers open. This is because of missing the joint limits interface which +lets the finger oscillate at their limits. For now only use the grasp action to close +the fingers!

+
+

It is assumed that the URDF contains two finger joints which can be force controlled, i.e. have a corresponding +EffortJointInterface transmission declared. This controller expects the following parameters in its namespace:

+
    +
  • type (string, required): Should be franka_gazebo/FrankaGripperSim

  • +
  • arm_id (string, required): The arm id of the panda, to infer the name of the finger joints

  • +
  • finger1/gains/p (double, required): The proportional gain for the position-force controller of the first finger

  • +
  • finger1/gains/i (double, default: 0): The integral gain for the position-force controller of the first finger

  • +
  • finger1/gains/d (double, default: 0): The differential gain for the position-force controller of the first finger

  • +
  • finger2/gains/p (double, required): The proportional gain for the position-force controller of the second finger

  • +
  • finger2/gains/i (double, default: 0): The integral gain for the position-force controller of the second finger

  • +
  • finger2/gains/d (double, default: 0): The differential gain for the position-force controller of the second finger

  • +
  • move/width_tolerance (double, default \(5\:mm\)): The move action succeeds, when the finger width becomes +below this threshold

  • +
  • grasp/resting_threshold (double, default \(1\:\frac{mm}{s}\)): Below which speed the target width should +be checked to abort or succeed the grasp action

  • +
  • grasp/consecutive_samples (double, default: 3): How many times the speed has to be consecutively below +resting_threshold before the grasping will be evaluated

  • +
  • gripper_action/width_tolerance (double, default \(5\:mm\)): The gripper action succeeds, when the finger +width becomes below this threshold

  • +
  • gripper_action/speed (double, default \(10\:\frac{cm}{s}\)): The speed to use during the gripper action

  • +
+
+
+

JointStateInterface

+

To be able to access the joint state interface from a ROS controller you only have to declare the corresponding +joint in any transmission tag in the URDF. Then a joint state interface will be automatically available. Usually +you declare transmission tags for command interfaces like the EffortJointInterface.

+
+

Note

+

For any joint named <arm_id>_jointN (with N as integer) FrankaHWSim will automatically compensate its gravity +to mimic the behavior of libfranka.

+
+
+
+

EffortJointInterface

+

To be able to send effort commands from your controller to a joint, you simply declare a transmission tag for the +joint in your URDF with the corresponding hardware interface type:

+
<transmission name="${joint}_transmission">
+ <type>transmission_interface/SimpleTransmission</type>
+ <joint name="${joint}">
+   <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
+ </joint>
+ <actuator name="${joint}_motor">
+   <hardwareInterface>${transmission}</hardwareInterface>
+ </actuator>
+</transmission>
+
+<gazebo reference="${joint}">
+  <!-- Needed for ODE to output external wrenches on joints -->
+  <provideFeedback>true</provideFeedback>
+</gazebo>
+
+
+
+

Note

+

If you want to be able to read external forces or torques, which come e.g. from collisions, make sure to set the +<provideFeedback> tag to true.

+
+
+
+

FrankaStateInterface

+

This is a robot-specific interface and thus a bit different from the normal hardware interfaces. +To be able to access the franka state interface from your controller declare the following transmission tag with +all seven joints in your URDF:

+
<transmission name="${arm_id}_franka_state">
+  <type>franka_hw/FrankaStateInterface</type>
+  <joint name="${arm_id}_joint1"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint2"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint3"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint4"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint5"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint6"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+  <joint name="${arm_id}_joint7"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></joint>
+
+  <actuator name="${arm_id}_motor1"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor2"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor3"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor4"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor5"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor6"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+  <actuator name="${arm_id}_motor7"><hardwareInterface>franka_hw/FrankaStateInterface</hardwareInterface></actuator>
+</transmission>
+
+
+

When your controller accesses the robot state via the FrankaStateInterface it can +expect the following values to be simulated:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Field

Comment

O_T_EE

O_T_EE_d

Motion generation not yet supported, field will contain only zeros

F_T_EE

Can be configured via parameters F_T_NE, NE_T_EE and/or +service calls to set_EE_frame

NE_T_EE

Can be configured via parameter NE_T_EE and/or service calls +to set_EE_frame

EE_T_K

Can be configured via parameter EE_T_K and/or service calls +to set_K_frame

m_ee

Will be set from the mass in the inertial tag of URDF, if a hand can +be found, otherwise zero. Can be overwritten by parameter m_ee

I_ee

Will be set from the inertia in the inertial tag of URDF, if a hand +be found, otherwise zero. Can be overwritten by parameter I_ee

F_x_Cee

Will be set from the origin in the inertial tag of URDF, if a hand can +be found, otherwise zero. Can be overwritten by parameter F_x_Cee

m_load

Can be configured via parameter m_load and/or service calls to +set_load

I_load

Can be configured via parameter I_load and/or service calls to +set_load

F_x_Cload

Can be configured via parameter F_x_Cload and/or service calls to +set_load

m_total

I_total

F_x_Ctotal

elbow

elbow_d

elbow_c

delbow_d

delbow_c

tau_J

Comes directly from Gazebo

tau_J_d

The values send by your effort controller. Zero otherwise.

dtau_J

Numerical derivative of tau_J

q

Comes directly from Gazebo

q_d

The last commanded joint position when using the position interface. +Same as q when using the velocity interface. However, +the value will not be updated when using the effort interface.

dq

Comes directly from Gazebo

dq_d

The last commanded joint velocity when using the velocity interface. +Same as dq when using the position interface. However, +the value will be zero when using the effort interface.

ddq_d

Current acceleration when using the position or velocity interface. +However, the value will be zero when using the effort interface.

joint_contact

\(\mid \hat{\tau}_{ext} \mid > \mathrm{thresh}_{lower}\) where the +threshold can be set by calling set_force_torque_collision_behavior

joint_collision

\(\mid \hat{\tau}_{ext} \mid > \mathrm{thresh}_{upper}\) where the +threshold can be set by calling set_force_torque_collision_behavior

cartesian_contact

\(\mid {}^K \hat{F}_{K,ext} \mid > \mathrm{thresh}_{lower}\) where +threshold can be set by calling set_force_torque_collision_behavior

cartesian_collision

\(\mid {}^K \hat{F}_{K,ext} \mid > \mathrm{thresh}_{upper}\) where +threshold can be set by calling set_force_torque_collision_behavior

tau_ext_hat_filtered

\(\hat{\tau}_{ext}\) i.e. estimated external torques and forces at +the end-effector, filtered with a exponential moving average filter +(EMA). This filtering \(\alpha\) can be configured via a ROS +parameter. This field does not contain any gravity, i.e. +\(\tau_{ext} = \tau_J - \tau_{J_d} - \tau_{gravity}\)

O_F_ext_hat_K

\({}^O\hat{F}_{K,ext} = J_O^{\top +} \cdot \hat{\tau}_{ext}\)

K_F_ext_hat_K

\({}^K\hat{F}_{K,ext} = J_K^{\top +} \cdot \hat{\tau}_{ext}\)

O_dP_EE_d

O_ddP_0

Will be the same as the gravity_vector ROS parameter. +By default it is {0,0,-9.8}

O_T_EE_c

O_dP_EE_c

O_ddP_EE_c

theta

Same as q, since we don’t simulate soft joints in Gazebo

dtheta

Same as dq, since we don’t simulate soft joints in Gazebo

current_errors

Will entirely be false, reflex system not yet implemented

last_motion_errors

Will entirely be false, reflex system not yet implemented

control_command_success_rate

Always 1.0

robot_mode

Robot mode switches and reflex system not yet implemented

time

Current ROS time in simulation, comes from Gazebo

+
+
+

FrankaModelInterface

+

This is a robot-specific interface and thus a bit different from the normal hardware interfaces. +To be able to access the franka model interface from your controller declare the following transmission tag with +the root (e.g. panda_joint1) and the tip (e.g. panda_joint8) of your kinematic chain in your URDF:

+
<transmission name="${arm_id}_franka_model">
+  <type>franka_hw/FrankaModelInterface</type>
+  <joint name="${root}">
+    <role>root</role>
+    <hardwareInterface>franka_hw/FrankaModelInterface</hardwareInterface>
+  </joint>
+  <joint name="${tip}">
+    <role>tip</role>
+    <hardwareInterface>franka_hw/FrankaModelInterface</hardwareInterface>
+  </joint>
+
+  <actuator name="${root}_motor_root"><hardwareInterface>franka_hw/FrankaModelInterface</hardwareInterface></actuator>
+  <actuator name="${tip}_motor_tip"  ><hardwareInterface>franka_hw/FrankaModelInterface</hardwareInterface></actuator>
+</transmission>
+
+
+

The model functions themselve are implemented with KDL. This takes the kinematic +structure and the inertial properties from the URDF to calculate model properties like the Jacobian or the mass matrix.

+
+
+

Friction

+

For objects to have proper friction between each other (like fingers and objects) you need to tune +some Gazebo parameters in your URDF. For the links panda_finger_joint1 and panda_finger_joint2 we recommend to +set the following parameters:

+
<gazebo reference="${link}">
+  <collision>
+    <max_contacts>10</max_contacts>
+    <surface>
+      <contact>
+        <ode>
+          <!-- These two parameter need application specific tuning. -->
+          <!-- Usually you want no "snap out" velocity and a generous -->
+          <!-- penetration depth to keep the grasping stable -->
+          <max_vel>0</max_vel>
+          <min_depth>0.003</min_depth>
+        </ode>
+      </contact>
+      <friction>
+        <ode>
+          <!-- Rubber/Rubber contact -->
+          <mu>1.16</mu>
+          <mu2>1.16</mu2>
+        </ode>
+      </friction>
+      <bounce/>
+    </surface>
+  </collision>
+</gazebo>
+
+
+ +
+
+
+

franka_msgs

+

This package contains message, service and action types that are primarily used the packages +franka_hw and franka_control to publish robot states or to expose the libfranka API +in the ROS ecosystem. For more information about the services and actions offered in this +package, please refer to franka_control.

+
+
+

panda_moveit_config

+
+

Note

+

This package was moved to the ros_planning repos.

+
+

For more details, documentation and tutorials, please have a look at the +MoveIt! tutorials website.

+
+
+

Writing your own controller

+

All example controllers from the example controllers package are +derived from the controller_interface::MultiInterfaceController class, which allows to claim +up to four interfaces in one controller instance. The declaration of your class then looks like:

+
class NameOfYourControllerClass : controller_interface::MultiInterfaceController <
+                              my_mandatory_first_interface,
+                              my_possible_second_interface,
+                              my_possible_third_interface,
+                              my_possible_fourth_interface> {
+  bool init (hardware_interface::RobotHW* hw, ros::NodeHandle& nh);  // mandatory
+  void update (const ros::Time& time, const ros::Duration& period);  // mandatory
+  void starting (const ros::Time& time)   // optional
+  void stopping (const ros::Time& time);  // optional
+  ...
+}
+
+
+

The available interfaces are described in Section franka_hw.

+
+

Important

+

Note that the claimable combinations of commanding interfaces are restricted as it does not +make sense to e.g. command joint positions and Cartesian poses simultaneously. Read-only +interfaces like the JointStateInterface, the FrankaStateInterface or the +FrankaModelInterface can always be claimed and are not subject to restrictions.

+
+

Possible claims to command interfaces are:

+ ++++ + + + + + + + + + + +

franka_hw::FrankaHW

franka_combinable_hw::FrankaCombinableHW

    +
  • all possible single interface claims

  • +
  • EffortJointInterface + +PositionJointInterface

  • +
  • EffortJointInterface + +VelocityJointInterface

  • +
  • EffortJointInterface + +FrankaCartesianPoseInterface

  • +
  • EffortJointInterface + +FrankaCartesianVelocityInterface

  • +
+
    +
  • EffortJointInterface

  • +
  • EffortJointInterface + +FrankaCartesianPoseInterface

  • +
  • EffortJointInterface + +FrankaCartesianVelocityInterface

  • +
+
+

The idea behind offering the EffortJointInterface in combination with a motion generator +interface is to expose the internal motion generators to the user. The calculated desired joint +pose corresponding to a motion generator command is available in the robot state one time step +later. One use case for this combination would be following a Cartesian trajectory using your own +joint-level torque controller. In this case you would claim the combination EffortJointInterface ++ FrankaCartesianPoseInterface, stream your trajectory into the FrankaCartesianPoseInterface, +and compute your joint-level torque commands based on the resulting desired joint pose (q_d) from +the robot state. This allows to use the robot’s built-in inverse kinematics instead of having to +solve it on your own.

+

To implement a fully functional controller you have to implement at least the inherited virtual +functions init and update. Initializing - e.g. start poses - should be done in the +starting function as starting is called when restarting the controller, while init is +called only once when loading the controller. The stopping method should contain shutdown +related functionality (if needed).

+
+

Important

+

Always command a gentle slowdown before shutting down the controller. When using velocity +interfaces, do not simply command zero velocity in stopping. Since it might be called +while the robot is still moving, it would be equivalent to commanding a jump in velocity +leading to very high resulting joint-level torques. In this case it would be better to keep the +same velocity and stop the controller than sending zeros and let the robot handle +the slowdown.

+
+

Your controller class must be exported correctly with pluginlib which requires adding:

+
#include <pluginlib/class_list_macros.h>
+// Implementation ..
+PLUGINLIB_EXPORT_CLASS(name_of_your_controller_package::NameOfYourControllerClass,
+                       controller_interface::ControllerBase)
+
+
+

at the end of the .cpp file. In addition you need to define a plugin.xml file with the +following content:

+
<library path="lib/lib<name_of_your_controller_library>">
+  <class name="name_of_your_controller_package/NameOfYourControllerClass"
+         type="name_of_your_controller_package::NameOfYourControllerClass"
+         base_class_type="controller_interface::ControllerBase">
+    <description>
+      Some text to describe what your controller is doing
+    </description>
+  </class>
+</library>
+
+
+

which is exported by adding:

+
<export>
+  <controller_interface plugin="${prefix}/plugin.xml"/>
+</export>
+
+
+

to your package.xml. Further, you need to load at least a controller name in combination with a +controller type to the ROS parameter server. Additionally, you can include other parameters you +need. An exemplary configuration.yaml file can look like:

+
your_custom_controller_name:
+  type: name_of_your_controller_package/NameOfYourControllerClass
+  additional_example_parameter: 0.0
+  # ..
+
+
+

Now you can start your controller using the controller_spawner node from ROS control or via the +service calls offered by the hardware_manager. Just make sure that both the +controller_spawner and the franka_control_node run in the same namespace. For more details +have a look at the controllers from the +franka_example_controllers package or the +ROS control tutorials.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_ros2.html b/franka_ros2.html new file mode 100644 index 0000000..11c859b --- /dev/null +++ b/franka_ros2.html @@ -0,0 +1,786 @@ + + + + + + + franka_ros2 — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

franka_ros2

+
+

Note

+

franka_ros2 is not supported on Windows.

+
+

The franka_ros2 repo contains a ROS 2 integration of +libfranka.

+
+

Caution

+

franka_ros2 is in rapid development. Anticipate breaking changes. Report bugs on +GitHub.

+
+
+

Prerequisites

+
    +
  • A ROS 2 Humble installation +(ros-humble-desktop) or a VSCode IDE with DevContainer.

  • +
  • A PREEMPT_RT kernel (optional, but strongly recommended).

  • +
  • For cartesian_pose, joint_position and elbow_position command interfaces realtime-kernel is absolutely necessary.

  • +
  • A system-wide libfranka installation. Minimum supported version of libfranka is 0.13.0. +Here is a minimal example:

  • +
+
sudo apt install -y libpoco-dev libeigen3-dev
+git clone https://github.com/frankaemika/libfranka.git --recursive
+cd libfranka
+git switch 0.13.2
+mkdir build && cd build
+cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF  ..
+cmake --build . -j$(nproc)
+cpack -G DEB
+sudo dpkg -i libfranka-*.deb
+
+
+
+

Optional .bashrc Settings

+
    +
  • To get colorized warn and error messages you can put +export RCUTILS_COLORIZED_OUTPUT=1 into your .bashrc.

  • +
  • If your system language is not set to American English you should put +export LC_NUMERIC=en_US.UTF-8 into your .bashrc to avoid issues in RViz.

  • +
+
+
+
+

Setup

+
+

Install From Source

+
    +
  1. Install requirements:

    +
    sudo apt install -y \
    +ros-humble-hardware-interface \
    +ros-humble-generate-parameter-library \
    +ros-humble-ros2-control-test-assets \
    +ros-humble-controller-manager \
    +ros-humble-control-msgs \
    +ros-humble-xacro \
    +ros-humble-angles \
    +ros-humble-ros2-control \
    +ros-humble-realtime-tools \
    +ros-humble-control-toolbox \
    +ros-humble-moveit \
    +ros-humble-ros2-controllers \
    +ros-humble-joint-state-publisher \
    +ros-humble-joint-state-publisher-gui \
    +ros-humble-ament-cmake \
    +ros-humble-ament-cmake-clang-format \
    +python3-colcon-common-extensions
    +
    +
    +
  2. +
  3. Create a ROS 2 workspace:

    +
    mkdir -p ~/franka_ros2_ws/src
    +
    +
    +
  4. +
  5. Clone repo and build packages:

    +
    source /opt/ros/humble/setup.bash
    +cd ~/franka_ros2_ws
    +git clone https://github.com/frankaemika/franka_ros2.git src/franka_ros2
    +colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release
    +source install/setup.sh
    +
    +
    +
  6. +
+
+
+

Use VSCode DevContainer

+

FrankaROS2 package comes with .devcontainer folder which enables developer to use FrankaROS2 packages without manually installing ROS2 or libfranka. +VSCode DevContainer working schematic is shown in the below image:

+
+
+_images/vscode.png +
+
+
    +
  1. Follow the setup guide from VSCode devcontainer_setup.

  2. +
  3. Create a ROS 2 workspace:

    +
    mkdir franka_ros2_ws
    +cd franka_ros2_ws
    +
    +
    +
  4. +
  5. Clone repo:

    +
    git clone https://github.com/frankaemika/franka_ros2.git src/franka_ros2
    +
    +
    +
  6. +
  7. Move the .devcontainer folder to the franka_ros2_ws parent folder:

    +
    mv franka_ros2/.devcontainer .
    +
    +
    +
  8. +
  9. Open VSCode:

    +
    code .
    +
    +
    +
  10. +
  11. Open the current folder in DevContainer:

    +
    ctrl + shift + p
    +
    +
    +

    Write in the command prompt bar:

    +
    Dev Containers: Rebuild and Reopen in Container
    +
    +
    +

    and click this option in the search results

    +
  12. +
  13. Open up the terminal in VScode:

    +
    ctrl + `
    +
    +
    +
  14. +
  15. Source the environment:

    +
    source /ros_entrypoint.sh
    +
    +
    +
  16. +
  17. Install the Franka ROS 2 packages:

    +
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release
    +source install/setup.sh
    +
    +
    +
  18. +
+
+
+
+

MoveIt

+

To see if everything works, you can try to run the MoveIt example on the robot:

+
ros2 launch franka_moveit_config moveit.launch.py robot_ip:=<fci-ip>
+
+
+

Then activate the MotionPlanning display in RViz.

+

If you do not have a robot you can still test your setup by running on a dummy hardware:

+
ros2 launch franka_moveit_config moveit.launch.py robot_ip:=dont-care use_fake_hardware:=true
+
+
+

Wait until you can see the green You can start planning now! message from MoveIt inside the +terminal. Then turn off the PlanningScene and turn it on again. After that turn on the MotionPlanning.

+
+
+

Example Controllers

+

This repo comes with a few example controllers located in the franka_example_controllers package.

+

The following launch files are executed with the gripper by default. If you +do not have the gripper attached you can disable the gripper in the launch file with load_gripper:=false.

+
+

Move-to-start

+

This controller moves the robot to its home configuration.

+
ros2 launch franka_bringup move_to_start_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Gravity Compensation

+

This is the simplest controller that we have and is a good starting point to write your own. +It sends zero as torque command to all joints, which means that the robot only compensates its own weight.

+
ros2 launch franka_bringup gravity_compensation_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Joint Impedance Example

+

The example moves joints 4 and 5 in a periodic movement that is very compliant. You can try to move the +joints while it is running.

+
ros2 launch franka_bringup joint_impedance_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Joint Impedance With IK Example

+

The example uses the LMA-Orocos solver from MoveIt service to compute the joint positions for the desired pose. +The desired pose is to move the end-effector periodically in x and z directions. You can change the kinematic solver +in the franka_moveit_config package, kinematics.yaml file.

+
ros2 launch franka_bringup joint_impedance_with_ik_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Model Example Controller

+

This is a read-only controller which prints the coriolis force vector, gravity force vector, pose matrix of Joint4, +Joint4 body jacobian and end-effector jacobian with respect to the base frame.

+
ros2 launch franka_bringup model_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Joint Position Example

+

This example sends periodic position commands to the robot.

+
+

Important

+

The position trajectory needs to start from the initial position of the robot.

+
+

To read the start position of the robot, you can read claim the initial_joint_position. +state interface values before starting to send any commands.

+
if (initialization_flag_) {
+  for (size_t i = 0; i < 7; ++i) {
+    initial_q_.at(i) = state_interface[i].get_value();
+  }
+  initialization_flag_ = false;
+}
+
+
+
ros2 launch franka_bringup joint_position_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Joint Velocity Example

+

This example sends periodic velocity commands to the 4th and 5th joint of the robot.

+
ros2 launch franka_bringup joint_velocity_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Cartesian Pose Example

+

This example uses the CartesianPose interface to send periodic pose commands to the robot.

+
ros2 launch franka_bringup cartesian_pose_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Cartesian Orientation Example

+

This example uses CartesianOrientation interface to send periodic orientation commands around X axis of the end effector of the robot.

+
ros2 launch franka_bringup cartesian_orientation_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Cartesian Pose Elbow Example

+

This example sends periodic elbow commands while keeping the end effector pose constant.

+
ros2 launch franka_bringup cartesian_elbow_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Cartesian Velocity Example

+

This example uses the CartesianVelocity interface to send periodic velocity commands to the robot.

+
ros2 launch franka_bringup cartesian_velocity_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+

Cartesian Elbow Example

+

This example uses the CartesianElbow interface to send periodic elbow commands to the robot while keeping the end effector velocity constant.

+
ros2 launch franka_bringup elbow_example_controller.launch.py robot_ip:=<fci-ip>
+
+
+
+
+
+

Package Descriptions

+

This section contains more detailed descriptions of what each package does. In general the package structure tries to +adhere to the structure that is proposed +here.

+
+

franka_bringup

+

This package contains the launch files for the examples as well as the basic franka.launch.py launch file, that +can be used to start the robot without any controllers.

+

When you start the robot with:

+
ros2 launch franka_bringup franka.launch.py robot_ip:=<fci-ip> use_rviz:=true
+
+
+

There is no controller running apart from the joint_state_broadcaster. However, a connection with the robot is still +established and the current robot pose is visualized in RViz. In this mode the robot can be guided when the user stop +button is pressed. However, once a controller that uses the effort_command_interface is started, the robot will be +using the torque interface from libfranka. For example it is possible to launch the +gravity_compensation_example_controller by running:

+
ros2 control load_controller --set-state active  gravity_compensation_example_controller
+
+
+

This is the equivalent of running the gravity_compensation_example_controller.launch.py launch file mentioned in +Gravity Compensation.

+

When the controller is stopped with:

+
ros2 control set_controller_state gravity_compensation_example_controller inactive
+
+
+

the robot will stop the torque control and will only send its current state over the FCI.

+

You can now choose to start the same controller again with:

+
ros2 control set_controller_state gravity_compensation_example_controller active
+
+
+

or load and start a different one:

+
ros2 control load_controller --set-state active joint_impedance_example_controller
+
+
+
+
+

franka_description

+

This package contains the xacro files and meshes that are used to visualize the robot. +Further, it contains a launch file that visualizes the robot model without access to a real robot:

+
ros2 launch franka_description visualize_franka.launch.py load_gripper:=<true|false>
+
+
+
+
+

franka_example_controllers

+

This package contains a few controllers that can be seen as example of how to write controllers in ROS 2. Currently, +a controller only has access to measured joint positions and joint velocities. Based on this information the controller +can send torque commands. It is currently not possible to use other interfaces like the joint position interface.

+
+
+

franka_gripper

+

This package contains the franka_gripper_node for interfacing with the Franka Hand.

+

The franka_gripper_node provides the following actions:

+
    +
  • homing - homes the gripper and updates the maximum width given the mounted fingers.

  • +
  • move - moves to a target width with the defined speed.

  • +
  • grasp - tries to grasp at the desired width with the desired force while closing with the given speed. The operation is successful if the +distance d between the gripper fingers is width - epsilon.inner < d < width + epsilon.outer

  • +
  • gripper_action - a special grasping action for MoveIt.

  • +
+

Also, there is a stop service that aborts gripper actions and stops grasping.

+

Use the following launch file to start the gripper:

+
ros2 launch franka_gripper gripper.launch.py robot_ip:=<fci-ip>
+
+
+

In a different tab you can now perform the homing and send a grasp command.:

+
ros2 action send_goal /panda_gripper/homing franka_msgs/action/Homing {}
+ros2 action send_goal -f /panda_gripper/grasp franka_msgs/action/Grasp "{width: 0.00, speed: 0.03, force: 50}"
+
+
+

The inner and outer epsilon are 0.005 meter per default. You can also explicitly set the epsilon:

+
ros2 action send_goal -f /panda_gripper/grasp franka_msgs/action/Grasp "{width: 0.00, speed: 0.03, force: 50, epsilon: {inner: 0.01, outer: 0.01}}"
+
+
+

To stop the grasping, you can use stop service.:

+
ros2 service call /panda_gripper/stop std_srvs/srv/Trigger {}
+
+
+
+
+

franka_hardware

+

This package contains the franka_hardware plugin needed for ros2_control. +The plugin is loaded from the URDF of the robot and passed to the controller manager via the robot description. +It provides for each joint:

+
    +
  • a position state interface that contains the measured joint position.

  • +
  • a velocity state interface that contains the measured joint velocity.

  • +
  • an effort state interface that contains the measured link-side joint torques including gravity.

  • +
  • an initial_position state interface that contains the initial joint position of the robot.

  • +
  • an effort command interface that contains the desired joint torques without gravity.

  • +
  • a position command interface that contains the desired joint position.

  • +
  • a velocity command interface that contains the desired joint velocity.

  • +
+

In addition

+
    +
  • a franka_robot_state that contains the robot state information, franka_robot_state.

  • +
  • a franka_robot_model_interface that contains the pointer to the model object.

  • +
+
+

Important

+

franka_robot_state and franka_robot_model_interface state interfaces should not be used directly from hardware state interface. +Rather, they should be utilized by the franka_semantic_components interface.

+
+

The IP of the robot is read over a parameter from the URDF.

+
+
+

franka_semantic_components

+

This package contains franka_robot_model, franka_robot_state and cartesian command classes. +These classes are used to convert franka_robot_model object and franka_robot_state objects, +which are stored in the hardware_state_interface as a double pointer.

+

For further reference on how to use these classes: +Franka Robot State Broadcaster +and +Franka Example Controllers(model_example_controller)

+
    +
  • Cartesian Pose Interface:

  • +
+

This interface is used to send Cartesian pose commands to the robot by using the loaned command interfaces. +FrankaSemanticComponentInterface class is handling the loaned command interfaces and state interfaces. +While starting the cartesian pose interface, the user needs to pass a boolean flag to the constructor +to indicate whether the interface is for the elbow or not.

+
auto is_elbow_active = false;
+CartesianPoseInterface cartesian_pose_interface(is_elbow_active);
+
+
+

This interface allows users to read the current pose command interface values set by the franka hardware interface.

+
std::array<double, 16> pose;
+pose = cartesian_pose_interface.getInitialPoseMatrix();
+
+
+

One could also read quaternion and translation values in Eigen format.

+
Eigen::Quaterniond quaternion;
+Eigen::Vector3d translation;
+std::tie(quaternion, translation) = cartesian_pose_interface.getInitialOrientationAndTranslation();
+
+
+

After setting up the cartesian interface, you need to assign_loaned_command_interfaces and assign_loaned_state_interfaces in your controller. +This needs to be done in the on_activate() function of the controller. Examples can be found in the +assign loaned comamand interface example

+
cartesian_pose_interface.assign_loaned_command_interfaces(command_interfaces_);
+cartesian_pose_interface.assign_loaned_state_interfaces(state_interfaces_);
+
+
+

In the update function of the controller you can send pose commands to the robot.

+
std::array<double, 16> pose;
+pose = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.5, 0, 0.5, 1};
+cartesian_pose_interface.setCommanded(pose);
+
+
+

Or you can send quaternion, translation values in Eigen format.

+
Eigen::Quaterniond quaternion(1, 0, 0, 0);
+Eigen::Vector3d translation(0.5, 0, 0.5);
+cartesian_pose_interface.setCommand(quaternion, translation);
+
+
+
    +
  • Cartesian Velocity Interface:

  • +
+

This interface is used to send Cartesian velocity commands to the robot by using the loaned command interfaces. +FrankaSemanticComponentInterface class is handling the loaned command interfaces and state interfaces.

+
auto is_elbow_active = false;
+CartesianVelocityInterface cartesian_velocity_interface(is_elbow_active);
+
+
+

To send the velocity command to the robot, you need to assign_loaned_command_interface in your custom controller.

+
cartesian_velocity_interface.assign_loaned_command_interface(command_interfaces_);
+
+
+

In the update function of the controller you can send cartesian velocity command to the robot.

+
std::array<double, 6> cartesian_velocity;
+cartesian_velocity = {0, 0, 0, 0, 0, 0.1};
+cartesian_velocity_interface.setCommand(cartesian_velocity);
+
+
+
+
+

franka_robot_state_broadcaster

+

This package contains read-only franka_robot_state_broadcaster controller. +It publishes franka_robot_state topic to the topic named /franka_robot_state_broadcaster/robot_state. +This controller node is spawned by franka_launch.py in the franka_bringup. +Therefore, all the examples that include the franka_launch.py publishes the robot_state topic.

+
+
+

franka_moveit_config

+

This package contains the configuration for MoveIt2. There is a new move group called +panda_manipulator that has its tip between the fingers of the gripper and has its Z-axis rotated by -45 degrees, so +that the X-axis is now facing forward, making it easier to use. The panda_arm move group is still available +for backward compatibility. New applications should use the new panda_manipulator move group instead.

+
+_images/move-groups.png +
+

Visualization of the old and the new move group

+
+
+
+
+

franka_msgs

+

This package contains the definitions for the different gripper actions and robot state message.

+
+
+

joint_effort_trajectory_controller

+

This package contains a modified joint_trajectory_controller that can use the effort interface of the +franka_hardware::FrankaHardwareInterface. It is based on this +Pull request.

+
+

Note

+

This package will be soon deleted as the fix is available in +ros2_controllers master branch. +As soon as, it’s backported to Humble, it will be deleted from franka_ros2 repository.

+
+
+
+
+

Differences between franka_ros and franka_ros2

+

This section gives an overview of the fundamental changes between franka_ros and franka_ros2.

+
+

franka_gripper

+
    +
  • All topics and actions were previously prefixed with franka_gripper. This prefix was renamed to panda_gripper +to enable, in the future, a workflow where all prefixes are based on the arm_id +to effortlessly enable multi arm setups.

  • +
  • The stop action is now a service action as it is not preemptable.

  • +
  • All actions (apart from the gripper_action) have the current gripper width as feedback.

  • +
+
+
+

franka_gazebo

+

Currently, we do not offer a Gazebo integration with franka_ros2. However, we provide one +with franka_ros.

+
+
+

franka_visualization

+

This package does not exist anymore. However, franka_description provides a launch file to visualize the robot +model without a connection to a robot.

+
+
+

franka_control

+

This package does not exist anymore. The connection to the robot is provided by the hardware plugin in +the franka_hardware package. The actions and services that it provided are currently +not offered in franka_ros2.

+
+
+

Writing Controllers

+

Compared to franka_ros we currently offer a reduced set of controller interfaces:

+
    +
  • Joint positions

  • +
  • Joint velocities

  • +
  • Measured torques

  • +
  • Franka robot state

  • +
  • Franka robot model

  • +
+
+

Important

+

Franka robot state is published through franka_robot_state_broadcaster +package to the topic named /franka_robot_state_broadcaster/robot_state

+
+
+

Important

+

Both Franka robot state and Franka robot model are advised to use through franka_semantic_components class. +They are stored in the state_interface as double pointers and casted back to their original objects inside the franka_semantic_component class.

+

Example of using franka_model can be found in the franka_example_controllers package: +model_example_controller.

+
+

You can base your own controller on one of the franka_example_controllers. To compute kinematic +and dynamic quantities of the robot you can use the joint states and the URDF of the robot in libraries like +KDL (of which there is also a ROS 2 package available).

+
+
+
+

Non-realtime robot parameter setting

+

Non-realtime robot parameter setting can be done via ROS 2 services. They are advertised after the robot hardware is initialized.

+

Service names are given below:

+
* /service_server/set_cartesian_stiffness
+* /service_server/set_force_torque_collision_behavior
+* /service_server/set_full_collision_behavior
+* /service_server/set_joint_stiffness
+* /service_server/set_load
+* /service_server/set_parameters
+* /service_server/set_parameters_atomically
+* /service_server/set_stiffness_frame
+* /service_server/set_tcp_frame
+
+
+

Service message descriptions are given below.

+
+
    +
  • franka_msgs::srv::SetJointStiffness specifies joint stiffness for the internal controller +(damping is automatically derived from the stiffness).

  • +
  • franka_msgs::srv::SetCartesianStiffness specifies Cartesian stiffness for the internal +controller (damping is automatically derived from the stiffness).

  • +
  • franka_msgs::srv::SetTCPFrame specifies the transformation from <arm_id>_EE (end effector) to +<arm_id>_NE (nominal end effector) frame. The transformation from flange to end effector frame +is split into two transformations: <arm_id>_EE to <arm_id>_NE frame and <arm_id>_NE to +<arm_id>_link8 frame. The transformation from <arm_id>_NE to <arm_id>_link8 frame can only be +set through the administrator’s interface.

  • +
  • franka_msgs::srv::SetStiffnessFrame specifies the transformation from <arm_id>_K to <arm_id>_EE frame.

  • +
  • franka_msgs::srv::SetForceTorqueCollisionBehavior sets thresholds for external Cartesian +wrenches to configure the collision reflex.

  • +
  • franka_msgs::srv::SetFullCollisionBehavior sets thresholds for external forces on Cartesian +and joint level to configure the collision reflex.

  • +
  • franka_msgs::srv::SetLoad sets an external load to compensate (e.g. of a grasped object).

  • +
+
+

Launch franka_bringup/franka.launch.py file to initialize robot hardware:

+
ros2 launch franka_bringup franka.launch.py robot_ip:=<fci-ip>
+
+
+

Here is a minimal example:

+
ros2 service call /service_server/set_joint_stif
+fness franka_msgs/srv/SetJointStiffness "{joint_stiffness: [1000.0, 1000.0, 10
+00.0, 1000.0, 1000.0, 1000.0, 1000.0]}"
+
+
+
+

Important

+

Non-realtime parameter setting can only be done when the robot hardware is in idle mode. +If a controller is active and claims command interface this will put the robot in the move mode. +In move mode non-realtime param setting is not possible.

+
+
+

Important

+

The <arm_id>_EE frame denotes the part of the +configurable end effector frame which can be adjusted during run time through franka_ros. The +<arm_id>_K frame marks the center of the internal +Cartesian impedance. It also serves as a reference frame for external wrenches. Neither the +<arm_id>_EE nor the <arm_id>_K are contained in the URDF as they can be changed at run time. +By default, <arm_id> is set to “panda”.

+
+_images/frames.svg
+

Overview of the end-effector frames.

+
+
+
+
+
+

Known Issues

+
    +
  • When using the fake_hardware with MoveIt, it takes some time until the default position is applied.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_ros2_changelog.html b/franka_ros2_changelog.html new file mode 100644 index 0000000..5ce2864 --- /dev/null +++ b/franka_ros2_changelog.html @@ -0,0 +1,269 @@ + + + + + + + franka_ros2 changelog — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

franka_ros2 changelog

+
+

0.1.11 - 2023-12-20

+

Requires libfranka >= 0.13.2, requires ROS 2 Humble

+
    +
  • franka_example_controllers: Add a joint impedance example using OrocosKDL(LMA-ik) through MoveIt service.

  • +
  • franka_hardware: Register initial joint positions and cartesian pose state interface without having running command interfaces.

  • +
+
+
+

0.1.10 - 2023-12-04

+

Requires libfranka >= 0.13.0, required ROS 2 Humble

+
    +
  • Adapted the franka robot state broadcaster to use ROS 2 message types

  • +
  • Adapted the Cartesian velocity command interface to use Eigen types

  • +
+
+
+

0.1.9 - 2023-12-04

+

Requires libfranka >= 0.13.0, required ROS 2 Humble

+
    +
  • franka_hardware: add state interfaces for initial position, cartesian pose and elbow.

  • +
  • franka_hardware: support cartesian pose interface.

  • +
  • franka_semantic_component: support cartesian pose interface.

  • +
  • franka_example_controllers: add cartesian pose example controller

  • +
  • franka_example_controllers: add cartesian elbow controller

  • +
  • franka_example_controllers: add cartesian orientation controller

  • +
+
+
+

0.1.8 - 2023-11-16

+

Requires libfranka >= 0.13.0, required ROS 2 Humble

+
    +
  • franka_hardware: add unit tests for robot class.

  • +
  • joint_trajectory_controller: hotfix add joint patched old JTC back.

  • +
+
+
+

0.1.7 - 2023-11-10

+

Requires libfranka >= 0.12.1, required ROS 2 Humble

+
    +
  • franka_hardware: joint position command interface supported

  • +
  • franka_hardware: controller initializer automatically acknowledges error, if arm is in reflex mode

  • +
  • franka_example_controllers: joint position example controller provided

  • +
  • franka_example_controllers: fix second start bug with the example controllers

  • +
+
+
+

0.1.6 - 2023-11-03

+

Requires libfranka >= 0.12.1, required ROS 2 Humble

+
    +
  • franka_hardware: support for cartesian velocity command interface

  • +
  • franka_semantic_component: implemented cartesian velocity interface

  • +
  • franka_example_controllers: implement cartesian velocity example controller

  • +
  • franka_example_controllers: implement elbow example controller

  • +
+
+
+

0.1.5 - 2023-10-13

+

Requires libfranka >= 0.12.1, required ROS 2 Humble

+
    +
  • franka_hardware: support joint velocity command interface

  • +
  • franka_example_controllers: implement joint velocity example controller

  • +
  • franka_description: add velocity command interface to the control tag

  • +
+
+
+

0.1.4 - 2023-09-26

+

Requires libfranka >= 0.12.1, required ROS 2 Humble

+
    +
  • franka_hardware: adapt to libfranka active control v0.12.1

  • +
+
+
+

0.1.3 - 2023-08-24

+

Requires libfranka >= 0.11.0, required ROS 2 Humble

+
    +
  • franka_hardware: hotfix start controller when user claims the command interface

  • +
+
+
+

0.1.2 - 2023-08-21

+

Requires libfranka >= 0.11.0, required ROS 2 Humble

+
    +
  • franka_hardware: implement non-realtime parameter services

  • +
+
+
+

0.1.1 - 2023-08-21

+

Requires libfranka >= 0.11.0, required ROS 2 Humble

+
    +
  • franka_hardware: uses updated libfranka version providing the possibility to have the control loop on the ROS side

  • +
+
+
+

0.1.0 - 2023-07-28

+

Requires libfranka >= 0.10.0, required ROS 2 Humble

+
    +
  • franka_bringup: franka_robot_state broadcaster added to franka.launch.py.

  • +
  • franka_example_controllers: model printing read only controller implemented

  • +
  • franka_robot_model: semantic component to access robot model parameters.

  • +
  • franka_msgs: franka robot state msg added

  • +
  • franka_robot_state: broadcaster publishes robot state.

  • +
+
+

Added

+
    +
  • CI tests in Jenkins.

  • +
  • joint_effort_trajectory_controller package that contains a version of the +joint_trajectory_controller that can use the torque interface. +See this PR

  • +
  • franka_bringup package that contains various launch files to start controller examples or Moveit2.

  • +
  • franka_moveit_config package that contains a minimal moveit config to control the robot.

  • +
  • franka_example_controllers package that contains some example controllers to use.

  • +
  • franka_hardware package that contains a plugin to access the robot.

  • +
  • franka_msgs package that contains common message, service and action type definitions.

  • +
  • franka_description package that contains all meshes and xacro files.

  • +
  • franka_gripper package that offers action and service interfaces to use the Franka Hand gripper.

  • +
+
+
+

Fixed

+
    +
  • franka_hardware Fix the mismatched joint state interface type logger error message.

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/franka_ros_changelog.html b/franka_ros_changelog.html new file mode 100644 index 0000000..8699aae --- /dev/null +++ b/franka_ros_changelog.html @@ -0,0 +1,455 @@ + + + + + + + franka_ros changelog — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

franka_ros changelog

+
+

0.10.0 - 2022-09-02

+

Requires libfranka >= 0.8.0

+
    +
  • franka_example_controllers: Normalize rotations before usage

  • +
  • franka_example_controllers: Extend the teleop_joint_pd_example_controller with markers indicating leader and follower roles + consistently use leader and follower as robot names in the example.

  • +
  • franka_example_controllers: Don’t require MoveIt for move_to_start.launch

  • +
  • BREAKING franka_description: Refactor URDF files to also support FR3

  • +
  • franka_control: Introduce optional robot argument in the franka_control.launch launch file that chooses either the URDF for panda or fr3. This argument can also be used in the launch files of franka_examples. The default is “panda”.

  • +
+
+
+

0.9.1 - 2022-08-29

+

Requires libfranka >= 0.8.0

+
    +
  • franka_example_controllers: Extend the teleop_joint_pd_example_controller with markers indicating leader and follower roles + consistently use leader and follower as robot names in the example.

  • +
  • franka_example_controllers: Extend the teleop_joint_pd_example_controller with a finite state machine that aligns the follower robot before starting to track the leader.

  • +
  • franka_example_controllers: Extend the teleop_joint_pd_example_controller with joint walls to actively avoid position or velocity limit violations.

  • +
  • franka_example_controllers: Fix namespacing of dynamic reconfigure node of cartesian impedance example controller

  • +
  • franka_control: Configurable arm_id in launch & config files

  • +
  • franka_description: URDF now contains $(arm_id)_linkN_sc links containing the capsule collision modules used for self-collision avoidance (MoveIt).

  • +
  • franka_description: Unit test suite for URDFs

  • +
  • franka_description: Make util.xacro be includable from other packages

  • +
  • franka_description: Add tcp_xyz & tcp_rpy offsets to specify a custom TCP for MoveIT’s $(arm_id)_manipulator move group

  • +
  • franka_control: franka_control.launch accepts xacro_args to pass down additional arguments to URDF

  • +
  • franka_gazebo: panda.launch accepts xacro_args to pass down additional arguments to URDF

  • +
  • franka_gazebo: Fix motion generator config respects arm_id

  • +
  • BREAKING: gripper_action goes now to the commanded gripper position when max_effort is zero

  • +
  • franka_gazebo: Drop delayed_controller_spawner.py script in favor of --wait-for TOPIC flag from controller_manager

  • +
  • franka_gazebo: Properly calculate inertial properties of world/stone/model.sdf

  • +
  • franka_gazebo: set_user_stop service to simulate User stop in Gazebo

  • +
  • franka_gazebo: error_recovery action similar to franka_control

  • +
  • BREAKING: franka_gazebo: Move services like set_EE_frame, set_K_frame … into franka_control namespace to be more consistent with real robot

  • +
+
+
+

0.9.0 - 2022-03-29

+

Requires libfranka >= 0.8.0

+
    +
  • Added support for libfranka 0.9.0

  • +
  • BREAKING add base acceleration O_ddP_O (will for now always be {0,0,-9.81}) to FrankaState.msg

  • +
  • BREAKING add following errors to Errors.msg:

    +
      +
    • joint_move_in_wrong_direction

    • +
    • cartesian_spline_motion_generator_violation

    • +
    • joint_via_motion_generator_planning_joint_limit_violation

    • +
    • base_acceleration_initialization_timeout

    • +
    • base_acceleration_invalid_reading

    • +
    +
  • +
  • franka_gazebo:

    +
      +
    • Add JointPosition and JointVelocity Interface

    • +
    • Fix: Robot now keeps position when no controller is running

    • +
    • joint_{position,velocity}_example controller are now available in franka_gazebo

    • +
    +
  • +
+
+
+

0.8.2 - 2022-02-22

+

Requires libfranka >= 0.8.0

+
    +
  • franka_gazebo:

    +
      +
    • Fix: homing action works again

    • +
    • Fix: move action can fail instead of blocking indefinitely

    • +
    • Fix: align behavior of grasping action with franka_gripper

    • +
    • Add joint_state_desired publisher

    • +
    • Add singularity warning if Jacobian becomes singular

    • +
    • Make /panda namespace optional

    • +
    • Make finger collisions primitive in franka_gazebo

    • +
    • Add ‘gravity_vector’ gravity ROS parameter to FrankaHWSim

    • +
    • Improve Gazebo ‘stone’ world objects

    • +
    • Introduce new tau_ext_lowpass_filter parameter for franka_gazebo to configure the filtering of tau_ext_hat_filtered

    • +
    • Add realistic hand/finger collision geometries to the Gazebo robot description

    • +
    +
  • +
  • Fix: Allow interactive marker server to shut down if not initialized

  • +
  • No further ROS Kinetic support, since End-of-Life was in April 2021

  • +
  • Make position + orientation targets threadsafe in cartesian example controller

  • +
  • Add effort joint trajectory controller to be used by MoveIT

  • +
  • Fix “Failed to create robot simulation interface loader” bug when installing from APT

  • +
  • Add connected_to option to panda_gazebo.xacro macro, similar to panda_arm.xacro

  • +
  • Rename ns -> arm_id in hand.xacro macros to be consistent with the other xacro files

  • +
+
+
+

0.8.1 - 2021-09-08

+

Requires libfranka >= 0.8.0

+
    +
  • franka_hw:

    +
      +
    • Add bool hasError() member function to FrankaCombinableHW

    • +
    • Only execute controller update in franka_combined_control_node when not in error state

    • +
    • Reset command buffer upon recover in FrankaCombinableHW

    • +
    +
  • +
+
+
+

0.8.0 - 2021-08-03

+

Requires libfranka >= 0.8.0

+
    +
  • franka_hw, franka_combinable_hw, franka_combined_hw: Added service interface to disconnect +and reconnect when no controller is active. This allows mixing FCI- and DESK-based application +without stopping the according hardware nodes.

  • +
  • BREAKING franka_hw, franka_combinable_hw method control() now is non-const to allow +locking a mutex member variable.

  • +
  • BREAKING Change behavior of franka_msgs/SetEEFrame. Previously, this method would set +the flange-to-end-effector transformation F_T_EE. This has been split up into two transformations: +F_T_NE, only settable in Desk, and NE_T_EE, which can be set in franka_ros with SetEEFrame +and defaults to the identity transformation.

  • +
  • Add F_T_NE and NE_T_EE to franka_msgs/FrankaState.

  • +
  • Franka Gazebo Integration: Now you can simulate Panda robots in Gazebo including:

    +
      +
    • gravity compensation

    • +
    • non-realtime commands like setEEFrame or setLoad

    • +
    • gripper simulation with the same action interface as franka_gripper

    • +
    • estimated inertias in the URDF

    • +
    • no need to change existing ROS controllers

    • +
    • only torque control supported in this version

    • +
    +
  • +
  • Extract Model Library in abstract base class interface. This allows users to implement their own model.

  • +
  • BREAKING Remove panda_arm_hand.urdf.xacro. Use panda_arm.urdf.xacro hand:=true instead.

  • +
+
+
+

0.7.1 - 2020-10-22

+

Requires libfranka >= 0.7.0

+
    +
  • franka_example_controllers: Added example for dual-arm teleoperation based on franka_combinable_hw.

  • +
  • franka_gripper: Made stopping on shutdown optional.

  • +
  • franka_hw: Added franka_control_services install instruction.

  • +
+
+
+

0.7.0 - 2020-07-15

+

Requires libfranka >= 0.7.0

+
    +
  • BREAKING moved services and action from franka_control to franka_msgs.

  • +
  • BREAKING moved Service container from franka_control to franka_hw.

  • +
  • franka_example_controllers: Added example for dual-arm control based on franka_combinable_hw.

  • +
  • franka_description :

    +
      +
    • Added an example urdf with two panda arms.

    • +
    • BREAKING Updated collision volumes.

    • +
    • Removed invalid axis for joint8.

    • +
    +
  • +
  • franka_hw:

    +
      +
    • Added hardware classes to support torque-controlling multiple robots from one controller.

    • +
    • Refactored FrankaHW class to serve as base class (e.g. for FrankaCombinableHW).

    • +
    • Added joint limits checking to FrankaHW which means parameterized warning prints when approaching limits.

    • +
    • Made initial collision behavior a parameter.

    • +
    • Added default constructor and init method to FrankaHW.

    • +
    • BREAKING moved parsing of parameters from control node to FrankaHW::init.

    • +
    • BREAKING made libfranka robot a member of FrankaHW.

    • +
    • Added missing return value to franka::ControllerMode stream operator function.

    • +
    +
  • +
  • franka_control:

    +
      +
    • Added control node that can runs a FrankaCombinedHW to control mulitple Pandas.

    • +
    • Publish whole libfranka franka::RobotState in franka_state_controller.

    • +
    +
  • +
  • franka_example_controllers:

    +
      +
    • Cartesian impedance example controller: Interpolate desired orientations with slerp and change orientation error +to quaternion.

    • +
    +
  • +
  • BREAKING Moved panda_moveit_config to ros-planning.

  • +
  • Added support for ROS Melodic Morenia.

  • +
  • Raised minimum CMake version to 3.4 to match libfranka.

  • +
  • Add rosparam to choose value of franka::RealtimeConfig.

  • +
  • Fix unused parameter bugs in FrankaModelHandle (#78).

  • +
  • Added (experimental) support for ROS Noetic Ninjemys.

  • +
+
+
+

0.6.0 - 2018-08-08

+

Requires libfranka >= 0.5.0

+
    +
  • BREAKING Fixes for MoveIt, improving robot performance:

    +
      +
    • Fixed joint velocity and acceleration limits in joint_limits.yaml

    • +
    • Use desired joint state for move group

    • +
    +
  • +
  • BREAKING Updated joint limits in URDF

  • +
  • BREAKING Fixed velocity, acceleration and jerk limits in franka_hw

  • +
  • BREAKING Start franka_gripper_node when giving load_gripper:=true to franka_control.launch

  • +
  • Allow to configure rate limiting, filtering and internal controller in franka_control_node

  • +
  • BREAKING FrankaHW::FrankaHW takes additional parameters.

  • +
  • BREAKING Enabled rate limiting and low-pass filtering by default (franka_control_node.yaml)

  • +
  • Publish desired joint state in /joint_state_desired

  • +
  • Removed effort_joint_trajectory_controller from default_controllers.yaml

  • +
  • Fixed a bug when switching between controllers using the same libfranka interface

  • +
+
+
+

0.5.0 - 2018-06-28

+

Requires libfranka >= 0.4.0

+
    +
  • BREAKING Updated URDF:

    +
      +
    • Adjusted maximum joint velocity

    • +
    • Updated axis 4 hard and soft limits

    • +
    +
  • +
+
+
+

0.4.1 - 2018-06-21

+

Requires libfranka >= 0.3.0

+
    +
  • Added some missing includes to franka_hw

  • +
  • Add support for commanding elbow in Cartesian pose and Cartesian velocity interfaces

  • +
+
+
+

0.4.0 - 2018-03-26

+

Requires libfranka >= 0.3.0

+
    +
  • BREAKING Removed arm_id and default robot_ip from launchfiles

  • +
  • BREAKING Changed namespace of franka_control controller manager

  • +
  • BREAKING Changed behavior of gripper_action for compatibility with MoveIt

  • +
  • Changes in panda_moveit_config:

    +
      +
    • Updated joint limits from URDF

    • +
    • Removed home poses

    • +
    • Fixed fake execution

    • +
    • Add load_gripper argument (default: true) to panda_moveit.launch

    • +
    • Conditionally load controllers/SRDFs based on load_gripper

    • +
    • Add gripper controller configuration (requires running franka_gripper_node)

    • +
    +
  • +
  • Added mimic tag for gripper fingers to URDF and fixed velocity limits

  • +
+
+
+

0.3.0 - 2018-02-22

+

Requires libfranka >= 0.3.0

+
    +
  • BREAKING Changed signatures in franka_hw::FrankaModelHandle

  • +
  • BREAKING Added epsilon parameters to franka_gripper/Grasp action

  • +
  • Added Collada meshes for Panda and Hand

  • +
  • Added missing dependencies to panda_moveit_config and franka_example_controllers

  • +
  • Fixed linker errors when building with -DFranka_DIR while an older version of +ros-kinetic-libfranka is installed

  • +
  • Added gripper joint state publisher to franka_visualization

  • +
  • Moved move_to_start.py example script to franka_example_controllers

  • +
+
+
+

0.2.2 - 2018-01-31

+

Requires libfranka >= 0.2.0

+
    +
  • Catkin-related fixes for franka_example_controllers

  • +
  • Added missing <build_export_depend> for message_runtime

  • +
+
+
+

0.2.1 - 2018-01-30

+

Requires libfranka >= 0.2.0

+
    +
  • Added missing dependency to franka_example_controllers

  • +
  • Lowered rotational gains for Cartesian impedance example controller

  • +
+
+
+

0.2.0 - 2018-01-29

+

Requires libfranka >= 0.2.0

+
    +
  • Added missing run-time dependencies to franka_description and franka_control

  • +
  • Added tau_J_d, m_ee, F_x_Cee, I_ee, m_total, F_x_Ctotal, I_total, +theta and dtheta to franka_msgs/FrankaState

  • +
  • Added new errors to franka_msgs/Errors

  • +
  • Updated and improved examples in franka_example_controllers

  • +
  • Fixed includes for Eigen3 in franka_example_controllers

  • +
  • Fixed gripper state publishing in franka_gripper_node

  • +
+
+
+

0.1.2 - 2017-10-10

+
    +
  • Fixed out-of-workspace build

  • +
+
+
+

0.1.1 - 2017-10-09

+
    +
  • Integrated franka_description as subdirectory

  • +
  • Fixed dependencies on libfranka

  • +
  • Fixed RViz config file paths

  • +
  • Added missing test_depend to franka_hw

  • +
  • Added missing CMake install rules

  • +
+
+
+

0.1.0 - 2017-09-15

+
    +
  • Initial release

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..2d16570 --- /dev/null +++ b/genindex.html @@ -0,0 +1,121 @@ + + + + + + Index — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started.html b/getting_started.html new file mode 100644 index 0000000..44882b0 --- /dev/null +++ b/getting_started.html @@ -0,0 +1,424 @@ + + + + + + + Getting started — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting started

+

After setting up the required software for Linux or +Windows, it is time to connect to the robot and test the whole setup +by using FCI to read the current robot state.

+
+

Operating the robot

+

Before going further though, here are a few safety considerations. +Always check the following things before powering on the robot:

+
    +
  1. Make sure that the Arm has been mounted on a stable base and cannot topple over, even +when performing fast motions or abrupt stops.

  2. +
+
+

Caution

+

Only tabletop mounting is supported, i.e. the Arm must be mounted perpendicular to the +ground! Other mountings will void your warranty and might cause damage +to the robot!

+
+
    +
  1. Ensure that the cable connecting Arm and Control is firmly attached on both sides.

  2. +
  3. Connect the external activation device to Arm’s base and keep it next to you in order to be +able to stop the robot at any time.

  4. +
+
+

Hint

+

Activating the external activation device will disconnect the Arm from Control. +The joint motor controllers will then hold their current position. +The external activation device is not an emergency stop!

+
+

This list is non-exhaustive! The manual delivered with your robot contains a chapter dedicated +to safety. Please read it carefully and follow the instructions. The manual can also be found in +our Franka World Hub.

+
+

Important

+

The workstation PC which commands your robot using the FCI must always be connected to the LAN +port of Control (shop floor network) and not to the LAN port of the Arm (robot network).

+
+
+
+

Installing the FCI Feature

+

In order to be able to operate the robot through the Franka Control Interface, the corresponding +Feature needs to be installed on your device. If it is already installed to the controller, it +will be listed under Desk –> Settings –> System –> Installed Features. +If FCI is not installed yet, you can synchronize it from your Franka World account to your +controller. Therefore, you must have an available FCI license in your company/university account and have +your controller registered in this account. The software (offline or online) synchronization +process is described in more detail in the +Franka World user manual.

+
+
+

Setting up the network

+

Good network performance is crucial when controlling the robot using FCI. +Therefore it is strongly recommended to use a direct connection between the +workstation PC and Panda’s Control. This section describes how to configure your +network for this use case.

+
+_images/control.png +
+

Use Control’s LAN port when controlling the robot through FCI. +Do not connect to the port in Arm’s base.

+
+
+

The Control and your workstation must be configured to appear on the same +network. Simplest way to achieve that is to use static IP addresses. Any two +addresses on the same network would work, but the following values will be used +for the purpose of this tutorial:

+ +++++ + + + + + + + + + + + + + + + + +

Workstation PC

Control

Address

172.16.0.1

172.16.0.2

Netmask

24

24

+

The Control’s address (172.16.0.2) is called <fci-ip> in the following chapters.

+
+

Hint

+

With this network configuration, Desk can be accessed via https://<fci-ip>, although +you will see a certificate warning in your browser.

+
+

The configuration process consists of two steps:

+
+
    +
  • Configuring Control’s network settings.

  • +
  • Configuring your workstation’s network settings.

  • +
+
+
+

Control network configuration

+

For this step, the robot needs to be installed and tested. Please read through +the documents shipped with your robot and follow the setup instructions before +continuing any further!

+

The Control’s network can be configured in the administrator’s interface. For +the duration of this step you can connect to the robot through the port in the +robot’s base. For details, consult the Connecting a user interface device +section in the manual delivered with your robot.

+
+_images/accessing-admin.png +
+

Accessing the administrator’s interface through Desk.

+
+
+

To set up a static address, enter the following values in the Network section:

+
+_images/control-static-ip.png +
+

Setting a static IP for the Control’s LAN port (Shop Floor network). +DHCP Client option is deselected.

+
+
+

Press Apply. After the settings are successfully applied, connect your +workstation’s LAN port to the robot’s control unit.

+
+
+

Linux workstation network configuration

+

This section describes how to set up a static IP address on Ubuntu 16.04 +using the GUI. Follow the official Ubuntu guide if you prefer to use the +command line.

+
+

Caution

+

The following steps will modify your network settings. If in doubt, +contact your network’s administrator.

+
+

First, go to Network Connection widget. Select the wired connection you +will be using and click edit.

+
+_images/edit-connections.png +
+

Edit the connection in the Ethernet section.

+
+
+

Next, click on the IPv4 settings tab, set the method to Manual, and enter the +following values:

+
+_images/static-ip-ubuntu.png +
+

Setting a static IP for the Workstation PC. Method is set to Manual.

+
+
+
+

Hint

+

This step will disable DHCP, which means you will no longer obtain an address +when connecting to a DHCP server, like the one in Arm’s base. When you no +longer use FCI, you can change the Method back to Automatic (DHCP).

+
+

Save the changes, and close the Network Connection window. Click on the +connection name from the drop down menu. It should now be possible to connect to +the robot from your workstation. To verify this, perform the +Network bandwidth, delay and jitter test. From now on, you can also access Desk +through this address in your browser.

+
+
+

Windows workstation network configuration

+

Setup a static IP address on the Windows workstation. Therefore, open Control Panel and go to +Network and Internet > Network and Sharing Center > Change adapter settings. +Right-click the network adapter and open Properties. Use the same example address and netmask +as in the Linux workstation network configuration.

+
+
+
+

Preparing the robot for FCI usage in Desk

+

In order to verify the connection, the robot’s brakes need to be unlocked in Desk and the activation +device needs to be relased so that the robot is ready for execution indicated by blue LED mode.

+
+

Enabling the FCI mode

+

Using a robot with system version >= 4.2.0 requires to enable the FCI mode. To do that open Desk, +expand the menu in the sidebar and press ‘Activate FCI’.

+
+_images/activate_fci.png +
+

Enabling the FCI mode in the Desk sidebar menu

+
+
+
+

FCI mode in Panda

+

After enabling the FCI mode, a pop-up as shown below is appearing. This pop-up indicates that the FCI mode +is currently active and that Desk interactions are not allowed while it is active. This pop-up needs to +remain open while working with FCI. Further information about Single Point of Control (SPoC) can be found +in the manual shipped with the robot which can also be found in our +Franka World Hub.

+
+_images/pop_up_fci.png +
+

Pop-up when the FCI mode is enabled

+
+
+
+
+

FCI mode in Franka Research 3

+

After enabling the FCI mode, the sidebar shows that FCI is on.

+
+_images/fci-on.png +
+

Sidebar when the FCI mode is enabled

+
+
+
+
+
+
+

Verifying the connection

+

The previous sections described how to specify the IP address of the Control’s +LAN port. In the following sections that address is referred to as <fci-ip>.

+

In order to verify that everything is correctly set up, be sure the robot is prepared for FCI +usage in Desk and run the echo_robot_state +example from libfranka. If you decided to install franka_ros and libfranka from the ROS +repository, you can instead read the instructions for +visualizing the robot in ros .

+

Change to the build directory of libfranka and execute the example:

+

Linux:

+
./examples/echo_robot_state <fci-ip>
+
+
+

Windows:

+
cd /path/to/libfranka/build/examples/
+echo_robot_state.exe <fci-ip>
+
+
+
+

Hint

+

Before executing libfranka programms, make sure that the executables are able to find their runtime libraries. +On Windows, the easiest way is to copy the needed libraries into the same directory as the executable.

+
+

The program will print the current state of the robot to the console and terminate after a few +iterations. The fields are explained in the +libfranka API documentation.

+

Example output:

+
{
+  "O_T_EE": [0.998578,0.0328747,-0.0417381,0,0.0335224,-0.999317,0.0149157,0,-0.04122,-0.016294,
+             -0.999017,0,0.305468,-0.00814133,0.483198,1],
+  "O_T_EE_d": [0.998582,0.0329548,-0.041575,0,0.0336027,-0.999313,0.0149824,0,-0.0410535,
+               -0.0163585,-0.999023,0,0.305444,-0.00810967,0.483251,1],
+  "F_T_EE": [0.7071,-0.7071,0,0,0.7071,0.7071,0,0,0,0,1,0,0,0,0.1034,1],
+  "EE_T_K": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
+  "m_ee": 0.73, "F_x_Cee": [-0.01,0,0.03], "I_ee": [0.001,0,0,0,0.0025,0,0,0,0.0017],
+  "m_load": 0, "F_x_Cload": [0,0,0], "I_load": [0,0,0,0,0,0,0,0,0],
+  "m_total": 0.73, "F_x_Ctotal": [-0.01,0,0.03], "I_total": [0.001,0,0,0,0.0025,0,0,0,0.0017],
+  "elbow": [-0.0207622,-1], "elbow_d": [-0.0206678,-1],
+  "tau_J": [-0.00359774,-5.08582,0.105732,21.8135,0.63253,2.18121,-0.0481953],
+  "tau_J_d": [0,0,0,0,0,0,0],
+  "dtau_J": [-54.0161,-18.9808,-64.6899,-64.2609,14.1561,28.5654,-11.1858],
+  "q": [0.0167305,-0.762614,-0.0207622,-2.34352,-0.0305686,1.53975,0.753872],
+  "dq": [0.00785939,0.00189343,0.00932415,0.0135431,-0.00220327,-0.00492024,0.00213604],
+  "q_d": [0.0167347,-0.762775,-0.0206678,-2.34352,-0.0305677,1.53975,0.753862],
+  "dq_d": [0,0,0,0,0,0,0],
+  "joint_contact": [0,0,0,0,0,0,0], "cartesian_contact": [0,0,0,0,0,0],
+  "joint_collision": [0,0,0,0,0,0,0], "cartesian_collision": [0,0,0,0,0,0],
+  "tau_ext_hat_filtered": [0.00187271,-0.700316,0.386035,0.0914781,-0.117258,-0.00667777,
+                           -0.0252562],
+  "O_F_ext_hat_K": [-2.06065,0.45889,-0.150951,-0.482791,-1.39347,0.109695],
+  "K_F_ext_hat_K": [-2.03638,-0.529916,0.228266,-0.275938,0.434583,0.0317351],
+  "theta": [0.01673,-0.763341,-0.0207471,-2.34041,-0.0304783,1.54006,0.753865],
+  "dtheta": [0,0,0,0,0,0,0],
+  "current_errors": [], "last_motion_errors": [],
+  "control_command_success_rate": 0, "robot_mode": "Idle", "time": 3781435
+}
+
+
+
+

Hint

+

If an error occurs at this point, perform the +ping test and ensure that the robot’s fail-safe +safety locking system is opened. Further information are provided in the manual shipped with +the robot.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..afd5ad8 --- /dev/null +++ b/index.html @@ -0,0 +1,330 @@ + + + + + + + Franka Control Interface Documentation — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Franka Control Interface Documentation

+
+

Note

+

The software and its documentation support two different robots, the +Franka Research 3 (FR3) and an older Franka Robotics Robot (FER or Panda).

+
+

The Franka Control Interface (FCI) can be accessed via several open source components which we +provide on GitHub. We welcome contributions and suggestions +for improvements.

+

These components are:

+
+
+
+

The source code of this documentation is also available online.

+
+

Important

+

Before you start using the FCI, please read through the documents shipped with the robot and +the Minimum system and network requirements chapter.

+
+
+

Note

+

Using a robot with system version 4.2.0 or higher requires to enable the FCI mode. To do that +open Desk -> expand the menu in the sidebar -> press ‘Activate FCI’. Further information +about Single Point of Control (SPoC) can be found in the manual shipped with the robot.

+
+
+

Contents:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/installation_linux.html b/installation_linux.html new file mode 100644 index 0000000..3528a59 --- /dev/null +++ b/installation_linux.html @@ -0,0 +1,433 @@ + + + + + + + Installation on Linux — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation on Linux

+

This chapter describes how to install libfranka and franka_ros, either +as binary packages or by building from source, and how to install a real-time +Linux kernel. franka_ros is only required if you want to control your robot +using ROS.

+
+

Note

+

While libfranka and the franka_ros packages should work on different Linux distributions, +official support is currently only provided for:

+
    +
  • Ubuntu 18.04 LTS Bionic Beaver and ROS Melodic Morenia (requires at least libfranka 0.6.0)

  • +
  • Ubuntu 20.04 LTS Focal Fossa and ROS Noetic Ninjemys (requires at least libfranka 0.8.0)

  • +
+

The following instructions are exemplary for Ubuntu 20.04 LTS system and ROS Noetic Ninjemys. +They only work in the supported environments.

+
+
+

Warning

+

We do not offer support for Ubuntu 16.04 LTS Xenial Xerus and ROS Kinetic Kame anymore, as they have reached their +end-of-life.

+
+
+

Installing from the ROS repositories

+
+

Hint

+

These packages might not always be up-to-date, as they are only synced at certain intervals. +Read the changelog at https://frankaemika.github.io to find out which libfranka version is required for +a particular robot software version. If this doesn’t match the ros-noetic-libfranka version from the +repositories, you need to build from source.

+
+

Binary packages for libfranka and franka_ros are available from the ROS repositories. +After setting up ROS Noetic, execute:

+
sudo apt install ros-noetic-libfranka ros-noetic-franka-ros
+
+
+
+
+

Building from source

+

Before building from source, please uninstall existing installations of libfranka and +franka_ros to avoid conflicts:

+
sudo apt remove "*libfranka*"
+
+
+
+

Building libfranka

+

To build libfranka, install the following dependencies from Ubuntu’s package manager:

+
sudo apt install build-essential cmake git libpoco-dev libeigen3-dev
+
+
+

Then, download the source code by cloning libfranka from GitHub.

+

For Panda you need to clone:

+
git clone --recursive https://github.com/frankaemika/libfranka # only for panda
+cd libfranka
+
+
+

By default, this will check out the newest release of libfranka. If you want to build a particular version of +libfranka instead, check out the corresponding Git tag:

+
git checkout <version>
+git submodule update
+
+
+

The above instructions for cloning libfranka only work for Panda. For Franka Research 3 you have to clone:

+
git clone --recursive https://github.com/frankaemika/libfranka --branch 0.10.0 # only for FR3
+cd libfranka
+
+
+

In the source directory, create a build directory and run CMake:

+
mkdir build
+cd build
+cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF ..
+cmake --build .
+
+
+

Optionally (but recommended), a libfranka Debian package can be built using the following command in the same directory:

+
cpack -G DEB
+
+
+

This creates libfranka-<version>-<architecture>.deb. This package can then be installed with:

+
sudo dpkg -i libfranka*.deb
+
+
+
+
+

Building the ROS packages

+

After setting up ROS Noetic, create a Catkin +workspace in a directory of your choice:

+
cd /path/to/desired/folder
+mkdir -p catkin_ws/src
+cd catkin_ws
+source /opt/ros/noetic/setup.sh
+catkin_init_workspace src
+
+
+

Then clone the franka_ros repository from GitHub:

+
git clone --recursive https://github.com/frankaemika/franka_ros src/franka_ros
+
+
+

By default, this will check out the newest release of franka_ros. If you want to build a particular version of +franka_ros instead, check out the corresponding Git tag:

+
git checkout <version>
+
+
+

Install any missing dependencies and build the packages:

+
rosdep install --from-paths src --ignore-src --rosdistro noetic -y --skip-keys libfranka
+catkin_make -DCMAKE_BUILD_TYPE=Release -DFranka_DIR:PATH=/path/to/libfranka/build
+source devel/setup.sh
+
+
+
+

Warning

+

If you also installed ros-noetic-libfranka, libfranka might be picked up from /opt/ros/noetic +instead of from your custom libfranka build!

+
+
+
+
+

Setting up the real-time kernel

+

In order to control your robot using libfranka, the controller program on +the workstation PC must run with real-time priority under a PREEMPT_RT +kernel. This section describes the procedure of patching a kernel to support +PREEMPT_RT and creating an installation package.

+
+

Note

+

NVIDIA binary drivers are not supported on PREEMPT_RT kernels.

+
+

First, install the necessary dependencies:

+
sudo apt-get install build-essential bc curl ca-certificates gnupg2 libssl-dev lsb-release libelf-dev bison flex dwarves zstd libncurses-dev
+
+
+

Then, you have to decide which kernel version to use. To find the one you are +using currently, use uname -r. Real-time patches are only available for +select kernel versions, see +https://www.kernel.org/pub/linux/kernel/projects/rt/. We recommend choosing the +version closest to the one you currently use. If you choose a +different version, simply substitute the numbers. Having decided on a version, +use curl to download the source files:

+
+

Note

+

For Ubuntu 16.04 tested with the kernel version 4.14.12:

+
curl -SLO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.12.tar.sign
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patch-4.14.12-rt10.patch.sign
+
+
+
+
+

Note

+

For Ubuntu 18.04 tested with the kernel version 5.4.19:

+
curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.4.19.tar.sign
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.4/older/patch-5.4.19-rt10.patch.sign
+
+
+
+
+

Note

+

For Ubuntu 20.04 tested with the kernel version 5.9.1:

+
curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.9.1.tar.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.9.1.tar.sign
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.9/patch-5.9.1-rt20.patch.xz
+curl -SLO https://www.kernel.org/pub/linux/kernel/projects/rt/5.9/patch-5.9.1-rt20.patch.sign
+
+
+
+

And decompress them with:

+
xz -d *.xz
+
+
+
+

Verifying file integrity

+
+

Note

+

This step is optional but recommended!

+
+

The .sign files can be used to verify that the downloaded files were not +corrupted or tampered with. The steps shown here are adapted from the +Linux Kernel Archive , see the +linked page for more details about the process.

+

You can use gpg2 to verify the .tar archives:

+
gpg2 --verify linux-*.tar.sign
+gpg2 --verify patch-*.patch.sign
+
+
+

If your output is similar to the following:

+
$ gpg2 --verify linux-*.tar.sign
+gpg: assuming signed data in 'linux-4.14.12.tar'
+gpg: Signature made Fr 05 Jan 2018 06:49:11 PST using RSA key ID 6092693E
+gpg: Can't check signature: No public key
+
+
+

You have to first download the public key of the person who signed the above +file. As you can see from the above output, it has the ID 6092693E. You can +obtain it from the key server:

+
gpg2  --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 6092693E
+
+
+

Similarly for the patch:

+
gpg2 --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2872E4CC
+
+
+

Note that keys for other kernel version might have different IDs, you will have to +adapt accordingly.

+

Having downloaded the keys, you can now verify the sources. Here is an example of +a correct output:

+
$ gpg2 --verify linux-*.tar.sign
+gpg: assuming signed data in 'linux-4.14.12.tar'
+gpg: Signature made Fr 05 Jan 2018 06:49:11 PST using RSA key ID 6092693E
+gpg: Good signature from "Greg Kroah-Hartman <gregkh@linuxfoundation.org>" [unknown]
+gpg:                 aka "Greg Kroah-Hartman <gregkh@kernel.org>" [unknown]
+gpg:                 aka "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>" [unknown]
+gpg: WARNING: This key is not certified with a trusted signature!
+gpg:          There is no indication that the signature belongs to the owner.
+Primary key fingerprint: 647F 2865 4894 E3BD 4571  99BE 38DB BDC8 6092 693E
+
+
+

See Linux Kernel Archive +for more information about the warning.

+
+
+

Compiling the kernel

+

Once you are sure the files were downloaded properly, you can extract the source +code and apply the patch:

+
tar xf linux-*.tar
+cd linux-*/
+patch -p1 < ../patch-*.patch
+
+
+

Next copy your currently booted kernel configuration as the default config for the new real time kernel:

+
cp -v /boot/config-$(uname -r) .config
+
+
+

Now you can use this config as the default to configure the build:

+
make olddefconfig
+make menuconfig
+
+
+

The second command brings up a terminal interface in which you can configure the preemption model. Navigate with the +arrow keys to General Setup > Preemption Model and select Fully Preemptible Kernel (Real-Time).

+

After that navigate to Cryptographic API > Certificates for signature checking +(at the very bottom of the list) > Provide system-wide ring of trusted keys > +Additional X.509 keys for default system keyring

+

Remove the “debian/canonical-certs.pem” from the prompt and press Ok. Save this +configuration to .config and exit the TUI.

+
+

Note

+

If you prefer GUIs over TUIs use make xconfig instead of make menuconfig

+
+

Afterwards, you are ready to compile the kernel. As this is a lengthy process, set the +multithreading option -j to the number of your CPU cores:

+
make -j$(nproc) deb-pkg
+
+
+

Finally, you are ready to install the newly created package. The exact names +depend on your environment, but you are looking for headers and images +packages without the dbg suffix. To install:

+
sudo dpkg -i ../linux-headers-*.deb ../linux-image-*.deb
+
+
+
+
+

Verifying the new kernel

+

Restart your system. The Grub boot menu should now allow you to choose your +newly installed kernel. To see which one is currently being used, see the output +of the uname -a command. It should contain the string PREEMPT RT and the +version number you chose. Additionally, /sys/kernel/realtime should exist and +contain the the number 1.

+
+

Note

+

If you encounter errors that you fail to boot the new kernel see Cannot boot realtime kernel because of “Invalid Signature”

+
+
+
+

Allow a user to set real-time permissions for its processes

+

After the PREEMPT_RT kernel is installed and running, add a group named +realtime and add the user controlling your robot to this group:

+
sudo addgroup realtime
+sudo usermod -a -G realtime $(whoami)
+
+
+

Afterwards, add the following limits to the realtime group in +/etc/security/limits.conf:

+
@realtime soft rtprio 99
+@realtime soft priority 99
+@realtime soft memlock 102400
+@realtime hard rtprio 99
+@realtime hard priority 99
+@realtime hard memlock 102400
+
+
+

The limits will be applied after you log out and in again.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/installation_windows.html b/installation_windows.html new file mode 100644 index 0000000..f84b0e1 --- /dev/null +++ b/installation_windows.html @@ -0,0 +1,198 @@ + + + + + + + Installation on Windows — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation on Windows

+
+

Caution

+

Windows support is very experimental and only intended for experienced users. Since Windows is not real-time capable, +communication errors are very likely. Further, these instructions are may not up-to-date and adjustments might be necessary.

+
+

Starting from libfranka >= 0.6.0, experimental Windows support is provided. +This chapter describes how to install libfranka on Windows. +franka_ros is not supported on Windows.

+
+

Note

+

Windows support has only been tested with Windows 10 and Visual Studio 2017.

+
+
+

Building from source

+

To build libfranka, install the following dependencies:

+
+
+
+

Both can be easily installed with vcpkg via cmd prompt:

+
cd /path/to/vcpkg
+vcpkg install eigen3
+vcpkg install poco
+
+
+

Download the source code by cloning libfranka from GitHub:

+
git clone --recursive https://github.com/frankaemika/libfranka
+
+
+

By default, this will check out the newest release of libfranka. If you want to build +a particular version of libfranka instead, check out the corresponding Git tag:

+
git checkout <version>
+git submodule update
+
+
+
+

Important

+

Only libfranka >= 0.6.0 has Windows support!

+
+

To build libfranka with Visual Studio open it as a CMake Project. +Choose File > Open > CMake > C:PathTolibfrankaCMakeLists.txt.

+

Generate the CMakeSettings.json, which contains the CMake project settings. +Select CMake > Cache > Generate > CMakeSettings.json. The file will be placed in +your main libfranka directory.

+

The next step is to solve the build dependencies. +Make sure the compiler is able to find +the required dependencies. This can be done either by copying all needed .dll libries into +the chosen build root determined by CMakeSettings.json or using cmakeCommandArgs. +Open CMake > Change CMake settings > libfranka and add

+
{
+  "cmakeCommandArgs": "-DPoco_DIR=C:\Path\To\Poco-Install\lib\cmake\Poco -DEigen3_DIR=C:\Path\To\Eigen-Install\lib\cmake\eigen3"
+}
+
+
+

Choose CMake > Build to build libfranka into the build directory, +determined in CMakeSettings.json

+
+

Hint

+

Alternatively you can build libfranka using the Developer Command Prompt for VS:

+
cd /path/to/libfranka
+mkdir build
+cd build
+cmake -DPoco_DIR=/Path/To/Poco/CMake/Config -DEigen3_DIR=/Path/To/Eigen/CMake/Config -G Ninja ..
+ninja
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/libfranka.html b/libfranka.html new file mode 100644 index 0000000..e3d51f4 --- /dev/null +++ b/libfranka.html @@ -0,0 +1,742 @@ + + + + + + + libfranka — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

libfranka

+

Before continuing with this chapter, please install or compile libfranka for Linux +or Windows. +API documentation for the latest version of libfranka is available at +https://frankaemika.github.io/libfranka.

+
+_images/libfranka-architecture.png +
+

Schematic overview

+
+
+

libfranka is the C++ implementation of the client side of the FCI. It handles the network +communication with Control and provides interfaces to easily:

+
+
    +
  • execute non-realtime commands to control the Hand and configure Arm parameters.

  • +
  • execute realtime commands to run your own 1 kHz control loops.

  • +
  • read the robot state to get sensor data at 1 kHz.

  • +
  • access the model library to compute your desired kinematic and dynamic parameters.

  • +
+
+

During operation you might also encounter several errors that we detail at the end of +this section.

+
+

Non-realtime commands

+

Non-realtime commands are blocking, TCP/IP-based and always executed outside of any realtime +control loop. They encompass all of the Hand commands and some configuration-related commands +for the Arm.

+
+_images/fci-architecture-non-realtime.png +
+

Non-realtime commands for both Arm and Hand.

+
+
+

The most relevant ones for the Hand are

+
+
    +
  • homing which calibrates the maximum grasping width of the Hand.

  • +
  • move, grasp and stop, to move or grasp with the Hand.

  • +
  • readOnce, which reads the Hand state.

  • +
+
+

Concerning the Arm, some useful non-realtime commands are:

+
+
    +
  • setCollisionBehavior which sets the contact and collision detection thresholds.

  • +
  • setCartesianImpedance and setJointImpedance which set the impedance parameters +for the internal Cartesian impedance and internal joint impedance controllers.

  • +
  • setEE sets the transformation NE_T_EE from nominal end effector to end effector +frame. The transformation from flange to end effector frame F_T_EE is split into two +transformations: F_T_NE and NE_T_EE. The transformation from flange to nominal end +effector frame F_T_NE can only be set through the administrator’s interface.

  • +
  • setK sets the transformation EE_T_K from end effector frame to stiffness frame.

  • +
  • setLoad sets the dynamic parameters of a payload.

  • +
  • automaticErrorRecovery that clears any command or control exception that previously +happened in the robot.

  • +
+
+

For a complete and fully specified list check the API documentation for the +Arm +or the Hand.

+

All operations (non-realtime or realtime) on the Arm or the Hand are performed through the +franka::Robot and franka::Gripper objects respectively. A connection to the Arm/Hand +will be established when the object is created:

+
#include <franka/robot.h>
+#include <franka/gripper.h>
+
+...
+
+franka::Gripper gripper("<fci-ip>");
+franka::Robot robot("<fci-ip>");
+
+
+

The address can be passed either as a hostname or an IP address. In case of any error, either due +to networking or conflicting library version, an exception of type franka::Exception will +be thrown. When using several robots at the same time, simply create several objects with +appropriate IP addresses.

+

To run a specific command, simply call the corresponding method, e.g.

+
gripper.homing();
+robot.automaticErrorRecovery();
+
+
+
+
+

Realtime commands

+

Realtime commands are UDP based and require a 1 kHz connection to Control. +There are two types of realtime interfaces:

+
+
    +
  • Motion generators, which define a robot motion in joint or Cartesian space.

  • +
  • Controllers, which define the torques to be sent to the robot joints.

  • +
+
+

There are 4 different types of external motion generators and 3 different types of controllers +(one external and 2 internal) as depicted in the following figure:

+
+_images/rt-interfaces.png +
+

Realtime interfaces: motion generators and controllers.

+
+
+

You can either use a single interface or combine two different types. Specifically, you can +command:

+
+
    +
  • only a motion generator and therefore use one of the two internal controllers to follow +the commanded motion.

  • +
  • only an external controller and ignore any motion generator signals, i.e. torque control only.

  • +
  • a motion generator and an external controller to use the inverse kinematics of Control in +your external controller.

  • +
+
+

All realtime loops (motion generator or controller) are defined by a callback function that +receives the robot state and the duration of the last cycle (1 ms unless packet losses occur) +and returns the specific type of the interface. The control method of the franka::Robot +class will then run the control loop by executing the callback function at a 1 kHz frequency, +as shown in this example

+
std::function<franka::Torques(const franka::RobotState&, franka::Duration)>
+   my_external_controller_callback;
+// Define my_external_controller_callback
+...
+
+std::function<franka::JointVelocities(const franka::RobotState&, franka::Duration)>
+    my_external_motion_generator_callback;
+// Define my_external_motion_generator_callback
+...
+
+try {
+  franka::Robot robot("<fci-ip>");
+  // only a motion generator
+  robot.control(my_external_motion_generator_callback);
+  // only an external controller
+  robot.control(my_external_controller_callback);
+  // a motion generator and an external controller
+  robot.control(my_external_motion_generator_callback, my_external_controller_callback);
+} catch (franka::Exception const& e) {
+  std::cout << e.what() << std::endl;
+  return -1;
+}
+  return 0;
+}
+
+
+

All control loops are finished once the motion_finished flag of a realtime command is set +to true. An excerpt of the generate_joint_velocity_motion example included +in the libfranka examples is shown here

+
robot.control(
+     [=, &time](const franka::RobotState&, franka::Duration period) -> franka::JointVelocities {
+       time += period.toSec();
+
+       double cycle = std::floor(std::pow(-1.0, (time - std::fmod(time, time_max)) / time_max));
+       double omega = cycle * omega_max / 2.0 * (1.0 - std::cos(2.0 * M_PI / time_max * time));
+
+       franka::JointVelocities velocities = {{0.0, 0.0, 0.0, omega, omega, omega, omega}};
+
+       if (time >= 2 * time_max) {
+         std::cout << std::endl << "Finished motion, shutting down example" << std::endl;
+         return franka::MotionFinished(velocities);
+       }
+       return velocities;
+     });
+
+
+

In this case, the callback function is defined directly in the call of the +robot.control( ... ) function. It uses the joint velocity motion generator interface, +as it returns a franka::JointVelocities object. It commands joint velocities to the last four +joints and move them by approx. +/-12 degrees. After 2 * time_max seconds it will return a +motion_finished flag by setting it to true with the franka::MotionFinished method and +the control loop will stop.

+

Note that if you use only a motion generator, the default controller is the internal joint +impedance controller. You can however use the internal Cartesian impedance controller by +setting the optional argument of the control function, e.g.

+
// Set joint impedance (optional)
+robot.setJointImpedance({{3000, 3000, 3000, 3000, 3000, 3000, 3000}});
+// Runs my_external_motion_generator_callback with the default joint impedance controller
+robot.control(my_external_motion_generator_callback);
+// Identical to the previous line (default franka::ControllerMode::kJointImpedance)
+robot.control(my_external_motion_generator_callback, franka::ControllerMode::kJointImpedance);
+
+// Set Cartesian impedance (optional)
+robot.setCartesianImpedance({{2000, 2000, 2000, 100, 100, 100}});
+// Runs my_external_motion_generator_callback with the Cartesian impedance controller
+robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance);
+
+
+

For writing a controller, the franka::Robot::control function is used as well. The following +example shows a simple controller commanding zero torque for each joint. Gravity is +compensated by the robot.

+
robot.control([&](const franka::RobotState&, franka::Duration) -> franka::Torques {
+      return {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
+    });
+
+
+

You can find examples for all interfaces and combinations of control loops in the +libfranka examples. Prior to running +the examples, verify that the robot has enough free space to move without colliding. Then, for +instance for the generate_joint_velocity_motion example execute the following command from +the libfranka build directory:

+
./examples/generate_joint_velocity_motion <fci-ip>
+
+
+
+

Warning

+

For writing your own motion generators or controllers it is crucial to deliver a smooth +signal to the robot. Nonsmooth signals can easily generate discontinuity errors or even +make the robot unstable. Check the interface specifications before starting.

+
+
+

Signal processing

+

To facilitate the control of the robot under non-ideal network connections, libfranka includes +signal processing functions that will modify the user-commanded values to make them conform +with the limits of the interface. +There are two optional functions included in all realtime control loops:

+
+
    +
  • A first-order low-pass filter to smooth the user-commanded signal.

  • +
  • A rate limiter, that saturates the time derivatives of the user-commanded values.

  • +
+
+
    +
  • As of version 0.5.0, libfranka includes a low-pass filter for all realtime +interfaces running by default with a 100 Hz cutoff frequency. +The filter smooths commanded signals +to provide more stable robot motions but does not prevent the violation of the +limits of the interface.

  • +
  • As of version 0.4.0, rate limiters for all realtime interfaces are +running by default. Rate limiters, also called safe controllers, will limit the +rate of change of the signals sent by the user to prevent the violation of the +limits of the interface. For motion generators, it +will limit the acceleration and jerk, while, for an external controller, it will limit the +torque rate. Their main purpose is to increase the robustness of your control loop. +In case of packet losses, even when the signals that you send conform with the +interface limits, Control might detect a violation of velocity, acceleration or jerk limits. +Rate limiting will adapt your commands to make sure that this does not happen. +Check the noncompliant errors section for more details.

    +
    +

    Caution

    +

    Rate limiting will ensure no limits are violated except for the joint limits after +inverse kinematics, whose violation produces the family of errors starting with +cartesian_motion_generator_joint_*. Check the +noncompliant errors section for more details.

    +
    +
    +

    Hint

    +

    The limits used in the rate limiter are defined in franka/rate_limiting.h +and are set to the interface limits. If this produces a jerky or unstable behavior +you can set the limits to lower values, activate the low-pass filter or reduce its cutoff +frequency.

    +
    +
  • +
+

To control the signal processing functions, all robot.control() function calls +have two additional optional parameters. The first one is a flag to activate or +deactivate the rate limiter while the second one +specifies the cutoff frequency of the first-order low-pass filter. If the cutoff frequency +>=1000.0 the filter will be deactivated. For instance

+
// Set Cartesian impedance (optional)
+robot.setCartesianImpedance({{2000, 2000, 2000, 100, 100, 100}});
+// Runs my_external_motion_generator_callback with the Cartesian impedance controller,
+// rate limiters on and low-pass filter with 100 Hz cutoff
+robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance);
+// Identical to the previous line (default true, 100.0 Hz cutoff)
+robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance, true, 100.0);
+// Runs my_external_motion_generator_callback with the Cartesian impedance controller,
+// rate limiters off and low-pass filter off
+robot.control(my_external_motion_generator_callback, franka::ControllerMode::kCartesianImpedance, false, 1000.0);
+
+
+

Or similarly for an external controller

+
// With rate limiting and filter
+robot.control(my_external_controller);
+// Identical to the previous line (default true, 100.0 Hz cutoff)
+robot.control(my_external_controller, true, 100.0);
+// Without rate limiting but with low-pass filter (100.0 Hz)
+robot.control(my_external_controller, false);
+// Without rate limiting and without low-pass filter
+robot.control(my_external_controller, false, 1000.0);
+
+
+
+

Danger

+

The low-pass filter and the rate limiter are robustness features against packet losses +to be used after you have already designed a smooth motion generator or controller. +For the first tests of a new control loop we strongly recommend to deactivate these +features. +Filtering and limiting the rate of a nonsmooth signal can yield instabilities or +unexpected behavior. Too many packet losses can also generate unstable behavior. +Check your communication quality by monitoring the control_command_success_rate +signal of the robot state.

+
+
+
+

Under the hood

+

Until now we have covered details of the interface running on the client side, i.e your own +workstation PC. The behavior of the full control loop including the Control side of the +realtime interface is shown in the following figure

+
+_images/rt-loop.png +
+

Realtime loop: from control commands to the robot desired joint torques.

+
+
+

Motion generators: all motion generator commands sent by the user have the subscript c +which stands for ‘commanded’. When a motion generator is sent, the Robot Kinematics completion +block will compute the forward/inverse kinematics of the user-commanded signal yielding the +‘desired’ signals, subscript d. If an internal controller is used, it will generate the +necessary torques \(\tau_{d}\) to track the corresponding computed d signals (the internal +joint impedance controller will follow the joint signals \(q_{d}, \dot{q}_{d}\) and the +internal Cartesian impedance controller the Cartesian ones +\({}^OT_{EE,d}, {}^O\dot{P}_{EE,d}\)) and send them to the robot joints. +All variables on the Control side of the figure, i.e. the last received c values +(after the low pass filter and the extrapolation due to packet losses, +read below for an explanation), the computed d values +and their time derivatives are sent back to the user in the robot state. This way you can +take advantage of the inverse kinematics in your own external controller and, at the same time, +it will offer you full transparency: you will always know the exact values +and derivatives that the robot received and tracked in the last sample.

+
+

Hint

+

When you are using a joint motion generator, the Robot kinematics completion block will +not modify the commanded joint values and therefore \(q_d, \dot{q}_d, \ddot{q}_d\) and +\(q_c, \dot{q}_c, \ddot{q}_c\) are equivalent. Note that you will only find the +d signals in the robot state. If you use a Cartesian motion generator, the Robot +kinematics completion block might modify the user-commanded values to avoid singularities +and therefore the desired signals \({}^OT_{EE,d}, {}^O\dot{P}_{EE,d}\) and the commanded +signals \({}^OT_{EE,c}, {}^O\dot{P}_{EE,c}, {}^O\ddot{P}_{EE,c}\) might differ. +You will find both the d and the c signals in the robot state.

+
+

External controller: if an external controller is sent, the desired joint torques commanded +by the user \(\tau_{d}\) are directly fed to the robot joints with the additional compensation +of gravity and motor friction, resulting in the following equation:

+
+

\(\tau_{c} = \tau_{d} + \tau_{f} + \tau_{g}\)

+
+

Where:

+
    +
  • \(\tau_{d}\) is the desired torque given as input by the libfranka user,

  • +
  • \(\tau_{c}\) is the torque effectively commanded to the joint,

  • +
  • \(\tau_{f}\) is the torque to compensate the motor friction,

  • +
  • \(\tau_{g}\) is the torque required for compensating the gravity of the whole kinematic chain.

  • +
+

Note that, on the Control side, there are two things that could modify your signals:

+
    +
  • Packet losses, which may occur if you:

    +
    +
      +
    • don’t have a very good connection due to the performance of your PC + network card.

    • +
    • your control loop is taking too long to compute (you have, depending on you network card and +PC configuration, approx. < 300 \(\mu s\) for your own control loop).

    • +
    +
    +

    In this case, Control assumes a constant acceleration model or a constant torque to extrapolate +your signals. If >=20 packets are lost in a row the control loop is stopped with the +communication_constraints_violation exception.

    +
  • +
+
+

Hint

+

If you are not sure if your signals are being filtered or extrapolated, you can always check the +last commanded values that you sent and compare them with the values you receive on the robot +state in the next sample. You will also find these values after an exception occurs in the +franka::ControlException::log member of the exception.

+
+
+
+
+

Robot state

+

The robot state delivers the robot sensor readings and estimated values at a 1 kHz rate. +It provides:

+
+
    +
  • Joint level signals: motor and estimated joint angles and their derivatives, +joint torque and derivatives, estimated external torque, joint collision/contacts.

  • +
  • Cartesian level signals: Cartesian pose, configured endeffector and load parameters, +external wrench acting on the endeffector, Cartesian collision

  • +
  • Interface signals: the last commanded and desired values and their derivatives, +as explained in the previous subsection.

  • +
+
+

For a complete list check the API of the franka::RobotState +here. +As shown in the the previous subsection, the robot state is always an input of all callback +functions for control loops. However, if you wish to only read the robot state without controlling +it, the functions read or readOnce can be used to gather it, e.g. for +logging or visualization purposes.

+

With a valid connection, a single sample of the robot state can be read using the readOnce +function:

+
franka::RobotState state = robot.readOnce();
+
+
+

The next example shows how to continuously read the robot state using the read function and a +callback. Returning false in the callback stops the loop. In the following, an excerpt of the +echo_robot_state example is shown:

+
size_t count = 0;
+robot.read([&count](const franka::RobotState& robot_state) {
+  // Printing to std::cout adds a delay. This is acceptable for a read loop such as this,
+  // but should not be done in a control loop.
+  std::cout << robot_state << std::endl;
+  return count++ < 100;
+});
+
+
+
+
+

Model library

+

The robot model library provides

+
+
    +
  • The forward kinematics of all robot joints.

  • +
  • The body and zero jacobian matrices of all robot joints.

  • +
  • Dynamic parameters: inertia matrix, Coriolis and centrifugal vector and gravity vector.

  • +
+
+

Note that after you load the model library, you can compute kinematic and dynamic parameters for +an arbitrary robot state, not just the current one. You can also use the model library in a non +realtime fashion, e.g. in an optimization loop. The libfranka examples include exemplary code +printing joint poses +or computing jacobians and dynamic parameters.

+
+
+

Errors

+

Using the FCI you will encounter several errors that happen either due to noncompliant +commands sent by the user, due to communication problems or due to the robot behavior. +The most relevant ones are detailed in the following subsections. +For a complete list please check the API documentation.

+
+

Hint

+

Note that, after an error occurs, you can automatically clear it and continue running your +program with the franka::Robot::automaticErrorRecovery() command without user intervention. +Check the exception string before continuing to make sure that the error is not a critical +one.

+

Some errors can also be cleared manually by toggling the external activation device or by +using the error recovery button in Desk.

+
+
+

Errors due to noncompliant commanded values

+

If the commanded values sent by the user +do not comply with the interface requirements, +one of the following errors will occur:

+
    +
  • Errors due to wrong initial values of a motion generator:

  • +
+
+
    +
  • joint_motion_generator_start_pose_invalid

  • +
  • cartesian_position_motion_generator_start_pose_invalid

  • +
  • cartesian_motion_generator_start_elbow_invalid

  • +
  • cartesian_motion_generator_elbow_sign_inconsistent

  • +
+

These errors indicate a discrepancy between the current robot values and the initial values sent +by the user. To fix these errors, make sure that your control loop starts with the last commanded +value observed in the robot state. For instance, for the joint position interface

+
double time{0.0};
+robot.control(
+ [=, &time](const franka::RobotState& robot_state, franka::Duration period) -> franka::JointPositions {
+   time += period.toSec();
+   if (time == 0) {
+     // Send the last commanded q_c as the initial value
+     return franka::JointPositions(robot_state.q_c);
+   } else {
+     // The rest of your control loop
+     ...
+   }
+ });
+
+
+
+
    +
  • Errors due to a position limit violation using a joint position/velocity motion generator, +which will produce a joint_motion_generator_position_limits_violation. Solving this error +should be simple: make sure that the values that you send are in the +limits. Cartesian +interfaces also have limits on the joint signals that result after the inverse kinematics: the +cartesian_motion_generator_joint_position_limits_violation will be triggered if the inverse +kinematics solver of Control yields a joint configuration out of the limits.

  • +
  • Errors due to velocity limits violation and discontinuity errors, which refer to +acceleration and/or jerk limits violation. If you use a joint motion generator the +possible errors are

  • +
+
+
    +
  • joint_motion_generator_velocity_limits_violation

  • +
  • joint_motion_generator_velocity_discontinuity (acceleration limit violation)

  • +
  • joint_motion_generator_acceleration_discontinuity (jerk limit violation)

  • +
+

If you use a Cartesian one, the possible errors are

+
    +
  • Cartesian limits:

    +
    +
      +
    • cartesian_motion_generator_velocity_limits_violation

    • +
    • cartesian_motion_generator_velocity_discontinuity (acceleration limit violation)

    • +
    • cartesian_motion_generator_acceleration_discontinuity (jerk limit violation)

    • +
    +
    +
  • +
  • Joint limits after the inverse kinematics

    +
    +
      +
    • cartesian_motion_generator_joint_velocity_limits_violation

    • +
    • cartesian_motion_generator_joint_velocity_discontinuity +(acceleration limit violation)

    • +
    • cartesian_motion_generator_joint_acceleration_discontinuity (jerk limit violation)

    • +
    +
    +
  • +
+

To mitigate velocity violations or discontinuity errors, make sure that the signals that +you command do not violate the limits. For every +motion generator, Control differentiates the signals sent by the user with backwards Euler. +For instance, if, using a joint position motion generator, at time \(k\) the user sends +the command \(q_{c,k}\), the resulting velocity, acceleration and jerk will be

+
    +
  • Velocity \(\dot{q}_{c,k} = \frac{q_{c,k} - q_{c,k-1}}{\Delta t}\)

  • +
  • Acceleration \(\ddot{q}_{c,k} = \frac{\dot{q}_{c,k} - \dot{q}_{c,k-1}}{\Delta t}\)

  • +
  • Jerk \(\dddot{q}_{c,k} = \frac{\ddot{q}_{c,k} - \ddot{q}_{c,k-1}}{\Delta t}\) ,

  • +
+

where \(\Delta t = 0.001\). Note that \(q_{c,k-1}, \dot{q}_{c,k-1}\) and +\(\ddot{q}_{c,k-1}\) are always sent back +to the user in the robot state as \(q_{d}, \dot{q}_{d}\) and +\(\ddot{q}_{d}\) so you will be able to +compute the resulting derivatives in advance, even in case of packet losses. Check the +section about the details of the Control side of the interface +for more details.

+

Finally, for the torque interface a torque rate limit violation triggers the error

+
    +
  • controller_torque_discontinuity

  • +
+

Control also computes the torque rate with backwards Euler, i.e. +\(\dot{\tau}_{d,k} = \frac{\tau_{d,k} - \tau_{d,k-1}}{\Delta t}\). The previous desired +torque commanded by the user is also sent back in the robot state as \(\tau_d\) +so you will also be able to compute the resulting torque rate in advance, +even in case of packet losses.

+
+
+

Hint

+

The rate limiters included in libfranka since version 0.4.0 modify the signals +sent by the user to make them conform with all these limits except for the joint limits +after the inverse kinematics. You can check the include/franka/rate_limiting.h and +src/rate_limiting.cpp for exemplary code on how to compute resulting velocity, +acceleration and jerk for all interfaces. We emphasize again that using rate limiting on a +discontinuous signal can easily yield to unstable behavior, so please make sure that +your signal is smooth enough before enabling this robustness feature.

+
+
+
+

Errors due to communication problems

+

If during a realtime loop Control does not receive any packets during 20 cycles, i.e. 20 ms, you +will receive a communication_constraints_violation error. +Note that if your connection has intermittent packet drops, it might not stop, but it could +trigger discontinuity errors even when your source signals conform with the interface +specification. +In that case, check our troubleshooting section +and consider enabling the signal processing functions +to increase the robustness of your control loop.

+
+
+

Behavioral errors

+
+

Warning

+

These monitoring features are by no means conform with any safety norm and do not +guarantee any safety to the user. They only aim for helping researchers during the +development and testing of their control algorithms.

+
+
    +
  • Reflex errors. If the estimated external torques \(\hat{\tau}_{ext}\) or forces +\({}^O\hat{F}_{ext}\) surpass the configured thresholds, a cartesian_reflex or +joint_reflex error will be triggered respectively. You can configure the thresholds +with the franka::Robot::setCollisionBehavior non realtime command.

    +
    +

    Hint

    +

    If you wish the robot to have contacts with the environment you must set the +collision thresholds to higher values. Otherwise, once you grasp an object or push +against a surface, a reflex will be triggered. Also, very fast or abrupt motions +without contacts could +trigger a reflex if thresholds are low; the external torques and forces are +only estimated values that could be innacurate depending on the robot +configuration, especially during high acceleration phases. You can monitor +their values observing \(\hat{\tau}_{ext}\) and \({}^O\hat{F}_{ext}\) +in the robot state.

    +
    +
  • +
  • Self-collision avoidance. If the robot reaches a configuration which is close to a +self-collision, it will trigger a self_collision_avoidance_violation error.

    +
    +

    Warning

    +

    This error does not guarantee that the robot will prevent a self collision at any +configuration and speed. If, using the torque interface, you drive the robot at +full speed against itself the robot might self-collide.

    +
    +
  • +
  • If the torque sensor limit is reached, a tau_j_range_violation +will be triggered. This does not guarantee that the sensor will not be damaged after any +high-torque interactions or motions but aims for preventing some of it.

  • +
  • If the maximum allowed power is reached, the power_limit_violation will be triggered. +It will prevent the robot from moving and continuing the control loop.

  • +
  • If you reach the joint or the Cartesian limits you will get +a joint_velocity_violation or a cartesian_velocity_violation error respectively.

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/libfranka_changelog.html b/libfranka_changelog.html new file mode 100644 index 0000000..d58ea3f --- /dev/null +++ b/libfranka_changelog.html @@ -0,0 +1,461 @@ + + + + + + + libfranka changelog — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

libfranka changelog

+
+

0.13.2 - 2023-12-04

+
    +
  • Hotfix: (temporary-workaround) for max-path-pose-deviation in ExternalMode for active control.

  • +
+
+
+

0.13.1 - 2023-11-30

+
    +
  • Hotfix: (temporary-workaround) for max-path-pose-deviation in ExternalMode for callback based control.

  • +
+
+
+

0.13.0 - 2023-11-16

+
    +
  • Add abstract ActiveControlBase class for the ActiveControllers to implement.

  • +
  • BREAKING Fix function naming errors in robot class.

  • +
  • BREAKING ActiveController initializer functions return ActiveControlBase

  • +
+
+
+

0.12.1 - 2023-09-20

+
    +
  • Fix install common/include typo

  • +
+
+
+

0.12.0 - 2023-09-14

+

Requires Franka Research 3 system version >= 5.2.0

+
    +
  • BREAKING ActiveControl serves now as base class for ActiveTorqueControl, that implements the torque control functionality

  • +
  • ActiveMotionGenerator class introduced to support external control loops for JointPositions, JointVelocities, CartesianPose and CartesianVelocities

  • +
+
+
+

0.11.0 - 2023-08-16

+

Requires Franka Research 3 system version >= 5.2.0

+
    +
  • BREAKING Instead of using constants, velocity limits in rate-limiter are computed based on the current desired joint position.

  • +
  • Allow external control loops by the introduction of the ActiveControl read-write-interface for torque-control

  • +
+
+
+

0.10.0 - 2022-09-02

+

Requires Franka Research 3 system version >= 5.2.0

+
    +
  • BREAKING Remove getVirtualWall and setFilters commands

  • +
  • BREAKING Disable rate-limiter by default

  • +
  • BREAKING Adapt rate limiting values in rate_limiting.h for FR3 robot

  • +
+
+
+

0.9.2 - 2022-08-31

+

Requires Panda system version >= 4.2.1

+
    +
  • Revert usage of set_version_from_git in CMakeLists.txt, using hardcoded values instead

  • +
+
+
+

0.9.1 - 2022-08-26

+

Requires Panda system version >= 4.2.1

+
    +
  • Use orthonormalized rotations in cartesianLowpassFilter, limitRate and cartesian_impedance_control example

  • +
  • Support building libfranka from outside the library, so the debian package name can be set externally.

  • +
  • Check if GitHub remote is always in sync

  • +
  • Bug fixes in rate_limiting_tests.cpp

  • +
  • Make column names in logToCSV unique, separated them by state & cmd

  • +
  • Improve error messages including current RobotMode information

  • +
+
+
+

0.9.0 - 2022-03-25

+

Requires Panda system version >= 4.2.1

+
    +
  • BREAKING Add O_ddP_O base acceleration to robot state, hardcoded to {0, 0, -9.81}.

  • +
  • BREAKING New base_acceleration_initialization_timeout, base_acceleration_invalid_reading +cartesian_spline_motion_generator_violation and +joint_via_motion_generator_planning_joint_limit_violation reflexes.

  • +
  • Adjust network error messages. Distinguish between problems resulting from:

    +
      +
    • a wrong network configuration. A message is shown after a timeout of 60 seconds.

    • +
    • a missing FCI feature or a blocked port due to Single Point of Control. An immediate error +response is shown.

    • +
    +
  • +
  • Changed signature of franka::Model::gravity to use O_ddP_O in the robot state.

  • +
+
+
+

0.8.0 - 2020-04-29

+

Requires Panda system version >= 4.0.0

+
    +
  • BREAKING Change behavior of franka::Robot::setEE. Previously, this method would set the +flange-to-end-effector transformation F_T_EE. This has been split up into two transformations: +F_T_NE, only settable in Desk, and NE_T_EE, which can be set in libfranka with setEE and +defaults to the identity transformation.

  • +
  • Add F_T_NE and NE_T_EE to franka::RobotState.

  • +
  • Add support for the cobot pump with franka::VacuumGripper.

  • +
  • CPack: Add conflict with ros-melodic-libfranka.

  • +
  • Fix missing include for Windows (#55).

  • +
+
+
+

0.7.1 - 2019-07-08

+

Requires Panda system version >= 3.0.0

+
+

Changed

+
    +
  • Fix compilation error on ARM.

  • +
+
+
+
+

0.7.0 - 2019-07-05

+

Requires Panda system version >= 3.0.0

+
+

Added

+
    +
  • Add support for using franka::Model on Linux ARM and ARM64 +(requires Panda system version >= 3.1.0).

  • +
  • Add Cartesian low-pass filter in lowpass_filter.h.

  • +
  • Add control_tools.h with helper functions for writing control loops.

  • +
+
+
+

Changed

+
    +
  • BREAKING Rename franka::CartesianPose::hasValidElbow() +to franka::CartesianPose::hasElbow().

  • +
  • BREAKING Throw std::invalid_argument in franka::Robot::control if +NaN or infinity values are returned from a control callback.

  • +
  • BREAKING Throw std::invalid_argument in low-pass filter and rate limiting +functions if invalid values are given as function parameters.

  • +
  • BREAKING Do not throw exceptions in constructors of control types anymore.

  • +
  • Take arguments by value in franka::MotionFinished.

  • +
  • Bug fixes in communication_test.cpp.

  • +
  • Export requirements for C++ features as CMake target compile features.

  • +
+
+
+
+

0.6.0 - 2019-02-06

+

Requires Panda system version >= 3.0.0

+
    +
  • Added support for Ubuntu 18.04 (Bionic).

  • +
  • EXPERIMENTAL Added support for Windows.

  • +
  • Added support for using franka::Model on Linux and Windows x86 and x64.

  • +
  • Bugfix for aborting gripper commands with franka::Gripper::stop().

  • +
  • Improved documentation for franka::Robot::setCollisionBehavior.

  • +
+
+
+

0.5.0 - 2018-08-08

+

Requires Panda system version >= 1.3.0

+
+

Motion and control interfaces

+
    +
  • BREAKING Added low-pass filter for all realtime interfaces with default cutoff frequency +of 100 Hz

  • +
  • DEPRECATED franka::Robot::setFilters in favor of franka::lowpassFilter

  • +
  • Fixed description of log record entries

  • +
+
+
+

Library

+
    +
  • Added lowpass_filter.h to public interface

  • +
+
+
+

Examples

+
    +
  • Added communication_test.cpp to evaluate network performance.

  • +
+
+
+
+

0.4.0 - 2018-06-21

+

Requires Panda system version >= 1.3.0

+
+

Motion and control interfaces

+
    +
  • BREAKING Added rate limiting of commanded values as an option in the control loop and as +a helper function. Rate limiting is activated by default, this could change the behavior of +existing programs. Only works when filters (franka::Robot::setFilters) are deactivated.

  • +
  • Added joint_move_in_wrong_direction error to franka::RobotState

  • +
  • Added first and second derivatives of last commanded signals to franka::RobotState

  • +
+
+
+

Library

+
    +
  • Added rate_limiting.h to public interface

  • +
  • Removed unnecessary CMake script from installation

  • +
+
+
+
+

0.3.0 - 2018-02-22

+

Requires Panda system version >= 1.2.0

+
+

Motion and control interfaces

+
    +
  • Added optional epsilon parameters to franka::Gripper::grasp

  • +
+
+
+

Examples

+
    +
  • Set default collision behavior, impedances, and filter frequencies

  • +
  • Removed command line parameters to simplify usage

  • +
  • Fixed locking bug in joint_impedance_control

  • +
+
+
+

Library

+
    +
  • BREAKING Changed signatures and added overloads in franka::Model

  • +
  • Added additional variables to installed CMake config

  • +
  • Updated SOVERSION to include minor version number

  • +
  • Added conflict with ros-kinetic-libfranka to Debian packaging

  • +
  • Minor fixes and improvements for API documentation

  • +
  • Fixed build errors on Clang 5.0

  • +
  • Fixed test errors for Poco >= 1.8.0

  • +
+
+
+
+

0.2.0 - 2018-01-29

+

Requires Panda system version >= 1.1.0

+
+

Motion and control interfaces

+
    +
  • Improved external torque tracking behavior.

  • +
  • Fixed discontinuities in commanding orientation changes via the cartesian +pose interface.

  • +
  • Added joint_p2p_insufficient_torque_for_planning, tau_j_range_violation, and +instability_detected errors to franka::RobotState

  • +
  • Added tau_J_d, m_ee, F_x_Cee, I_ee, m_total, F_x_Ctotal, I_total, +theta and dtheta to franka::RobotState

  • +
  • Added setFilter command to franka::Robot

  • +
  • Added support for commanding elbow positions for Cartesian motions.

  • +
  • Added stiffness frame K to franka::Model

  • +
  • BREAKING Replaced p_min and p_max of franka::VirtualWallCuboid with object_world_size

  • +
+
+
+

Error handling

+
    +
  • WARNING Not all robot errors can be recovered using the guiding button +on the robot anymore. To manually recover from such errors, please use the +Franka DESK web interface.

  • +
  • Added logs to ControlException and improved exception messages.

  • +
  • Fail earlier (by throwing exception) if any of the commanded values are +NaN or infinity.

  • +
+
+
+

Examples

+
    +
  • Added saturation to joint velocity, joint position, and joint impedance +examples.

  • +
  • Changed examples to read initial states inside control loops (after +controller switching).

  • +
  • Examples first move to an initial joint position.

  • +
  • Added new cartesian impedance and force control examples.

  • +
  • Lowered grasping force in grasp_object example

  • +
+
+
+

Library

+
    +
  • BREAKING New build-time dependency on Eigen3.

  • +
  • Changed thread priority to the maximum allowed value.

  • +
  • Prepare for the removal of the socket-init in the default constructor in +POCO releases >= 1.8.0.

  • +
  • Removed unnecessary public dependencies for libfranka.

  • +
  • CI: Run linter on examples

  • +
  • Docu: Use SVG instead of MathML for math rendering in API documentation to support Chrome.

  • +
+
+
+
+

0.1.0 - 2017-09-15

+
    +
  • Initial release

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..4ab71c7 Binary files /dev/null and b/objects.inv differ diff --git a/overview.html b/overview.html new file mode 100644 index 0000000..cfcd8cc --- /dev/null +++ b/overview.html @@ -0,0 +1,175 @@ + + + + + + + Overview — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Overview

+
+_images/fci-architecture.png +
+

Schematic overview.

+
+
+

The Franka Control Interface (FCI) allows a fast and direct low-level bidirectional connection +to the Arm and Hand. It provides the current status of the robot and enables its direct control +with an external workstation PC connected via Ethernet. +By using libfranka, our open source C++ interface, you can send real-time control values +at 1 kHz with 5 different interfaces:

+
+
    +
  • Gravity & friction compensated joint level torque commands.

  • +
  • Joint position or velocity commands.

  • +
  • Cartesian pose or velocity commands.

  • +
+
+

At the same time, you get access to 1 kHz measurements of:

+
+
    +
  • Measured joint data, such as the position, velocity and link side torque sensor signals.

  • +
  • Estimation of externally applied torques and forces.

  • +
  • Various collision and contact information.

  • +
+
+

You also get access to the robot model library which provides:

+
+
    +
  • Forward kinematics of all robot joints.

  • +
  • Jacobian matrix of all robot joints.

  • +
  • Dynamics: inertia matrix, Coriolis and centrifugal vector and gravity vector.

  • +
+
+

In addition, franka_ros connects Franka Robotics research robots with the entire ROS ecosystem. +It integrates libfranka into ROS Control. +Additionally, it includes URDF models and detailed 3D meshes of our +robots and end effectors, which allows visualization (e.g. RViz) and kinematic simulations. +MoveIt! integration makes it easy to move the robot and control +the gripper, and the provided examples show you how to control your robot using ROS.

+
+

Important

+

Data is sent over the network with a frequency of 1 kHz. Therefore, a good network connection +is required!

+
+
+

Important

+

While the FCI is active you have full, exclusive control of the Arm and Hand. This means that +you cannot use Desk or Apps at the same time as the FCI.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/requirements.html b/requirements.html new file mode 100644 index 0000000..cc32cce --- /dev/null +++ b/requirements.html @@ -0,0 +1,202 @@ + + + + + + + Minimum system and network requirements — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Minimum system and network requirements

+

This page only specifies the requirements for running the Franka Control Interface (FCI). +Additional requirements are specified in the documents that you have received with your robot.

+
+

Workstation PC

+ ++++ + + + + + + + + + + + + +

Minimum System Requirements

Operating System

Linux with PREEMPT_RT patched kernel +OR Windows 10 (experimental)

Network card

100BASE-TX

+

Since the robot sends data at 1 kHz frequency, it is important that the workstation PC is configured +to minimize latencies. For example, we recommend to +disable CPU frequency scaling. Other possible optimizations +will depend on your particular system.

+
+
+

Network

+

If possible, directly connect your workstation PC to the LAN port of Control, i.e. avoid any +intermediate devices such as switches.

+
+

Important

+

The workstation PC which commands your robot using the FCI must always be connected to the LAN +port of Control (shop floor network) and not to the LAN port of the Arm (robot network).

+
+

Having relays in between could lead to delay, jitter or packet loss. This will decrease the +performance of your controller or make it unusable.

+
+

Hint

+

The best performance can be achieved when connecting directly to the LAN port of Control. +This requires setting up a static IP for the shop floor network in the administrator’s interface +beforehand. See Setting up the network.

+
+

To control the robot, it must be guaranteed that the sum of the following time measurements is +less than 1 ms:

+
+
    +
  • Round trip time (RTT) between the workstation PC and FCI.

  • +
  • Execution time of your motion generator or control loop.

  • +
  • Time needed by the robot to process your data and step the internal controller.

  • +
+
+
+

Caution

+

If the <1 ms constraint is violated for a cycle, the received packet is dropped by +FCI. After 20 consecutively dropped packets, your robot will stop with the +communication_constraints_violation error. Current measure of communication quality +can be read from the RobotState::control_command_success_rate field.

+
+

If a motion generator command packet is dropped, the robot takes the previous waypoints and +performs a linear extrapolation (keep acceleration constant and integrate) for the missed +time step. If more than 20 packets are lost or dropped in a row, your robot will stop.

+

If a controller command packet is dropped, FCI will reuse the torques of the last successful +received packet. Again, more than 20 consecutive lost or dropped packets will cause your robot to +stop.

+
+

Hint

+

Measure the performance of your network (see Network bandwidth, delay and jitter test) and the +control or motion generator loop beforehand.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 0000000..4983e6c --- /dev/null +++ b/search.html @@ -0,0 +1,136 @@ + + + + + + Search — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Search
  • +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2023, Franka Robotics GmbH.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..aceda43 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["compatibility", "control_parameters", "faq", "fr3-certification-remarks", "franka_matlab/CHANGELOG", "franka_matlab/compatibility", "franka_matlab/franka_matlab_changelog", "franka_matlab/getting_started", "franka_matlab/index", "franka_matlab/installation", "franka_matlab/matlab_library", "franka_matlab/matlab_toolbox_dependencies", "franka_matlab/simulink_library", "franka_matlab/troubleshooting", "franka_ros", "franka_ros2", "franka_ros2_changelog", "franka_ros_changelog", "getting_started", "index", "installation_linux", "installation_windows", "libfranka", "libfranka_changelog", "overview", "requirements", "troubleshooting"], "filenames": ["compatibility.rst", "control_parameters.rst", "faq.rst", "fr3-certification-remarks.rst", "franka_matlab/CHANGELOG.md", "franka_matlab/compatibility.rst", "franka_matlab/franka_matlab_changelog.rst", "franka_matlab/getting_started.rst", "franka_matlab/index.rst", "franka_matlab/installation.rst", "franka_matlab/matlab_library.rst", "franka_matlab/matlab_toolbox_dependencies.rst", "franka_matlab/simulink_library.rst", "franka_matlab/troubleshooting.rst", "franka_ros.rst", "franka_ros2.rst", "franka_ros2_changelog.rst", "franka_ros_changelog.rst", "getting_started.rst", "index.rst", "installation_linux.rst", "installation_windows.rst", "libfranka.rst", "libfranka_changelog.rst", "overview.rst", "requirements.rst", "troubleshooting.rst"], "titles": ["Compatible versions", "Robot and interface specifications", "FAQ", "FR3 certification remarks", "Changelog:", "Compatible versions", "Franka MATLAB Toolbox changelog", "Getting started", "Franka MATLAB Toolbox", "Installation", "Matlab Library", "Matlab toolbox dependencies", "Simulink Library", "Troubleshooting", "franka_ros", "franka_ros2", "franka_ros2 changelog", "franka_ros changelog", "Getting started", "Franka Control Interface Documentation", "Installation on Linux", "Installation on Windows", "libfranka", "libfranka changelog", "Overview", "Minimum system and network requirements", "Troubleshooting"], "terms": {"differ": [0, 1, 14, 19, 20, 22, 24, 26], "open": [0, 7, 8, 9, 14, 15, 18, 19, 21, 24, 26], "sourc": [0, 4, 6, 14, 19, 22, 24, 26], "compon": [0, 9, 16, 19], "The": [0, 1, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 24, 25, 26], "follow": [0, 1, 2, 3, 4, 6, 9, 11, 14, 15, 17, 18, 20, 21, 22, 25, 26], "tabl": [0, 3], "provid": [0, 1, 9, 11, 12, 14, 15, 16, 18, 19, 20, 21, 22, 24, 26], "an": [0, 1, 4, 6, 7, 10, 12, 14, 15, 17, 18, 19, 20, 22, 23, 24, 26], "overview": [0, 3, 8, 14, 15, 19, 22], "It": [0, 12, 14, 15, 18, 19, 20, 22, 24], "i": [0, 1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], "recommend": [0, 3, 9, 12, 13, 14, 15, 18, 20, 22, 25, 26], "alwai": [0, 12, 14, 17, 18, 20, 22, 23, 25], "work": [0, 3, 7, 14, 15, 17, 18, 20, 23], "up": [0, 5, 14, 15, 17, 19, 21, 23, 25, 26], "date": [0, 20, 21], "indic": [0, 3, 15, 17, 18, 20, 22, 26], "test": [0, 3, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25], "against": [0, 2, 22], "newer": 0, "robot": [0, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 16, 17, 19, 20, 23, 24, 25], "system": [0, 1, 3, 4, 5, 6, 8, 14, 15, 18, 19, 20, 23, 26], "mean": [0, 14, 15, 17, 18, 22, 24], "guarante": [0, 22, 25, 26], "e": [0, 9, 12, 13, 14, 15, 17, 18, 22, 24, 25, 26], "0": [0, 1, 5, 7, 10, 12, 14, 15, 18, 19, 20, 21, 22, 26], "2": [0, 1, 3, 5, 7, 14, 15, 18, 19, 22, 26], "4": [0, 1, 3, 14, 15, 18, 19, 20, 22, 26], "x": [0, 4, 5, 6, 14, 15, 20], "ar": [0, 1, 4, 6, 7, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25, 26], "list": [0, 5, 12, 14, 18, 20, 22, 26], "below": [0, 1, 14, 15, 18, 22], "includ": [0, 3, 4, 6, 7, 13, 14, 15, 17, 19, 22, 23, 24], "1": [0, 1, 3, 5, 10, 14, 15, 18, 19, 20, 22, 24, 25, 26], "3": [0, 3, 5, 14, 19, 20, 22, 26], "therefor": [0, 3, 9, 12, 15, 18, 22, 24], "5": [0, 1, 3, 14, 15, 18, 19, 20, 22, 24, 26], "gripper": [0, 4, 6, 8, 14, 15, 16, 17, 22, 23, 24], "server": [0, 14, 17, 18, 20, 26], "franka_ro": [0, 1, 18, 19, 20, 21, 24, 26], "ubuntu": [0, 18, 20, 23, 26], "ro": [0, 14, 15, 16, 17, 18, 19, 23, 24, 26], "fr3": [0, 9, 14, 17, 19, 20, 23], "10": [0, 1, 3, 4, 5, 6, 9, 14, 15, 19, 20, 21, 25], "6": [0, 1, 3, 14, 15, 19, 20, 21], "20": [0, 1, 14, 19, 20, 22, 25], "04": [0, 18, 19, 20, 26], "noetic": [0, 17, 20, 26], "fer": [0, 9, 19], "9": [0, 1, 3, 4, 5, 6, 14, 19, 20], "8": [0, 3, 4, 5, 6, 14, 15, 19, 20], "7": [0, 1, 3, 5, 10, 14, 15, 19], "18": [0, 1, 14, 18, 20, 23], "melod": [0, 17, 20, 23], "16": [0, 3, 7, 14, 15, 18, 19, 20], "kinet": [0, 17, 20, 23], "line": [0, 1, 4, 6, 18, 22, 23], "17": [0, 1, 3], "part": [0, 3, 14, 15, 26], "common": [0, 15, 16, 23], "repositori": [0, 15, 18, 19, 26], "submodul": [0, 20, 21], "franka": [0, 2, 3, 4, 5, 7, 10, 12, 14, 15, 16, 17, 20, 22, 23, 24, 25, 26], "matlab": [0, 4, 7, 13, 19], "locat": [0, 2, 7, 15], "here": [0, 1, 12, 14, 15, 18, 20, 22, 26], "current": [0, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 18, 19, 20, 22, 23, 24, 25, 26], "doe": [0, 1, 12, 14, 15, 22, 26], "ani": [0, 1, 4, 6, 7, 13, 14, 15, 18, 20, 22, 23, 25, 26], "support": [0, 3, 4, 6, 9, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 26], "window": [0, 4, 6, 7, 8, 14, 15, 19, 22, 23, 25, 26], "arm": [0, 1, 3, 14, 15, 16, 17, 18, 22, 23, 24, 25, 26], "There": [0, 15, 20, 22], "which": [0, 1, 9, 12, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], "give": [0, 14, 15, 17], "focal": [0, 20], "fossa": [0, 20], "19": [0, 20], "bionic": [0, 20, 23], "14": [0, 3, 14, 18, 19, 20], "12": [0, 1, 3, 14, 19, 20, 22], "xenial": [0, 20], "xeru": [0, 20], "realtim": [1, 14, 16, 17, 19, 20, 23], "command": [1, 4, 6, 7, 8, 9, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25], "sent": [1, 14, 22, 24, 26], "should": [1, 9, 12, 14, 15, 18, 20, 22, 26], "fulfil": [1, 14], "ensur": [1, 13, 18, 22, 26], "optim": [1, 7, 14, 22, 25, 26], "oper": [1, 3, 10, 13, 14, 15, 17, 19, 22, 25, 26], "If": [1, 4, 6, 7, 12, 14, 15, 18, 20, 21, 22, 25, 26], "met": 1, "motion": [1, 8, 12, 14, 17, 18, 19, 22, 25], "abort": [1, 14, 15, 23], "final": [1, 9, 20, 22], "result": [1, 14, 15, 22, 23, 26], "process": [1, 9, 13, 18, 25], "user": [1, 3, 7, 9, 14, 15, 16, 17, 18, 21, 22, 26], "specifi": [1, 14, 15, 17, 18, 22, 25, 26], "As": [1, 3, 15, 20, 22, 26], "long": [1, 13, 22, 26], "try": [1, 7, 14, 15, 22, 26], "onli": [1, 5, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26], "match": [1, 7, 14, 17, 20], "also": [1, 3, 14, 15, 17, 18, 19, 20, 22, 24, 26], "violat": [1, 17, 22, 25, 26], "error": [1, 4, 6, 8, 12, 14, 15, 16, 17, 18, 19, 20, 21, 25, 26], "instanc": [1, 8, 14, 22], "first": [1, 4, 6, 9, 14, 18, 20, 22, 23, 26], "point": [1, 3, 13, 14, 15, 18, 19, 23, 26], "defin": [1, 12, 14, 15, 22, 26], "veri": [1, 14, 15, 20, 21, 22], "from": [1, 3, 4, 5, 6, 7, 12, 13, 14, 17, 18, 19, 22, 23, 25, 26], "start": [1, 4, 6, 8, 9, 10, 12, 13, 14, 16, 17, 19, 21, 22], "posit": [1, 3, 10, 12, 14, 16, 17, 18, 22, 23, 24], "q": [1, 10, 14, 18, 22], "t": [1, 2, 14, 17, 20, 22], "neq": 1, "q_c": [1, 22], "start_pose_invalid": 1, "valu": [1, 4, 6, 12, 14, 15, 17, 18, 23, 24, 26], "constant": [1, 14, 15, 22, 23, 25], "us": [1, 3, 7, 9, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], "equat": [1, 22], "shown": [1, 15, 18, 20, 22, 23], "section": [1, 9, 12, 14, 15, 18, 20, 22, 26], "q_": [1, 22], "min": [1, 26], "max": [1, 23, 26], "dot": [1, 22], "_": [1, 14, 22], "_c": [1, 22], "ddot": [1, 22], "dddot": [1, 22], "tau_j": [1, 14, 18], "_d": [1, 22], "At": [1, 24], "begin": 1, "c": [1, 4, 6, 8, 9, 19, 21, 22, 23, 24, 26], "end": [1, 3, 4, 6, 14, 15, 17, 20, 22, 23, 24], "proper": [1, 14], "transform": [1, 14, 15, 17, 22, 23], "matrix": [1, 14, 15, 22, 24], "p": [1, 14, 15, 20, 22, 26], "p_c": 1, "veloc": [1, 14, 16, 17, 22, 23, 24], "acceler": [1, 14, 17, 22, 23, 25], "jerk": [1, 17, 22], "deriv": [1, 14, 15, 22, 23], "invers": [1, 14, 22], "kinemat": [1, 14, 15, 22, 24], "ot_": [1, 22], "ee": [1, 14, 22], "d": [1, 14, 15, 20, 22, 26], "space": [1, 7, 14, 22], "name": [1, 2, 4, 6, 9, 12, 14, 15, 17, 18, 20, 23], "translat": [1, 15], "rotat": [1, 14, 15, 17, 23], "elbow": [1, 14, 16, 17, 18, 23], "frac": [1, 14, 22], "text": [1, 4, 6, 12, 14], "m": [1, 7, 22, 25, 26], "": [1, 7, 9, 13, 14, 15, 17, 18, 20, 22, 25, 26], "rad": [1, 14], "1750": 1, "13": [1, 3, 5, 15, 19], "25": [1, 14, 19], "6500": 1, "12500": 1, "5000": 1, "unit": [1, 16, 17, 18], "8973": 1, "7628": 1, "0698": 1, "7525": 1, "0718": 1, "0175": 1, "6100": 1, "15": [1, 3, 19], "7500": 1, "3750": 1, "6250": 1, "10000": [1, 26], "87": 1, "nm": [1, 14], "1000": [1, 14, 15, 22], "can": [1, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], "reach": [1, 20, 22, 26], "its": [1, 3, 14, 15, 19, 22, 24], "maximum": [1, 14, 15, 17, 22, 23, 26], "extens": [1, 3, 15], "when": [1, 3, 4, 6, 7, 13, 14, 15, 16, 17, 18, 22, 23, 25, 26], "ha": [1, 12, 13, 14, 15, 17, 18, 20, 21, 22, 23], "angl": [1, 14, 15, 22], "flip": 1, "where": [1, 3, 14, 15, 22, 26], "467002423653011": 1, "thi": [1, 3, 5, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], "determin": [1, 14, 21], "direct": [1, 3, 15, 18, 24, 26], "620": 1, "4500": 1, "8500": 1, "7437": 1, "7837": 1, "9007": 1, "1518": 1, "8065": 1, "5169": 1, "0159": 1, "0421": 1, "5445": 1, "62": 1, "26": [1, 19], "note": [1, 14, 20, 22], "depend": [1, 4, 6, 8, 9, 17, 19, 20, 21, 22, 23, 25, 26], "minimum": [1, 15, 17, 19, 26], "certain": [1, 14, 20], "calcul": [1, 14, 17], "most": [1, 22, 26], "planner": 1, "cannot": [1, 14, 18, 19, 20, 24], "deal": 1, "those": 1, "function": [1, 3, 4, 6, 7, 8, 10, 14, 15, 17, 19, 22, 23], "describ": [1, 9, 14, 18, 20, 21, 26], "each": [1, 14, 15, 22], "thei": [1, 7, 13, 14, 15, 20, 22, 26], "fix": [1, 4, 6, 13, 15, 17, 22, 23], "rectangular": 1, "we": [1, 3, 9, 13, 14, 15, 19, 20, 22, 25, 26], "suggest": [1, 19], "them": [1, 7, 14, 20, 22, 23], "In": [1, 7, 9, 14, 15, 18, 20, 22, 24, 26], "figur": [1, 10, 13, 22], "red": [1, 14], "blue": [1, 14, 18], "threshold": [1, 4, 6, 14, 15, 22], "while": [1, 14, 15, 17, 18, 20, 22, 24], "black": 1, "3093": 1, "5133": 1, "4937": 1, "4461": 1, "4800": 1, "2094": 1, "6895": 1, "7478": 1, "8521": 1, "These": [1, 14, 15, 19, 20, 22], "default": [1, 4, 6, 7, 14, 15, 17, 20, 21, 22, 23, 26], "rate": [1, 4, 6, 14, 17, 22, 23, 26], "urdf": [1, 14, 15, 17, 19, 24], "insid": [1, 15, 23, 26], "howev": [1, 14, 15, 22, 26], "you": [1, 3, 5, 7, 9, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 24, 25, 26], "free": [1, 4, 6, 7, 12, 14, 22], "your": [1, 2, 3, 7, 13, 15, 18, 19, 20, 21, 22, 24, 25, 26], "own": [1, 15, 17, 19, 22, 26], "rectangl": 1, "within": [1, 7, 26], "accordingli": [1, 20], "need": [1, 4, 6, 7, 9, 11, 14, 15, 17, 18, 20, 21, 25, 26], "sinc": [1, 5, 14, 17, 21, 22, 25, 26], "inher": 1, "implement": [1, 4, 6, 14, 16, 17, 22, 23], "restrict": [1, 14], "plot": 1, "abov": [1, 14, 20], "gener": [1, 3, 4, 6, 7, 9, 12, 14, 15, 17, 20, 21, 22, 25, 26], "exploit": 1, "hw": [1, 14], "capabl": [1, 4, 6, 9, 14, 21], "beyond": 1, "impos": 1, "exist": [1, 4, 6, 15, 17, 20, 23], "chain": [1, 14, 22], "craig": 1, "convent": [1, 4, 6, 14], "alpha": [1, 14], "theta": [1, 14, 17, 18, 23], "333": 1, "theta_1": 1, "pi": 1, "theta_2": 1, "316": 1, "theta_3": 1, "0825": 1, "theta_4": 1, "384": 1, "theta_5": 1, "theta_6": 1, "088": 1, "theta_7": 1, "flang": [1, 14, 15, 17, 22, 23], "107": 1, "0t_": 1, "orient": [1, 4, 6, 16, 17, 23], "frame": [1, 4, 6, 14, 15, 22, 23], "A": [1, 7, 14, 15, 22, 23, 26], "like": [1, 3, 9, 14, 15, 17, 18, 21, 26], "1t_": 1, "add": [2, 4, 6, 8, 16, 17, 20, 21, 22, 23], "cmakelist": [2, 23], "txt": [2, 21, 23], "find_packag": 2, "requir": [2, 5, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26], "target_link_librari": 2, "target": [2, 4, 6, 10, 14, 15, 17, 23], "To": [2, 14, 15, 18, 19, 20, 21, 22, 23, 25, 26], "build": [2, 4, 6, 7, 9, 13, 15, 17, 18, 19, 22, 23, 26], "custom": [2, 4, 6, 7, 15, 17, 20], "compil": [2, 5, 14, 21, 22, 23, 26], "directori": [2, 8, 18, 20, 21, 22], "dfranka_dir": [2, 17, 20], "path": [2, 9, 14, 17, 18, 20, 21, 23], "instal": [2, 3, 4, 5, 6, 8, 13, 14, 17, 19, 22, 23, 26], "usr": 2, "lib": [2, 14, 21], "don": [2, 14, 17, 22], "forget": 2, "releas": [2, 7, 15, 17, 19, 20, 21, 23, 26], "mode": [2, 3, 4, 6, 7, 12, 14, 15, 16, 19, 26], "dcmake_build_typ": [2, 15, 20, 26], "congratul": 3, "new": [3, 4, 6, 13, 15, 17, 22, 23], "research": [3, 19, 20, 22, 23, 24], "packag": [3, 4, 6, 9, 12, 13, 14, 16, 17, 19, 23, 26], "receiv": [3, 9, 19, 22, 25], "document": [3, 9, 14, 18, 22, 23, 25, 26], "product": [3, 11], "manual": [3, 7, 14, 15, 18, 19, 22, 23, 26], "110010": 3, "en": 3, "even": [3, 18, 22, 26], "though": [3, 18], "titl": 3, "other": [3, 7, 8, 14, 15, 17, 18, 20, 25], "fp3": 3, "valid": [3, 7, 22], "read": [3, 4, 6, 8, 14, 15, 16, 18, 19, 20, 22, 23, 25, 26], "befor": [3, 7, 12, 13, 14, 15, 17, 18, 19, 20, 22], "pleas": [3, 9, 14, 18, 19, 20, 22, 23, 26], "awar": 3, "especi": [3, 12, 22], "fci": [3, 7, 9, 14, 15, 17, 19, 22, 23, 24, 25, 26], "case": [3, 4, 6, 7, 12, 14, 18, 22, 26], "applic": [3, 7, 12, 14, 15, 17], "might": [3, 14, 18, 20, 21, 22, 26], "some": [3, 4, 6, 8, 11, 14, 15, 16, 17, 22], "deviat": [3, 23, 26], "consult": [3, 18], "state": [3, 4, 6, 7, 8, 14, 15, 16, 17, 18, 19, 23, 26], "check": [3, 12, 14, 17, 18, 20, 21, 22, 23, 26], "refer": [3, 8, 12, 14, 15, 18, 22], "datasheet": 3, "chapter": [3, 14, 18, 19, 20, 21, 22], "content": [3, 14], "comment": [3, 14], "about": [3, 12, 14, 18, 19, 20, 22, 26], "fulli": [3, 12, 14, 20, 22], "right": [3, 14, 18], "OF": 3, "AND": 3, "properti": [3, 14, 17, 18], "identif": [3, 14], "declar": [3, 14], "incorpor": [3, 4, 6], "scope": [3, 13], "accord": [3, 14, 17, 26], "articl": 3, "h": [3, 8, 14, 22, 23], "further": [3, 14, 15, 17, 18, 19, 21, 26], "statement": 3, "due": [3, 4, 6, 9, 12, 19, 23], "intend": [3, 7, 21], "partial": 3, "roh": 3, "2011": 3, "65": 3, "eu": 3, "j": [3, 15, 20], "regard": [3, 12], "batteri": [3, 26], "appli": [3, 4, 6, 8, 9, 13, 14, 15, 18, 20, 24], "label": 3, "equip": 3, "safeti": [3, 14, 18, 19, 22], "instruct": [3, 9, 17, 18, 20, 21, 26], "notic": 3, "liabil": 3, "With": [3, 14, 18, 22], "misus": 3, "possibl": [3, 14, 15, 16, 18, 22, 25, 26], "danger": 3, "measur": [3, 15, 24, 25], "relat": [3, 14, 17, 22, 26], "peripher": 3, "fail": [3, 7, 13, 14, 17, 18, 19, 20, 23], "safe": [3, 18, 22], "lock": [3, 17, 18, 23], "move": [3, 7, 12, 14, 17, 22, 23, 24, 26], "concept": 3, "11": [3, 18, 19, 20], "limit": [3, 4, 6, 13, 14, 17, 19, 20, 22, 23, 26], "usag": [3, 14, 17, 19, 23], "mention": [3, 15], "120020": 3, "set": [3, 4, 5, 6, 7, 8, 12, 14, 17, 19, 21, 22, 23, 25, 26], "watchman": [3, 26], "role": [3, 14, 17], "persona": 3, "deliveri": 3, "accessori": 3, "addit": [3, 4, 6, 14, 15, 17, 20, 22, 23, 24, 25], "pager": 3, "120070": 3, "avail": [3, 12, 14, 15, 17, 18, 19, 20, 22, 26], "spare": 3, "technic": 3, "specif": [3, 14, 19, 22], "partli": 3, "page": [3, 20, 25, 26], "replac": [3, 4, 6, 14, 23], "still": [3, 9, 14, 15, 26], "transport": [3, 14], "handl": [3, 4, 6, 14, 15, 22], "ambient": 3, "condit": 3, "mount": [3, 14, 15, 18], "unpack": 3, "correct": [3, 14, 20, 26], "site": 3, "prepar": [3, 19, 23], "control": [3, 4, 6, 8, 9, 13, 16, 17, 20, 22, 24, 25, 26], "wire": [3, 18], "electr": 3, "effector": [3, 4, 6, 14, 15, 17, 22, 23, 24], "practic": [3, 13], "tip": [3, 14, 15], "re": 3, "pack": 3, "switch": [3, 14, 15, 17, 23, 25, 26], "On": [3, 4, 6, 8, 14, 18], "relev": [3, 13, 22], "connect": [3, 4, 6, 14, 15, 19, 22, 24, 25], "interfac": [3, 8, 9, 14, 15, 16, 17, 18, 20, 22, 24, 25, 26], "devic": [3, 18, 22, 25], "softwar": [3, 9, 18, 19, 20, 26], "setup": [3, 7, 8, 14, 18, 19, 20, 26], "off": [3, 15, 20, 22], "restart": [3, 14, 20, 26], "WITH": 3, "basic": [3, 15], "ui": [3, 4, 6], "app": [3, 4, 6, 7, 24], "singl": [3, 14, 18, 19, 22, 23, 26], "teach": 3, "task": [3, 14], "jog": 3, "09": [3, 19], "troubleshoot": [3, 8, 19, 22], "manag": [3, 8, 15, 17, 20], "world": [3, 14, 17, 18, 19], "updat": [3, 14, 15, 16, 17, 20, 21, 23], "hub": [3, 18], "mainten": 3, "dispos": 3, "clean": 3, "lifetim": 3, "servic": [3, 14, 15, 16, 17, 26], "appendix": 3, "stop": [3, 14, 15, 17, 18, 19, 22, 23, 25], "time": [3, 4, 6, 8, 9, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 26], "distanc": [3, 14, 15], "respons": [3, 12, 23], "accuraci": 3, "glossari": 3, "index": 3, "break": [4, 6, 15, 17, 23], "standard": [4, 6, 14, 26], "oop": [4, 6], "ad": [4, 6, 14, 17], "option": [4, 6, 14, 17, 18, 20, 22, 23], "cutoff": [4, 6, 14, 22, 23], "frequenc": [4, 6, 14, 22, 23, 24, 25], "simulink": [4, 6, 7, 8, 11, 19], "block": [4, 6, 8, 12, 17, 22, 23, 26], "remov": [4, 6, 17, 20, 23], "get": [4, 6, 8, 9, 15, 19, 20, 22, 24], "initi": [4, 6, 8, 10, 12, 14, 15, 16, 17, 22, 23], "librari": [4, 5, 6, 7, 8, 14, 15, 17, 18, 19, 24], "enhanc": [4, 6], "modular": [4, 6], "structur": [4, 6, 14, 15], "easi": [4, 6, 24], "larger": [4, 6], "project": [4, 6, 7, 19, 20, 21, 26], "harwar": [4, 6], "object": [4, 6, 7, 10, 14, 15, 17, 22], "api": [4, 6, 10, 14, 18, 19, 20, 22, 23], "express": [4, 6, 26], "base": [4, 6, 9, 10, 12, 14, 15, 17, 18, 19, 22, 23], "frankarobot": [4, 6, 7, 8], "class": [4, 6, 8, 14, 15, 16, 17, 22, 23], "method": [4, 6, 14, 17, 18, 22, 23], "all": [4, 6, 7, 9, 13, 14, 15, 16, 21, 22, 23, 24, 26], "panda": [4, 6, 14, 15, 17, 19, 20, 23], "now": [4, 6, 14, 15, 17, 18, 20, 22, 23, 26], "distribut": [4, 6, 9, 20], "toolbox": [4, 5, 7, 10, 13, 15, 19], "No": [4, 6, 8, 17, 20], "script": [4, 6, 10, 14, 17, 23], "collis": [4, 6, 15, 17, 22, 23, 24], "bug": [4, 6, 14, 15, 16, 17, 23], "oldest": [4, 6], "version": [4, 6, 7, 8, 9, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], "r2021a": [4, 5, 6, 7], "nomin": [4, 6, 14, 15, 22], "ne_t_e": [4, 6, 14, 17, 22, 23], "expans": [4, 6], "gripper_st": [4, 6, 10], "gripper_hom": [4, 6, 10], "gripper_grasp": [4, 6], "gripper_mov": [4, 6, 10], "gripper_stop": [4, 6, 10], "joint_trajectory_mot": [4, 6, 10], "precomput": [4, 6], "joint": [4, 6, 14, 16, 17, 18, 19, 22, 23, 24], "trajectori": [4, 6, 14, 15, 17, 19], "creation": [4, 6], "demo": [4, 5, 6, 8, 11, 12], "pick_and_place_with_rrt": [4, 5, 6, 10, 11], "mlx": [4, 5, 6, 10, 11], "showcas": [4, 6, 14], "workflow": [4, 6, 7, 15], "approach": [4, 6, 14, 17], "bugfix": [4, 6, 23], "properli": [4, 6, 9, 17, 20, 26], "experiment": [4, 6, 9, 17, 21, 23, 25], "mainli": [4, 6], "non": [4, 6, 14, 16, 17, 18, 19], "real": [4, 6, 9, 13, 14, 15, 17, 19, 21, 24, 26], "natur": [4, 6, 14], "reli": [4, 6], "leaner": [4, 6], "tlc": [4, 6], "grt": [4, 6], "framework": [4, 6, 14], "xcp": [4, 6], "commun": [4, 6, 9, 12, 13, 21, 25, 26], "protocol": [4, 6], "vehicl": [4, 6], "network": [4, 6, 9, 14, 19, 22, 23, 24], "data": [4, 6, 20, 22, 24, 25, 26], "inspector": [4, 6], "enabl": [4, 6, 15, 17, 19, 22, 24, 26], "run": [4, 6, 7, 14, 15, 16, 17, 18, 19, 20, 22, 23, 25], "hardwar": [4, 6, 7, 14, 15, 17, 26], "deploi": [4, 6, 7], "execut": [4, 6, 8, 9, 12, 13, 14, 15, 17, 18, 19, 20, 22, 25], "back": [4, 6, 7, 15, 16, 18, 22], "over": [4, 6, 14, 15, 18, 20, 24], "simplif": [4, 6], "modifi": [4, 6, 7, 9, 15, 18, 22, 26], "rt_main": [4, 6], "cpp": [4, 6, 14, 22, 23], "extern": [4, 6, 7, 14, 15, 18, 22, 23, 24], "seper": [4, 6], "thread": [4, 6, 23], "franka_": [4, 6], "prefix": [4, 6, 14, 15], "franka_communication_test": [4, 6], "franka_joint_pos": [4, 6], "franka_robot_st": [4, 6, 15, 16], "franka_joint_point_to_point_mot": [4, 6], "joint_impedance_control": [4, 6, 23], "slx": [4, 6, 11, 12], "util": [4, 6, 13, 14, 15, 17], "torqu": [4, 6, 14, 15, 16, 17, 22, 23, 24, 25], "supoort": [4, 6], "matlab2019a": [4, 6], "till": [4, 6], "matlab2021a": [4, 6], "libfranka": [4, 6, 8, 9, 11, 12, 14, 15, 16, 17, 18, 19, 21, 24], "franka_matlab": [4, 6, 9, 11, 13], "upgrad": [4, 6], "matlab2019b": [4, 6], "matlab2020a": [4, 6], "matlab2020b": [4, 6], "lead": [4, 6, 14, 25, 26], "code": [4, 6, 8, 15, 19, 20, 21, 22, 26], "link": [4, 5, 6, 14, 15, 17, 20, 24], "dure": [4, 6, 12, 14, 15, 22, 26], "mex": [4, 6], "That": [4, 6, 9, 14], "memori": [4, 6], "corrupt": [4, 6, 20], "shut": [4, 6, 14, 17, 22], "down": [4, 6, 12, 14, 17, 18, 22], "sfunction": [4, 6], "decoupl": [4, 6], "src": [4, 6, 15, 20, 22], "necessari": [4, 6, 7, 14, 15, 20, 21, 22], "automat": [4, 6, 8, 14, 15, 16, 18, 22], "gen": [4, 6, 8], "folder": [4, 6, 13, 15, 20, 26], "treat": [4, 6, 14], "featur": [4, 6, 19, 22, 23, 26], "iterfac": [4, 6], "desir": [4, 6, 7, 10, 12, 14, 15, 17, 20, 22, 23], "plu": [4, 6], "paramet": [4, 6, 7, 12, 14, 16, 17, 19, 22, 23], "step": [4, 6, 9, 12, 14, 18, 20, 21, 25, 26], "signal": [4, 6, 12, 14, 23, 24], "through": [4, 6, 7, 8, 9, 12, 14, 15, 16, 18, 19, 22], "mask": [4, 6], "form": [4, 6, 7, 12], "model": [4, 6, 7, 8, 13, 14, 16, 17, 19, 20, 23, 24], "durat": [4, 6, 8, 14, 18, 22], "period": [4, 6, 14, 15, 22], "sampl": [4, 6, 8, 22], "secur": [4, 6, 20, 26], "1m": [4, 6], "return": [4, 6, 10, 17, 22, 23], "2m": [4, 6], "3m": [4, 6], "etc": [4, 6, 20, 26], "out": [4, 6, 14, 17, 20, 21, 22, 26], "franka_robot": [4, 6], "franka_robot_shrlib": [4, 6], "linux": [4, 6, 7, 8, 19, 22, 23, 25, 26], "ert": [4, 6], "offer": [4, 6, 14, 15, 16, 20, 22, 26], "ext": [4, 6, 14, 22], "drop": [4, 6, 9, 12, 14, 17, 18, 22, 25, 26], "main": [4, 6, 12, 21, 22], "loss": [4, 6, 22, 25, 26], "automatic_error_recoveri": [4, 6, 7, 10], "robot_ip": [4, 6, 7, 14, 15, 17], "misc": [4, 6], "button": [4, 6, 7, 9, 15, 22, 23], "callback": [4, 6, 12, 22, 23], "potenti": [4, 6, 11], "autom": [4, 6], "dev": [4, 6, 9, 15, 20], "geq": [5, 10], "r2019a": 5, "r2021b": [5, 10, 11], "have": [5, 9, 12, 13, 14, 15, 16, 18, 20, 22, 24, 25, 26], "introduc": [5, 14, 17, 23], "make": [5, 7, 9, 10, 13, 14, 15, 17, 18, 20, 21, 22, 23, 24, 25], "sure": [5, 7, 9, 10, 13, 14, 18, 20, 21, 22], "ve": [5, 26], "find": [5, 9, 14, 18, 20, 21, 22], "exampl": [7, 9, 13, 16, 17, 18, 19, 20, 22, 24, 25, 26], "feel": [7, 14], "experi": 7, "adjust": [7, 14, 15, 17, 21, 23], "expand": [7, 18, 19, 26], "fit": 7, "navig": [7, 9, 20], "type": [7, 9, 14, 16, 22, 23], "franka_matlab_toolbox_exampl": [7, 9], "after": [7, 9, 14, 15, 18, 20, 22, 23, 25, 26], "doubl": [7, 10, 14, 15, 22], "click": [7, 9, 15, 18], "load": [7, 14, 15, 17, 22, 26], "workspac": [7, 14, 15, 17, 20], "fr": [7, 10, 20], "deliv": [7, 12, 18, 22], "convert": [7, 15], "save": [7, 18, 20, 26], "172": [7, 18], "well": [7, 12, 14, 15, 22, 26], "purpos": [7, 14, 18, 22], "ip": [7, 10, 13, 14, 15, 18, 22, 25, 26], "string": [7, 9, 10, 14, 20, 22], "frankarobotset": 7, "edit": [7, 9, 18], "let": [7, 9, 14], "select": [7, 12, 18, 20, 21], "pane": [7, 12], "brake": [7, 18], "disengag": 7, "activ": [7, 14, 15, 16, 17, 18, 19, 22, 23, 24], "desk": [7, 14, 17, 19, 22, 23, 24, 26], "tab": [7, 15, 18], "either": [7, 9, 11, 14, 17, 20, 21, 22, 26], "monitor": [7, 22], "tune": [7, 14], "just": [7, 12, 13, 14, 22], "without": [7, 15, 16, 17, 20, 22, 26], "situat": 7, "readi": [7, 9, 14, 18, 20], "take": [7, 12, 14, 15, 17, 22, 23, 25], "action": [7, 14, 15, 16, 17], "altern": [7, 14, 21], "auto": [7, 9, 15], "termin": [7, 9, 13, 15, 18, 20, 26], "simulink_model_nam": 7, "ex": [7, 18], "encount": [7, 20, 22, 26], "transit": [7, 26], "reflex": [7, 14, 15, 16, 22, 23], "mai": [7, 21, 22, 26], "attempt": [7, 10], "remain": [7, 18], "erron": 7, "guid": [7, 9, 15, 18, 23], "bring": [7, 14, 20], "configur": [7, 10, 12, 14, 15, 17, 20, 22, 23, 25, 26], "checkout": [7, 13, 20, 21], "helper": [7, 19, 23], "help": [7, 22, 26], "contain": [8, 14, 15, 16, 17, 18, 19, 20, 21, 26], "tool": [8, 15, 19], "expos": [8, 9, 14], "mathwork": [8, 11], "ecosystem": [8, 14, 24], "coder": [8, 11], "rapid": [8, 13, 15, 19], "prototyp": [8, 13, 19], "algorithm": [8, 22], "simpl": [8, 22], "loop": [8, 12, 14, 16, 22, 23, 25, 26], "compat": [8, 11, 15, 17, 19], "licens": [8, 18], "recoveri": [8, 14, 22], "pick": [8, 20], "place": [8, 12, 21], "rrt": 8, "control_mod": 8, "file": [8, 14, 15, 16, 17, 21, 26], "issu": [8, 9, 19, 26], "graphic": 8, "driver": [8, 20, 26], "libstdc": 8, "so": [8, 14, 15, 18, 22, 23], "dynam": [8, 14, 15, 17, 22, 24], "number": [8, 9, 14, 20, 23, 26], "same": [9, 13, 14, 15, 17, 18, 20, 22, 24, 26], "do": [9, 14, 15, 18, 19, 20, 22, 23, 26], "For": [9, 10, 12, 13, 14, 15, 18, 20, 22, 25, 26], "higli": 9, "sudo": [9, 13, 15, 20, 26], "apt": [9, 13, 15, 17, 20, 26], "essenti": [9, 20], "cmake": [9, 15, 17, 19, 20, 21, 23], "git": [9, 15, 20, 21], "libpoco": [9, 15, 20], "libeigen3": [9, 15, 20], "local": 9, "proce": 9, "wide": [9, 14, 15, 20, 26], "deb": [9, 15, 20], "former": 9, "kernel": [9, 13, 15, 19, 25], "could": [9, 11, 12, 13, 15, 22, 23, 25, 26], "aris": 9, "lack": 9, "hard": [9, 17, 20], "schedul": 9, "visual": [9, 14, 15, 18, 21, 22, 24], "studio": [9, 21], "2017": [9, 19, 21], "english": [9, 15], "pc": [9, 18, 19, 20, 22, 24, 26], "addition": [9, 10, 12, 14, 20, 24], "must": [9, 14, 18, 20, 22, 25, 26], "vcpkg": [9, 21], "ninja": [9, 21], "environ": [9, 14, 15, 20, 22], "variabl": [9, 17, 22, 23], "search": [9, 15], "env": 9, "choos": [9, 14, 15, 17, 20, 21, 26], "under": [9, 13, 18, 20, 26], "row": [9, 22, 25], "column": [9, 12, 23], "g": [9, 10, 12, 13, 14, 15, 17, 20, 21, 22, 24, 26], "verifi": [9, 19, 22, 26], "evalu": [9, 14, 23, 26], "64bit": 9, "eigen3": [9, 17, 21, 23], "poco": [9, 21, 23], "x64": [9, 23], "netssl": 9, "drag": 9, "mltbx": 9, "complet": [9, 14, 22], "simpli": [9, 14, 20, 22], "been": [9, 12, 13, 17, 18, 21, 23], "short": [9, 14], "uniqu": [9, 14, 23], "identifi": 9, "franka_matlab_toolbox_uid_gen": 9, "send": [9, 14, 15, 22, 24, 25], "franka_matlab_toolbox_instal": 9, "glimps": 9, "what": [9, 14, 15, 22], "familiar": 10, "good": [10, 13, 15, 18, 20, 22, 24], "live": 10, "Will": [10, 14], "jp": 10, "joint_pos": 10, "element": 10, "cell": 10, "arrai": [10, 15], "r": [10, 20], "robot_st": [10, 15, 22], "struct": [10, 12], "joint_point_to_point_mot": 10, "scalar": 10, "speed": [10, 14, 15, 22, 26], "factor": [10, 26], "7xn": 10, "given": [10, 14, 15, 22, 23], "mach": 10, "pass": [10, 14, 15, 17, 22, 23, 26], "suffici": 10, "smooth": [10, 22], "continu": [10, 12, 14, 18, 22], "perform": [10, 14, 15, 17, 18, 22, 23, 25], "succes": [10, 13], "ripper_grasp": 10, "width": [10, 14, 15, 22], "forc": [10, 14, 15, 22, 23, 24], "epsilon_inn": [10, 14], "epsilon_out": [10, 14], "otherwis": [10, 14, 22], "cartesian_impedance_control": [11, 23], "force_control": 11, "stateflow": 11, "grasp_object": [11, 12, 23], "greater": [11, 14], "input": [12, 22], "output": [12, 14, 18, 20, 26], "nomenclatur": 12, "datatyp": 12, "size": 12, "definit": [12, 15, 16], "adopt": 12, "attribut": 12, "major": 12, "format": [12, 14, 15], "mostli": 12, "write": [12, 19, 22, 23], "wish": [12, 22], "captur": 12, "last": [12, 14, 22, 23, 25], "second": [12, 14, 16, 20, 22, 23], "ideal": [12, 22], "001": [12, 14, 18, 22, 26], "lost": [12, 22, 25, 26], "002": 12, "sec": 12, "003": [12, 14], "seen": [12, 15], "count": [12, 22], "chang": [12, 14, 15, 17, 18, 21, 22, 26], "mishap": 12, "increment": 12, "although": [12, 18], "realiti": [12, 14], "special": [12, 14, 15], "design": [12, 13, 22], "consider": [12, 18], "demand": 12, "sensit": 12, "look": [12, 14, 20], "generate_cartesian_pose_mot": 12, "see": [12, 14, 15, 16, 18, 20, 25, 26], "how": [12, 14, 15, 18, 19, 20, 21, 22, 24, 26], "clock": 12, "numer": [12, 14], "analyt": 12, "descript": [12, 14, 17, 19, 23], "inform": [12, 14, 15, 18, 19, 20, 23, 24, 26], "highli": [12, 26], "gripperst": 12, "categori": 13, "post": 13, "creat": [13, 15, 17, 20, 22, 26], "ones": [13, 22], "proceed": 13, "would": [13, 14, 17, 18, 23], "coupl": 13, "order": [13, 18, 20, 22, 26], "constraint": [13, 25, 26], "messag": [13, 14, 15, 16, 23], "present": 13, "known": [13, 19], "left": [13, 14], "unus": [13, 17, 25], "advis": [13, 15], "nvidia": [13, 20], "offici": [13, 14, 18, 20], "caus": [13, 18, 25], "render": [13, 14, 23], "respect": [13, 14, 15, 17, 22], "softwareopengl": 13, "avoid": [13, 14, 15, 17, 20, 22, 25], "instead": [13, 14, 15, 17, 18, 20, 21, 23], "precompil": 13, "ship": [13, 18, 19, 26], "one": [13, 14, 15, 17, 18, 20, 22, 26], "mind": 13, "multipl": [13, 14, 17], "encourag": 13, "schemat": [14, 15, 22, 24], "metapackag": 14, "integr": [14, 15, 17, 19, 24, 25], "launch": [14, 15, 16, 17], "come": [14, 15], "omit": 14, "address": [14, 18, 22, 26], "namespac": [14, 17], "wa": [14, 15, 17], "call": [14, 15, 18, 22], "catkin_w": [14, 20], "devel": [14, 20], "sh": [14, 15, 20, 26], "our": [14, 18, 19, 22, 24], "term": 14, "surfac": [14, 22], "simplifi": [14, 23], "improv": [14, 17, 19, 23], "xml": 14, "want": [14, 20, 21, 26], "simul": [14, 17, 24, 26], "gazebo": [14, 15, 17], "argument": [14, 17, 22, 23], "xacro": [14, 15, 16, 17], "Then": [14, 15, 20, 22], "estim": [14, 17, 22, 24], "inertia": [14, 17, 22, 24], "mass": 14, "tensor": 14, "retriev": 14, "feasibl": 14, "penalti": 14, "rospack": 14, "true": [14, 15, 17, 22], "two": [14, 15, 17, 18, 19, 22, 23, 26], "fine": 14, "made": [14, 17, 20], "convex": 14, "mesh": [14, 15, 16, 17, 19, 24], "approxim": 14, "drastic": 14, "dae": 14, "coars": 14, "capsul": [14, 17], "cylind": 14, "semispher": 14, "cap": 14, "attach": [14, 15, 18], "inflat": 14, "more": [14, 15, 17, 18, 20, 22, 25], "effici": 14, "comput": [14, 15, 22, 23], "intern": [14, 15, 17, 22, 25], "self": [14, 17, 22], "geometri": [14, 17], "plan": [14, 15, 17], "moveit": [14, 16, 17, 19, 24], "distinguish": [14, 23], "between": [14, 17, 18, 19, 22, 23, 25, 26], "artifici": 14, "insert": 14, "_sc": 14, "suffix": [14, 20], "panda_link0": 14, "panda_link0_sc": 14, "panda_link1": 14, "panda_link1_sc": 14, "panda_link2": 14, "panda_link2_sc": 14, "panda_link3": 14, "panda_link3_sc": 14, "panda_link4": 14, "panda_link4_sc": 14, "panda_link5": 14, "panda_link5_sc": 14, "panda_link6": 14, "panda_link6_sc": 14, "panda_link7": 14, "panda_link7_sc": 14, "panda_link8": 14, "panda_link8_sc": 14, "panda_hand": 14, "panda_leftfing": 14, "panda_rightfing": 14, "panda_hand_sc": 14, "panda_hand_tcp": 14, "via": [14, 15, 18, 19, 21, 23, 24], "fals": [14, 15, 22], "both": [14, 15, 18, 21, 22], "arg": [14, 15], "entir": [14, 24], "simulat": 14, "cours": 14, "next": [14, 18, 20, 21, 22], "franka_gripper_nod": [14, 15, 17], "node": [14, 15, 17], "publish": [14, 15, 16, 17], "moveact": 14, "graspact": 14, "tri": [14, 15], "grasp": [14, 15, 17, 22, 23], "close": [14, 15, 18, 22, 26], "success": [14, 15, 25, 26], "finger": [14, 15, 17], "epsilon_": 14, "inner": [14, 15], "outer": [14, 15], "homingact": 14, "home": [14, 15, 17, 22], "stopact": 14, "control_msg": 14, "grippercommandact": 14, "max_effort": [14, 17], "recogn": 14, "than": [14, 25, 26], "zero": [14, 15, 17, 22], "hand": [14, 15, 16, 17, 22, 24], "ignor": [14, 20, 22], "roslaunch": 14, "load_gripp": [14, 15, 17], "abstract": [14, 17, 23], "frankahw": [14, 17], "hardware_interfac": 14, "velocityjointinterfac": 14, "positionjointinterfac": 14, "level": [14, 15, 19, 22, 24], "full": [14, 22, 24], "frankaposecartesianinterfac": 14, "cartesian": [14, 16, 17, 19, 22, 23, 24], "pose": [14, 16, 17, 22, 23, 24], "frankavelocitycartesianinterfac": 14, "resourc": 14, "arm_id": [14, 15, 17], "_joint1": 14, "_joint7": 14, "_robot": 14, "By": [14, 15, 20, 21, 24], "serv": [14, 15, 17, 23], "frankacombinablehw": [14, 17], "combin": [14, 22], "arbitrari": [14, 22], "http": [14, 15, 18, 19, 20, 21, 22], "github": [14, 15, 19, 20, 21, 22, 23], "com": [14, 15, 19, 20, 21], "ros_control": 14, "frankacombinedhw": [14, 17], "kei": [14, 20, 26], "latter": 14, "allow": [14, 15, 17, 18, 22, 23, 24, 26], "effort": [14, 15, 17], "runtim": [14, 18], "thu": 14, "admiss": 14, "claim": [14, 15, 16], "everi": [14, 22], "recov": [14, 17, 23], "occur": [14, 18, 22, 26], "until": [14, 15, 22], "joint_limits_interfac": 14, "enforc": 14, "positionjointsoftlimitsinterfac": 14, "velocityjointsoftlimitsinterfac": 14, "effortjointsoftlimitsinterfac": 14, "unannounc": 14, "modif": 14, "franka_control_nod": [14, 17], "franka_combined_control_nod": [14, 17], "varieti": 14, "setjointimped": [14, 22], "stiff": [14, 15, 22, 23], "damp": [14, 15], "setcartesianimped": [14, 22], "seteefram": [14, 17], "_ee": [14, 15], "_ne": [14, 15], "split": [14, 15, 17, 22, 23], "_link8": [14, 15], "administr": [14, 15, 18, 22, 25], "setkfram": 14, "_k": [14, 15], "setforcetorquecollisionbehavior": [14, 15], "wrench": [14, 15, 22], "setfullcollisionbehavior": [14, 15], "setload": [14, 15, 17, 22], "compens": [14, 17, 22, 24], "std_srv": [14, 15], "trigger": [14, 15, 22], "disconnect": [14, 17, 18], "onc": [14, 15, 20, 22], "resum": 14, "again": [14, 15, 17, 20, 22, 25, 26], "denot": [14, 15], "mark": [14, 15], "center": [14, 15, 18], "imped": [14, 16, 17, 22, 23], "neither": [14, 15], "nor": [14, 15], "errorrecoveryact": 14, "achiev": [14, 18, 25], "client": [14, 18, 22], "goal": 14, "topic": [14, 15, 17], "rostop": 14, "pub": [14, 20], "error_recoveri": [14, 17], "errorrecoveryactiongo": 14, "were": [14, 15, 20], "die": 14, "mandatori": 14, "besid": 14, "frankastatecontrol": 14, "rviz": [14, 15, 17, 24], "robot_state_publish": 14, "yaml": [14, 15, 17], "robot_hardwar": 14, "panda_1": 14, "panda_2": 14, "joint_nam": 14, "panda_1_joint1": 14, "panda_1_joint2": 14, "panda_1_joint3": 14, "panda_1_joint4": 14, "panda_1_joint5": 14, "panda_1_joint6": 14, "panda_1_joint7": 14, "print": [14, 15, 16, 17, 18, 22], "warn": [14, 15, 17, 18, 20, 23], "joint_limit_warning_threshold": 14, "rate_limit": [14, 22, 23], "low": [14, 17, 19, 22, 23, 24], "filter": [14, 17, 22, 23], "deactiv": [14, 22, 23], "cutoff_frequ": 14, "joint_imped": 14, "cartesian_imped": 14, "internal_control": 14, "behavior": [14, 17, 23], "collision_config": 14, "lower_torque_thresholds_acceler": 14, "upper_torque_thresholds_acceler": 14, "lower_torque_thresholds_nomin": 14, "upper_torque_thresholds_nomin": 14, "lower_force_thresholds_acceler": 14, "n": [14, 17], "upper_force_thresholds_acceler": 14, "lower_force_thresholds_nomin": 14, "upper_force_thresholds_nomin": 14, "panda_2_joint1": 14, "panda_2_joint2": 14, "panda_2_joint3": 14, "panda_2_joint4": 14, "panda_2_joint5": 14, "panda_2_joint6": 14, "panda_2_joint7": 14, "Be": 14, "consist": [14, 17, 18], "id": [14, 15, 20], "combined_robot_hw": 14, "combinedrobothw": 14, "import": [14, 25], "config": [14, 16, 17, 20, 21, 23], "default_combined_control": 14, "parameter": [14, 17], "panda_1_state_control": 14, "publish_r": 14, "30": [14, 19], "hz": [14, 22, 23], "panda_2_state_control": 14, "franka_combined_control": 14, "your_robot_ips_as_a_map": 14, "path_to_your_robot_descript": 14, "xacro_args_passed_to_the_robot_descript": 14, "robot_id": 14, "name_of_your_multi_robot_setup": 14, "hw_config_fil": 14, "path_to_your_hw_config_fil": 14, "controllers_fil": 14, "path_to_your_default_controller_parameter": 14, "controllers_to_start": 14, "list_of_default_controllers_to_start": 14, "joint_states_source_list": 14, "list_of_sources_to_fuse_a_complete_joint_states_top": 14, "style": 14, "map": 14, "arm_id_1": 14, "my_ip_1": 14, "arm_id_2": 14, "my_ip_2": 14, "pure": 14, "franka_joint_state_publish": 14, "parallel": 14, "impli": 14, "alongsid": [14, 26], "separ": [14, 23, 26], "program": [14, 18, 20, 22, 23], "depict": [14, 22], "stand": [14, 22], "alon": 14, "joint_impedance_example_control": [14, 15], "wai": [14, 18, 22], "dual_arm_cartesian_impedance_example_control": 14, "name_of_the_2_arm_setup": 14, "rqt": 14, "assum": [14, 20, 22], "dual_panda_exampl": 14, "meter": [14, 15], "top": [14, 26], "box": 14, "whether": [14, 15], "gui": [14, 15, 18, 20, 26], "onlin": [14, 18, 19], "adapt": [14, 16, 18, 20, 22, 23], "reconfigur": [14, 17], "becaus": [14, 19, 20], "abl": [14, 18, 21, 22], "gazebo_ro": 14, "dive": 14, "stone": [14, 17], "b": 14, "sdf": [14, 17], "cartesian_impedance_example_control": 14, "similar": [14, 17, 20], "cm": 14, "moveactiongo": 14, "08": [14, 19], "around": [14, 15, 26], "interact": [14, 17, 18, 22], "marker": [14, 17], "gizmo": 14, "white": 14, "strang": 14, "nullspac": 14, "nullspace_stiff": 14, "excerpt": [14, 22], "50": [14, 15], "heavi": 14, "graspactiongo": 14, "03": [14, 15, 18, 19], "epsilon": [14, 15, 17, 23], "005": [14, 15], "menu": [14, 18, 19, 20, 26], "go": [14, 18], "view": 14, "contact": [14, 18, 22, 24], "succeed": 14, "hold": [14, 18], "probabl": 14, "toler": 14, "too": [14, 22, 26], "small": 14, "gentli": 14, "dropoff": 14, "area": [14, 26], "pad": 14, "stopactiongo": 14, "disappear": 14, "anymor": [14, 15, 20, 23], "lot": 14, "spawn": [14, 15], "empty_world": 14, "paus": 14, "use_sim_tim": 14, "group": [14, 15, 17, 20], "y": [14, 15, 20], "force_example_control": 14, "quit": 14, "architectur": [14, 20], "understand": 14, "plugin": [14, 15, 16], "robotsimtyp": 14, "gazebo_ros_control": 14, "filenam": 14, "libgazebo_ros_control": 14, "robotnamespac": 14, "controlperiod": 14, "self_collid": 14, "spawner": 14, "scan": 14, "none": 14, "effortjoint": 14, "frankast": [14, 17], "yet": [14, 18], "explan": [14, 22], "set_joint_imped": 14, "directli": [14, 15, 22, 25, 26], "set_cartesian_imped": 14, "set_ee_fram": [14, 17], "mathrm": 14, "ne": 14, "mathbf": 14, "homogen": 14, "normal": [14, 17], "f": [14, 15, 22], "ident": [14, 17, 22, 23], "z": [14, 15], "45": [14, 15], "offset": [14, 17], "34": 14, "overwrit": 14, "set_k_fram": [14, 17], "k": [14, 22, 23], "set_force_torque_collision_behavior": [14, 15], "set_full_collision_behavior": [14, 15], "Not": [14, 23], "set_load": [14, 15], "graviti": [14, 17, 22, 23, 24], "m_load": [14, 18], "i_load": [14, 18], "f_x_load": 14, "set_user_stop": [14, 17], "setbool": 14, "press": [14, 15, 18, 19, 20, 26], "fed": [14, 22], "done": [14, 15, 21, 22], "five": 14, "gripper_act": [14, 15, 17], "succe": 14, "miss": [14, 17, 20, 23, 25], "oscil": 14, "correspond": [14, 18, 20, 21, 22, 26], "transmiss": 14, "expect": 14, "infer": 14, "finger1": 14, "gain": [14, 17], "proport": 14, "differenti": [14, 22], "finger2": 14, "width_toler": 14, "mm": 14, "becom": [14, 17], "resting_threshold": 14, "consecutive_sampl": 14, "mani": [14, 22], "consecut": [14, 25], "access": [14, 15, 16, 18, 19, 22, 24], "tag": [14, 16, 17, 20, 21], "usual": [14, 26], "_jointn": 14, "integ": 14, "mimic": [14, 17], "_transmiss": 14, "transmission_interfac": 14, "simpletransmiss": 14, "hardwareinterfac": 14, "actuat": 14, "_motor": 14, "od": 14, "providefeedback": 14, "bit": 14, "seven": 14, "_franka_st": 14, "_joint2": 14, "_joint3": 14, "_joint4": 14, "_joint5": 14, "_joint6": 14, "_motor1": 14, "_motor2": 14, "_motor3": 14, "_motor4": 14, "_motor5": 14, "_motor6": 14, "_motor7": 14, "field": [14, 18, 25], "o_t_e": [14, 18], "o_t_ee_d": [14, 18], "f_t_ee": [14, 17, 18, 22, 23], "f_t_ne": [14, 17, 22, 23], "ee_t_k": [14, 18, 22], "m_ee": [14, 17, 18, 23], "inerti": [14, 17], "found": [14, 15, 18, 19, 26], "overwritten": 14, "i_e": [14, 17, 18, 23], "f_x_cee": [14, 17, 18, 23], "origin": [14, 15], "f_x_cload": [14, 18], "m_total": [14, 17, 18, 23], "i_tot": [14, 17, 18, 23], "f_x_ctotal": [14, 17, 18, 23], "elbow_d": [14, 18], "elbow_c": 14, "delbow_d": 14, "delbow_c": 14, "tau_j_d": [14, 17, 18, 23], "dtau_j": [14, 18], "q_d": [14, 18, 22], "dq": [14, 18], "dq_d": [14, 18], "ddq_d": 14, "joint_contact": [14, 18], "mid": 14, "hat": [14, 22], "tau": [14, 22], "thresh": 14, "lower": [14, 17, 22, 23, 26], "joint_collis": [14, 18], "upper": 14, "cartesian_contact": [14, 18], "cartesian_collis": [14, 18], "tau_ext_hat_filt": [14, 17, 18], "exponenti": 14, "averag": [14, 26], "ema": 14, "tau_": [14, 22], "j_d": 14, "o_f_ext_hat_k": [14, 18], "o": [14, 22], "j_o": 14, "cdot": 14, "k_f_ext_hat_k": [14, 18], "j_k": 14, "o_dp_ee_d": 14, "o_ddp_0": 14, "gravity_vector": [14, 17], "o_t_ee_c": 14, "o_dp_ee_c": 14, "o_ddp_ee_c": 14, "soft": [14, 17, 20], "dtheta": [14, 17, 18, 23], "current_error": [14, 18], "last_motion_error": [14, 18], "control_command_success_r": [14, 18, 22, 25], "robot_mod": [14, 18], "root": [14, 21], "panda_joint1": 14, "panda_joint8": 14, "_franka_model": 14, "_motor_root": 14, "_motor_tip": 14, "themselv": 14, "kdl": [14, 15], "jacobian": [14, 15, 17, 22, 24], "panda_finger_joint1": 14, "panda_finger_joint2": 14, "max_contact": 14, "snap": 14, "penetr": 14, "depth": 14, "keep": [14, 15, 17, 18, 25], "stabl": [14, 18, 20, 22], "max_vel": 14, "min_depth": 14, "rubber": 14, "mu": [14, 22], "mu2": 14, "bounc": 14, "primarili": 14, "ros_plan": 14, "repo": [14, 15], "detail": [14, 15, 18, 20, 22, 24], "tutori": [14, 18], "websit": 14, "controller_interfac": 14, "multiinterfacecontrol": 14, "four": [14, 22], "nameofyourcontrollerclass": 14, "my_mandatory_first_interfac": 14, "my_possible_second_interfac": 14, "my_possible_third_interfac": 14, "my_possible_fourth_interfac": 14, "bool": [14, 17], "init": [14, 17, 23], "robothw": 14, "nodehandl": 14, "nh": 14, "void": [14, 18], "const": [14, 17, 22], "claimabl": 14, "sens": 14, "simultan": 14, "subject": 14, "franka_combinable_hw": [14, 17], "frankacartesianposeinterfac": 14, "frankacartesianvelocityinterfac": 14, "idea": 14, "behind": 14, "later": 14, "One": [14, 15], "stream": [14, 17], "built": [14, 20], "solv": [14, 21, 22], "least": [14, 20], "inherit": 14, "virtual": 14, "shutdown": [14, 17], "gentl": 14, "slowdown": 14, "equival": [14, 15, 22, 26], "jump": [14, 26], "high": [14, 22, 26], "better": [14, 26], "export": [14, 15, 23], "correctli": [14, 18, 26], "pluginlib": 14, "class_list_macro": 14, "pluginlib_export_class": 14, "name_of_your_controller_packag": 14, "controllerbas": 14, "name_of_your_controller_librari": 14, "base_class_typ": 14, "exemplari": [14, 20, 22], "your_custom_controller_nam": 14, "additional_example_paramet": 14, "controller_spawn": 14, "hardware_manag": 14, "develop": [15, 21, 22, 26], "anticip": 15, "report": 15, "humbl": [15, 16], "desktop": 15, "preempt_rt": [15, 20, 25], "strongli": [15, 18, 22], "cartesian_pos": 15, "joint_posit": 15, "elbow_posit": 15, "absolut": 15, "minim": [15, 16, 25], "clone": [15, 20, 21], "frankaemika": [15, 19, 20, 21, 22], "recurs": [15, 20, 21], "cd": [15, 18, 20, 21], "mkdir": [15, 20, 21], "dbuild_test": [15, 20], "nproc": [15, 20], "cpack": [15, 20, 23], "dpkg": [15, 20], "color": 15, "put": 15, "rcutils_colorized_output": 15, "languag": 15, "american": 15, "lc_numer": 15, "en_u": 15, "utf": 15, "ros2": 15, "asset": 15, "msg": [15, 16, 17], "ament": 15, "clang": [15, 23], "python3": 15, "colcon": 15, "franka_ros2_w": 15, "opt": [15, 20, 26], "bash": 15, "frankaros2": 15, "imag": [15, 20], "devcontainer_setup": 15, "parent": 15, "mv": 15, "ctrl": 15, "shift": 15, "prompt": [15, 20, 21], "bar": [15, 26], "rebuild": 15, "reopen": 15, "ros_entrypoint": 15, "everyth": [15, 18], "py": [15, 16, 17], "motionplan": 15, "displai": 15, "dummi": 15, "dont": 15, "care": 15, "use_fake_hardwar": 15, "wait": [15, 17], "green": 15, "turn": 15, "planningscen": 15, "few": [15, 18], "disabl": [15, 18, 23, 25], "move_to_start_example_control": 15, "simplest": [15, 18], "weight": 15, "gravity_compensation_example_control": 15, "movement": [15, 26], "compliant": 15, "lma": [15, 16], "oroco": 15, "solver": [15, 22], "joint_impedance_with_ik_example_control": 15, "corioli": [15, 22, 24], "vector": [15, 22, 24], "joint4": 15, "bodi": [15, 22], "model_example_control": 15, "initial_joint_posit": 15, "initialization_flag_": 15, "size_t": [15, 22], "initial_q_": 15, "state_interfac": 15, "get_valu": 15, "joint_position_example_control": 15, "4th": 15, "5th": 15, "joint_velocity_example_control": 15, "cartesianpos": [15, 23], "cartesian_pose_example_control": 15, "cartesianorient": 15, "axi": [15, 17], "cartesian_orientation_example_control": 15, "cartesian_elbow_example_control": 15, "cartesianveloc": [15, 23], "cartesian_velocity_example_control": 15, "cartesianelbow": 15, "elbow_example_control": 15, "adher": 15, "propos": 15, "use_rviz": 15, "apart": 15, "joint_state_broadcast": 15, "establish": [15, 22], "effort_command_interfac": 15, "load_control": 15, "set_controller_st": 15, "inact": 15, "visualize_franka": 15, "send_goal": 15, "panda_gripp": 15, "00": [15, 26], "per": 15, "explicitli": [15, 26], "01": [15, 18, 19], "srv": 15, "ros2_control": 15, "side": [15, 16, 18, 22, 24], "initial_posit": 15, "franka_robot_model_interfac": 15, "pointer": 15, "rather": 15, "franka_robot_model": [15, 16], "store": 15, "hardware_state_interfac": 15, "broadcast": [15, 16], "loan": 15, "frankasemanticcomponentinterfac": 15, "boolean": 15, "flag": [15, 17, 22], "constructor": [15, 17, 23, 26], "is_elbow_act": 15, "cartesianposeinterfac": 15, "cartesian_pose_interfac": 15, "std": [15, 22, 23], "getinitialposematrix": 15, "quaternion": [15, 17], "eigen": [15, 16, 21], "quaterniond": 15, "vector3d": 15, "tie": 15, "getinitialorientationandtransl": 15, "assign_loaned_command_interfac": 15, "assign_loaned_state_interfac": 15, "on_activ": 15, "assign": [15, 26], "comamand": 15, "command_interfaces_": 15, "state_interfaces_": 15, "setcommand": 15, "Or": [15, 22], "cartesianvelocityinterfac": 15, "cartesian_velocity_interfac": 15, "cartesian_veloc": 15, "franka_launch": 15, "moveit2": [15, 16], "panda_manipul": 15, "degre": [15, 22], "face": 15, "forward": [15, 22, 24], "easier": 15, "panda_arm": [15, 17], "backward": [15, 22], "old": [15, 16], "joint_trajectory_control": [15, 16], "frankahardwareinterfac": 15, "pull": 15, "request": 15, "soon": 15, "delet": [15, 26], "master": 15, "branch": [15, 20], "backport": 15, "fundament": 15, "previous": [15, 17, 22, 23], "renam": [15, 17, 23], "futur": 15, "effortlessli": 15, "multi": 15, "preemptabl": 15, "feedback": [15, 26], "compar": [15, 22], "reduc": [15, 22], "cast": 15, "franka_model": 15, "quantiti": 15, "advertis": 15, "service_serv": 15, "set_cartesian_stiff": 15, "set_joint_stiff": 15, "set_paramet": 15, "set_parameters_atom": 15, "set_stiffness_fram": 15, "set_tcp_fram": 15, "setjointstiff": 15, "setcartesianstiff": 15, "settcpfram": 15, "setstiffnessfram": 15, "set_joint_stif": 15, "fness": 15, "joint_stiff": 15, "idl": [15, 18], "param": 15, "fake_hardwar": 15, "franka_example_control": [16, 17, 19], "orocoskdl": 16, "ik": 16, "franka_hardwar": 16, "regist": [16, 18], "franka_semantic_compon": 16, "hotfix": [16, 23], "patch": [16, 20, 25], "jtc": 16, "acknowledg": 16, "franka_descript": [16, 17, 19], "v0": 16, "franka_bringup": 16, "semant": 16, "franka_msg": [16, 17, 19], "ci": [16, 23], "jenkin": 16, "joint_effort_trajectory_control": 16, "pr": 16, "variou": [16, 24], "franka_moveit_config": 16, "franka_gripp": [16, 17, 19], "mismatch": 16, "logger": 16, "extend": 17, "teleop_joint_pd_example_control": 17, "leader": 17, "move_to_start": 17, "refactor": 17, "franka_control": [17, 19], "franka_exampl": 17, "finit": 17, "machin": 17, "align": 17, "track": [17, 22, 23], "wall": 17, "_linkn_sc": 17, "modul": 17, "suit": 17, "tcp_xyz": 17, "tcp_rpy": 17, "tcp": [17, 22], "_manipul": 17, "accept": [17, 22], "xacro_arg": 17, "franka_gazebo": [17, 19], "goe": 17, "delayed_controller_spawn": 17, "favor": [17, 23], "controller_manag": 17, "o_ddp_o": [17, 23], "81": [17, 23], "joint_move_in_wrong_direct": [17, 23], "cartesian_spline_motion_generator_viol": [17, 23], "joint_via_motion_generator_planning_joint_limit_viol": [17, 23], "base_acceleration_initialization_timeout": [17, 23], "base_acceleration_invalid_read": [17, 23], "jointposit": [17, 22, 23], "jointveloc": [17, 22, 23], "joint_": 17, "_exampl": 17, "indefinit": 17, "joint_state_desir": 17, "singular": [17, 22], "primit": 17, "frankahwsim": 17, "tau_ext_lowpass_filt": 17, "realist": 17, "life": [17, 20], "april": 17, "threadsaf": 17, "loader": [17, 26], "connected_to": 17, "panda_gazebo": 17, "macro": 17, "franka_hw": [17, 19], "haserror": 17, "member": [17, 22], "reset": 17, "buffer": 17, "upon": 17, "franka_combined_hw": 17, "reconnect": 17, "mix": 17, "mutex": 17, "settabl": [17, 23], "extract": [17, 20], "panda_arm_hand": 17, "dual": [17, 26], "teleoper": 17, "franka_control_servic": 17, "volum": 17, "invalid": [17, 19, 20, 23], "joint8": 17, "pars": 17, "controllermod": [17, 22], "mulitpl": 17, "whole": [17, 18, 22], "robotst": [17, 22, 23, 25], "franka_state_control": 17, "interpol": 17, "slerp": 17, "panda_moveit_config": [17, 19], "morenia": [17, 20], "rais": 17, "rosparam": 17, "realtimeconfig": [17, 26], "frankamodelhandl": 17, "78": 17, "ninjemi": [17, 20], "joint_limit": 17, "effort_joint_trajectory_control": 17, "default_control": 17, "launchfil": 17, "fake": 17, "panda_moveit": 17, "condition": 17, "srdf": 17, "signatur": [17, 19, 20, 23], "collada": 17, "linker": 17, "older": [17, 19, 20], "franka_visu": [17, 19], "catkin": [17, 20], "build_export_depend": 17, "message_runtim": 17, "subdirectori": 17, "test_depend": 17, "rule": [17, 26], "thing": [18, 22], "power": [18, 22, 26], "toppl": 18, "fast": [18, 22, 24], "abrupt": [18, 22], "tabletop": 18, "perpendicular": 18, "ground": 18, "warranti": 18, "damag": [18, 22], "cabl": 18, "firmli": 18, "motor": [18, 22], "emerg": 18, "exhaust": 18, "dedic": [18, 26], "carefulli": 18, "lan": [18, 25, 26], "port": [18, 23, 25, 26], "shop": [18, 25], "floor": [18, 22, 25], "alreadi": [18, 22, 26], "synchron": 18, "account": 18, "compani": 18, "univers": 18, "offlin": 18, "crucial": [18, 22], "appear": 18, "static": [18, 25], "netmask": 18, "24": [18, 19, 26], "certif": [18, 19, 20], "browser": 18, "enter": [18, 26], "dhcp": 18, "deselect": 18, "successfulli": [18, 26], "prefer": [18, 20], "doubt": 18, "widget": [18, 26], "ethernet": [18, 24], "ipv4": 18, "longer": 18, "obtain": [18, 19, 20, 26], "bandwidth": [18, 19, 25], "delai": [18, 19, 22, 25], "jitter": [18, 19, 25], "panel": 18, "internet": 18, "share": 18, "unlock": 18, "relas": 18, "led": 18, "sidebar": [18, 19, 26], "pop": 18, "spoc": [18, 19, 26], "show": [18, 22, 24, 26], "previou": [18, 22, 25], "echo_robot_st": [18, 22], "decid": [18, 20, 26], "programm": 18, "easiest": [18, 26], "copi": [18, 20, 21], "consol": 18, "iter": 18, "explain": [18, 22, 26], "998578": 18, "0328747": 18, "0417381": 18, "0335224": 18, "999317": 18, "0149157": 18, "04122": 18, "016294": 18, "999017": 18, "305468": 18, "00814133": 18, "483198": 18, "998582": 18, "0329548": 18, "041575": 18, "0336027": 18, "999313": 18, "0149824": 18, "0410535": 18, "0163585": 18, "999023": 18, "305444": 18, "00810967": 18, "483251": 18, "7071": 18, "1034": 18, "73": 18, "0025": 18, "0017": 18, "0207622": 18, "0206678": 18, "00359774": 18, "08582": 18, "105732": 18, "21": [18, 19], "8135": 18, "63253": 18, "18121": 18, "0481953": 18, "54": 18, "0161": 18, "9808": 18, "64": [18, 26], "6899": 18, "2609": 18, "1561": 18, "28": [18, 19], "5654": 18, "1858": 18, "0167305": 18, "762614": 18, "34352": 18, "0305686": 18, "53975": 18, "753872": 18, "00785939": 18, "00189343": 18, "00932415": 18, "0135431": 18, "00220327": 18, "00492024": 18, "00213604": 18, "0167347": 18, "762775": 18, "0305677": 18, "753862": 18, "00187271": 18, "700316": 18, "386035": 18, "0914781": 18, "117258": 18, "00667777": 18, "0252562": 18, "06065": 18, "45889": 18, "150951": 18, "482791": 18, "39347": 18, "109695": 18, "03638": 18, "529916": 18, "228266": 18, "275938": 18, "434583": 18, "0317351": 18, "01673": 18, "763341": 18, "0207471": 18, "34041": 18, "0304783": 18, "54006": 18, "753865": 18, "3781435": 18, "ping": 18, "sever": [19, 22], "welcom": 19, "contribut": 19, "Its": 19, "io": [19, 20, 22], "collect": 19, "3d": [19, 24], "outsid": [19, 22, 23], "franka_ros2": 19, "beta": 19, "togeth": 19, "artifact": 19, "higher": [19, 22, 26], "workstat": [19, 20, 22, 24, 26], "changelog": [19, 20], "2023": 19, "2022": 19, "02": 19, "31": 19, "2020": 19, "29": 19, "2019": 19, "07": 19, "05": [19, 20], "06": [19, 20], "2018": [19, 20], "22": 19, "2021": 19, "prerequisit": 19, "2024": 19, "23": 19, "denavit": 19, "hartenberg": 19, "remark": 19, "boot": [19, 20], "timeout": [19, 23], "discontinu": [19, 22, 23], "communication_constraints_viol": [19, 22, 25], "reachabl": 19, "udp": [19, 22], "incompat": 19, "reject": 19, "preempt": [19, 20], "faq": 19, "my": 19, "binari": 20, "lt": 20, "beaver": 20, "kame": 20, "sync": [20, 23], "interv": 20, "particular": [20, 21, 25], "doesn": 20, "uninstal": 20, "conflict": [20, 22, 23], "download": [20, 21], "newest": [20, 21], "debian": [20, 23], "choic": 20, "catkin_init_workspac": 20, "rosdep": 20, "rosdistro": 20, "skip": 20, "catkin_mak": 20, "prioriti": [20, 23], "procedur": 20, "bc": 20, "curl": 20, "ca": 20, "gnupg2": 20, "libssl": 20, "lsb": 20, "libelf": 20, "bison": 20, "flex": 20, "dwarv": 20, "zstd": 20, "libncurs": 20, "unam": 20, "www": 20, "org": 20, "rt": 20, "closest": 20, "substitut": 20, "slo": 20, "v4": 20, "tar": 20, "xz": 20, "sign": 20, "rt10": 20, "v5": 20, "rt20": 20, "And": 20, "decompress": 20, "tamper": 20, "archiv": 20, "gpg2": 20, "gpg": 20, "jan": 20, "49": 20, "pst": 20, "rsa": 20, "6092693e": 20, "public": [20, 23], "person": 20, "who": 20, "keyserv": 20, "hkp": 20, "80": 20, "recv": 20, "similarli": [20, 22], "2872e4cc": 20, "greg": 20, "kroah": 20, "hartman": 20, "gregkh": 20, "linuxfound": 20, "unknown": 20, "aka": 20, "certifi": 20, "trust": 20, "belong": 20, "owner": 20, "primari": 20, "fingerprint": 20, "647f": 20, "2865": 20, "4894": 20, "e3bd": 20, "4571": 20, "99be": 20, "38db": 20, "bdc8": 20, "6092": 20, "693e": 20, "xf": 20, "p1": 20, "cp": 20, "v": [20, 21], "olddefconfig": 20, "menuconfig": 20, "preemption": 20, "arrow": 20, "preemptibl": 20, "cryptograph": 20, "bottom": 20, "ring": 20, "509": 20, "keyr": 20, "canon": 20, "cert": 20, "pem": 20, "ok": 20, "exit": 20, "tui": 20, "xconfig": 20, "afterward": 20, "lengthi": 20, "multithread": 20, "cpu": [20, 25], "core": 20, "pkg": 20, "newli": 20, "exact": [20, 22], "header": 20, "dbg": 20, "grub": 20, "being": [20, 22], "chose": 20, "sy": 20, "addgroup": 20, "usermod": 20, "whoami": 20, "conf": 20, "rtprio": 20, "99": 20, "memlock": 20, "102400": 20, "log": [20, 22, 23], "experienc": 21, "easili": [21, 22], "cmd": [21, 23], "pathtolibfrankacmakelist": 21, "cmakeset": 21, "json": 21, "cach": 21, "dll": 21, "libri": 21, "chosen": 21, "cmakecommandarg": 21, "dpoco_dir": 21, "deigen3_dir": 21, "latest": 22, "khz": [22, 24, 25], "sensor": [22, 24], "encompass": 22, "calibr": 22, "readonc": 22, "concern": 22, "setcollisionbehavior": [22, 23], "detect": 22, "sete": [22, 23], "setk": 22, "payload": 22, "automaticerrorrecoveri": 22, "clear": 22, "except": [22, 23], "happen": [22, 26], "hostnam": 22, "thrown": 22, "appropri": 22, "cycl": [22, 25, 26], "unless": 22, "packet": [22, 25, 26], "my_external_controller_callback": 22, "my_external_motion_generator_callback": 22, "catch": 22, "cout": 22, "endl": 22, "finish": 22, "motion_finish": 22, "generate_joint_velocity_mot": 22, "tosec": 22, "pow": 22, "fmod": 22, "time_max": 22, "omega": 22, "omega_max": 22, "co": 22, "m_pi": 22, "motionfinish": [22, 23], "approx": 22, "3000": 22, "kjointimped": 22, "2000": 22, "100": [22, 23], "kcartesianimped": 22, "prior": 22, "enough": 22, "collid": 22, "nonsmooth": 22, "unstabl": 22, "facilit": 22, "conform": 22, "satur": [22, 23], "prevent": 22, "Their": 22, "increas": 22, "robust": 22, "whose": 22, "produc": 22, "famili": 22, "cartesian_motion_generator_joint_": 22, "jerki": 22, "my_external_control": 22, "yield": 22, "instabl": 22, "unexpect": 22, "qualiti": [22, 25, 26], "cover": 22, "subscript": 22, "extrapol": [22, 25], "advantag": 22, "transpar": 22, "know": 22, "friction": [22, 24], "effect": 22, "card": [22, 25, 26], "300": 22, "controlexcept": [22, 23], "endeffector": 22, "act": 22, "subsect": 22, "gather": 22, "matric": 22, "centrifug": [22, 24], "fashion": 22, "intervent": 22, "critic": 22, "toggl": 22, "compli": 22, "wrong": [22, 23], "joint_motion_generator_start_pose_invalid": 22, "cartesian_position_motion_generator_start_pose_invalid": 22, "cartesian_motion_generator_start_elbow_invalid": 22, "cartesian_motion_generator_elbow_sign_inconsist": 22, "discrep": 22, "observ": 22, "els": 22, "rest": 22, "joint_motion_generator_position_limits_viol": 22, "cartesian_motion_generator_joint_position_limits_viol": 22, "joint_motion_generator_velocity_limits_viol": 22, "joint_motion_generator_velocity_discontinu": [22, 26], "joint_motion_generator_acceleration_discontinu": 22, "cartesian_motion_generator_velocity_limits_viol": 22, "cartesian_motion_generator_velocity_discontinu": 22, "cartesian_motion_generator_acceleration_discontinu": 22, "cartesian_motion_generator_joint_velocity_limits_viol": 22, "cartesian_motion_generator_joint_velocity_discontinu": 22, "cartesian_motion_generator_joint_acceleration_discontinu": 22, "mitig": 22, "euler": 22, "delta": 22, "advanc": 22, "controller_torque_discontinu": 22, "tau_d": 22, "emphas": 22, "intermitt": 22, "consid": [22, 26], "norm": 22, "aim": 22, "surpass": 22, "cartesian_reflex": 22, "joint_reflex": 22, "push": 22, "innacur": 22, "phase": [22, 26], "self_collision_avoidance_viol": 22, "drive": 22, "itself": 22, "tau_j_range_viol": [22, 23], "power_limit_viol": 22, "joint_velocity_viol": 22, "cartesian_velocity_viol": 22, "temporari": 23, "workaround": 23, "externalmod": 23, "activecontrolbas": 23, "activecontrol": 23, "typo": 23, "activetorquecontrol": 23, "activemotiongener": 23, "introduct": 23, "getvirtualwal": 23, "setfilt": 23, "revert": 23, "set_version_from_git": 23, "hardcod": 23, "orthonorm": 23, "cartesianlowpassfilt": 23, "limitr": 23, "remot": 23, "rate_limiting_test": 23, "logtocsv": 23, "robotmod": 23, "problem": [23, 26], "60": 23, "immedi": 23, "cobot": 23, "pump": 23, "vacuumgripp": 23, "55": 23, "arm64": 23, "lowpass_filt": 23, "control_tool": 23, "hasvalidelbow": 23, "haselbow": 23, "throw": 23, "invalid_argu": 23, "nan": 23, "infin": 23, "communication_test": [23, 26], "x86": 23, "deprec": 23, "lowpassfilt": 23, "record": 23, "entri": 23, "unnecessari": 23, "overload": 23, "sovers": 23, "minor": 23, "joint_p2p_insufficient_torque_for_plan": 23, "instability_detect": 23, "p_min": 23, "p_max": 23, "virtualwallcuboid": 23, "object_world_s": 23, "web": 23, "earlier": 23, "socket": 23, "linter": 23, "docu": 23, "svg": 23, "mathml": 23, "math": 23, "chrome": 23, "bidirect": 24, "statu": 24, "exclus": 24, "OR": 25, "100base": 25, "tx": 25, "latenc": [25, 26], "scale": 25, "intermedi": [25, 26], "relai": 25, "decreas": 25, "best": [25, 26], "beforehand": 25, "sum": [25, 26], "less": [25, 26], "round": [25, 26], "trip": [25, 26], "rtt": [25, 26], "waypoint": 25, "linear": 25, "reus": 25, "solut": 26, "uefi": 26, "unsign": 26, "f2": 26, "f3": 26, "f12": 26, "del": 26, "de": 26, "serial": 26, "subsequ": 26, "larg": 26, "exce": 26, "actual": 26, "reason": 26, "overal": 26, "instanti": 26, "kenforc": 26, "laptop": 26, "bio": 26, "often": 26, "light": 26, "conserv": 26, "cpufrequtil": 26, "cpufreq": 26, "info": 26, "governor": 26, "analyz": 26, "intel_pst": 26, "coordin": 26, "97": 26, "400": 26, "mhz": 26, "ghz": 26, "powersav": 26, "polici": 26, "rang": 26, "500": 26, "benefit": 26, "uniti": 26, "systemctl": 26, "ondemand": 26, "echo": 26, "daemon": 26, "reload": 26, "83": 26, "firewal": 26, "incom": 26, "iptabl": 26, "l": 26, "scenario": 26, "1200": 26, "1228": 26, "byte": 26, "1500982522": 26, "977579": 26, "1208": 26, "icmp_seq": 26, "ttl": 26, "279": 26, "978423": 26, "224": 26, "979434": 26, "196": 26, "980482": 26, "243": 26, "1500982533": 26, "034267": 26, "9999": 26, "236": 26, "035211": 26, "203": 26, "statist": 26, "transmit": 26, "10057m": 26, "avg": 26, "mdev": 26, "147": 26, "240": 26, "502": 26, "038": 26, "purchas": 26, "pci": 26, "lastli": 26, "webpag": 26}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"compat": [0, 5], "version": [0, 5, 26], "libfranka": [0, 2, 5, 13, 20, 22, 23, 26], "kernel": [0, 20, 26], "robot": [1, 10, 12, 15, 18, 22, 26], "interfac": [1, 19, 23], "specif": 1, "joint": [1, 10, 15], "trajectori": [1, 10], "requir": [1, 25], "necessari": 1, "condit": 1, "recommend": 1, "cartesian": [1, 15], "control": [1, 12, 14, 15, 18, 19, 23], "limit": 1, "panda": [1, 18], "franka": [1, 6, 8, 9, 13, 18, 19], "research": [1, 18], "3": [1, 4, 6, 16, 17, 18, 23], "visual": 1, "fr3": [1, 3], "denavit": 1, "hartenberg": 1, "paramet": [1, 15], "faq": 2, "how": 2, "can": 2, "i": [2, 26], "us": [2, 15], "my": 2, "own": [2, 14], "cmake": 2, "base": 2, "project": 2, "certif": 3, "remark": 3, "changelog": [4, 6, 16, 17, 23], "1": [4, 6, 16, 17, 23], "0": [4, 6, 16, 17, 23], "11": [4, 6, 16, 23], "03": [4, 6, 16, 17, 23], "2024": [4, 6], "23": [4, 6], "2023": [4, 6, 16, 23], "20": [4, 6, 16, 23], "09": [4, 6, 16, 17, 23], "2022": [4, 6, 17, 23], "2": [4, 6, 16, 17, 23], "29": [4, 6, 17, 23], "04": [4, 6, 16, 23], "31": [4, 6, 17, 23], "05": [4, 6, 23], "2021": [4, 6, 17], "01": [4, 6, 17, 23], "07": [4, 6, 16, 17, 23], "2020": [4, 6, 17, 23], "21": [4, 6, 16, 17, 23], "matlab": [5, 6, 8, 9, 10, 11], "toolbox": [6, 8, 9, 11], "get": [7, 10, 12, 18], "start": [7, 15, 18], "overview": [7, 24], "initi": 7, "execut": [7, 26], "automat": [7, 10], "error": [7, 10, 13, 22, 23], "recoveri": [7, 10], "content": [8, 19], "instal": [9, 15, 18, 20, 21], "linux": [9, 13, 18, 20], "system": [9, 13, 25], "setup": [9, 15], "window": [9, 18, 21], "add": 9, "On": 9, "licens": 9, "manag": 9, "librari": [10, 12, 13, 22, 23, 26], "demo": 10, "pick": [10, 14], "place": [10, 14], "rrt": 10, "frankarobot": 10, "class": 10, "pose": [10, 15], "state": [10, 12, 22], "point": 10, "motion": [10, 23, 26], "gripper": [10, 12], "home": 10, "grasp": 10, "move": [10, 15], "stop": [10, 26], "depend": 11, "simulink": [12, 13], "appli": 12, "durat": 12, "sampl": 12, "time": [12, 20], "model": [12, 15, 22], "troubleshoot": [13, 26], "control_mod": 13, "h": 13, "No": 13, "file": [13, 20], "directori": 13, "refer": 13, "issu": [13, 15], "graphic": 13, "driver": 13, "libstdc": 13, "so": 13, "other": 13, "dynam": 13, "number": 13, "block": 13, "instanc": 13, "franka_ro": [14, 15, 17], "franka_descript": [14, 15], "collis": 14, "volum": 14, "franka_gripp": [14, 15], "franka_hw": 14, "franka_control": [14, 15], "franka_visu": [14, 15], "franka_example_control": [14, 15], "franka_gazebo": [14, 15], "exampl": [14, 15, 23], "custom": 14, "frankahwsim": 14, "frankagrippersim": 14, "jointstateinterfac": 14, "effortjointinterfac": 14, "frankastateinterfac": 14, "frankamodelinterfac": 14, "friction": 14, "franka_msg": [14, 15], "panda_moveit_config": 14, "write": [14, 15], "your": 14, "franka_ros2": [15, 16], "prerequisit": 15, "option": 15, "bashrc": 15, "set": [15, 18, 20], "from": [15, 20, 21], "sourc": [15, 20, 21], "vscode": 15, "devcontain": 15, "moveit": 15, "graviti": 15, "compens": 15, "imped": 15, "With": 15, "ik": 15, "posit": 15, "veloc": 15, "orient": 15, "elbow": 15, "packag": [15, 20], "descript": 15, "franka_bringup": 15, "franka_hardwar": 15, "franka_semantic_compon": 15, "franka_robot_state_broadcast": 15, "franka_moveit_config": 15, "joint_effort_trajectory_control": 15, "differ": 15, "between": 15, "non": [15, 22], "realtim": [15, 22, 26], "known": 15, "12": [16, 23], "10": [16, 17, 23], "9": [16, 17, 23], "8": [16, 17, 23], "16": [16, 23], "7": [16, 17, 23], "6": [16, 17, 23], "5": [16, 17, 23], "13": [16, 23], "4": [16, 17, 23], "26": [16, 17, 23], "08": [16, 17, 23], "24": 16, "28": [16, 17], "ad": [16, 23], "fix": 16, "02": [17, 23], "22": [17, 23], "15": [17, 23], "2018": [17, 23], "06": [17, 23], "30": [17, 23], "2017": [17, 23], "oper": 18, "fci": 18, "featur": 18, "up": [18, 20], "network": [18, 25, 26], "configur": 18, "workstat": [18, 25], "prepar": 18, "usag": 18, "desk": 18, "enabl": 18, "mode": 18, "verifi": [18, 20], "connect": [18, 26], "document": 19, "ro": 20, "repositori": 20, "build": [20, 21], "real": 20, "integr": 20, "compil": 20, "new": 20, "allow": 20, "user": 20, "permiss": 20, "its": 20, "process": [20, 22], "command": [22, 26], "signal": 22, "under": 22, "hood": 22, "due": [22, 26], "noncompli": 22, "valu": 22, "commun": 22, "problem": 22, "behavior": 22, "14": 23, "25": 23, "2019": 23, "chang": 23, "handl": 23, "minimum": 25, "pc": 25, "cannot": 26, "boot": 26, "becaus": 26, "invalid": 26, "signatur": 26, "run": 26, "fail": 26, "timeout": 26, "discontinu": 26, "communication_constraints_viol": 26, "disabl": 26, "cpu": 26, "frequenc": 26, "scale": 26, "reachabl": 26, "udp": 26, "receiv": 26, "bandwidth": 26, "delai": 26, "jitter": 26, "test": 26, "simpl": 26, "ping": 26, "advanc": 26, "perform": 26, "analysi": 26, "incompat": 26, "reject": 26, "activ": 26, "safeti": 26, "function": 26, "preempt": 26}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx": 56}}) \ No newline at end of file diff --git a/troubleshooting.html b/troubleshooting.html new file mode 100644 index 0000000..b137a7e --- /dev/null +++ b/troubleshooting.html @@ -0,0 +1,343 @@ + + + + + + + Troubleshooting — Franka Control Interface (FCI) documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Troubleshooting

+

This section lists solutions to a set of possible errors which can happen when using the FCI.

+
+
+

Hint

+

Further help is provided in the troubleshooting page of the manual shipped with your robot.

+
+
+
+

Cannot boot realtime kernel because of “Invalid Signature”

+

When you have successfully installed the real-time kernel and try to boot it you might encounter +that Linux is not booting at all. This can happen when you have installed Ubuntu alongside with +Windows (e.g. Dual Boot). Usually then the UEFI boot loader will have Secure Boot activated, +which will not allow the unsigned real-time kernel to load.

+

The easiest solution is to disable “Secure Boot” in your boot loader. This highly depends on your +system, but usually you can enter the boot loader by pressing F2, F3, F12 or DEL key during boot.

+
+
+

Running a libfranka executable fails with “Connection timeout”

+

This error occurs if libfranka cannot connect to the robot at all. Please check that:

+
+
    +
  • Using a robot with system version 4.2.0 or higher requires to enable the FCI mode. To do that +open Desk -> expand the menu in the sidebar -> press ‘Activate FCI’. Further information +about Single Point of Control (SPoC) can be found in the manual shipped with the robot.

  • +
  • Your workstation is directly connected to Control, not the LAN port of the Arm (see +Network).

  • +
  • The robot can be reached from your workstation (see Robot is not reachable).

  • +
  • The FCI feature file is installed on the robot (see “Settings -> System -> Installed Features”). +If you do not have this feature, please install it. Please reach out to support@franka.de with +your serial number if you need to obtain the feature.

  • +
+
+
+
+

Motion stopped due to discontinuities or communication_constraints_violation

+

If the difference between commanded values in subsequent time steps is too large, then the motion is +stopped with a discontinuity error such as joint_motion_generator_velocity_discontinuity. See if +the commanded values do not exceed the limits.

+

Discontinuities can occur if your code commands actual jumps to the robot, but also because of +network packet losses. This is also the reason for communication_constraints_violation errors. +If the issue occurs even when using the provided examples, the problem is most likely related to +overall communication quality. To ensure best performance, please check the following:

+
+
    +
  • All source code is compiled with optimizations (-DCMAKE_BUILD_TYPE=Release). If you installed +libfranka and franka_ros from the ROS repositories, this is already the case for these +projects. However, your own source code needs to be compiled with optimizations as well.

  • +
  • Connect your PC directly to Control, without using any intermediate switches. The +network setup instructions describe how to do this.

  • +
  • Verify your network connection by executing the network bandwidth, delay and jitter test.

  • +
  • franka::Robot is instantiated with RealtimeConfig::kEnforce. This is the default if no +RealtimeConfig is explicitly specified in the constructor.

  • +
  • Power saving features are disabled (cpu frequency scaling, power saving mode, +laptop on battery, BIOS power saving features, etc.). See Disabling CPU frequency scaling.

  • +
+
+
+

Disabling CPU frequency scaling

+

CPUs are often configured to use a lower operating frequency when under a light load in order to +conserve power. We recommend to disable this feature as it leads to higher latencies when using +libfranka. To check and modify the power saving mode, install the cpufrequtils package:

+
sudo apt install cpufrequtils
+
+
+

Run cpufreq-info to see available “governors” and the current CPU frequency. Here is an example +output:

+
$ cpufreq-info
+...
+analyzing CPU 0:
+  driver: intel_pstate
+  CPUs which run at the same hardware frequency: 0
+  CPUs which need to have their frequency coordinated by software: 0
+  maximum transition latency: 0.97 ms.
+  hardware limits: 400 MHz - 3.00 GHz
+  available cpufreq governors: performance, powersave
+  current policy: frequency should be within 400 MHz and 3.00 GHz.
+                  The governor "powersave" may decide which speed to use
+                  within this range.
+  current CPU frequency is 500 MHz.
+...
+
+
+

In this example, the maximum frequency is 3 GHz, but the current one is 500 Mhz due to the +powersave policy. In this case we can benefit by setting the governor to performance.

+

To change this setting using the Ubuntu GUI, install the indicator-cpufreq package. A widget in +the top bar of the Unity user interface should allow you to set the current policy.

+

To change this setting using the terminal, execute the following commands:

+
sudo systemctl disable ondemand
+sudo systemctl enable cpufrequtils
+sudo sh -c 'echo "GOVERNOR=performance" > /etc/default/cpufrequtils'
+sudo systemctl daemon-reload && sudo systemctl restart cpufrequtils
+
+
+

They will disable the ondemand CPU scaling daemon, create a /etc/default/cpufrequtils +configuration file, and then restart the cpufrequtils service.

+

After enabling the performance governor, the cpufreq-info results are:

+
$ cpufreq-info
+...
+analyzing CPU 0:
+  driver: intel_pstate
+  CPUs which run at the same hardware frequency: 0
+  CPUs which need to have their frequency coordinated by software: 0
+  maximum transition latency: 0.97 ms.
+  hardware limits: 400 MHz - 3.00 GHz
+  available cpufreq governors: performance, powersave
+  current policy: frequency should be within 400 MHz and 3.00 GHz.
+                  The governor "performance" may decide which speed to use
+                  within this range.
+  current CPU frequency is 2.83 GHz.
+...
+
+
+

Now the example output shows a CPU frequency close to the maximum one. You can +also directly verify the current governor using the cpufreq-info -p command.

+
+
+
+

Robot is not reachable

+

Try to ping the robot using the following command:

+
ping <fci-ip>
+
+
+

If this command fails, the robot is not properly connected to the network, or the IP address +is not correctly assigned during the setup phase. Please set up the network according to the +documents sent with your robot.

+
+
+

Running a libfranka executable fails with “UDP receive: Timeout”

+

This error occurs if the robot state can not be received by libfranka. Please check that +your workstation’s firewall does not block incoming UDP packets (see sudo iptables -L).

+
+
+

Network bandwidth, delay and jitter test

+

In order to evaluate the network performance of your (possible) control PC we’ve developed two +tests. The first, a ping test, can be executed without the need of libfranka or franka_ros +installed on your system. If your system passes the first ping test, you can run the +advanced UDP network performance analysis.

+
+

Simple ping tests

+

The following command will simulate a network load which is equivalent to a scenario where the +robot is controlled by the FCI:

+
sudo ping <fci-ip> -i 0.001 -D -c 10000 -s 1200
+
+
+

Example output:

+
PING <fci-ip> 1200(1228) bytes of data.
+[1500982522.977579] 1208 bytes from <fci-ip>: icmp_seq=1 ttl=64 time=0.279 ms
+[1500982522.978423] 1208 bytes from <fci-ip>: icmp_seq=2 ttl=64 time=0.224 ms
+[1500982522.979434] 1208 bytes from <fci-ip>: icmp_seq=3 ttl=64 time=0.196 ms
+[1500982522.980482] 1208 bytes from <fci-ip>: icmp_seq=4 ttl=64 time=0.243 ms
+....
+[1500982533.034267] 1208 bytes from <fci-ip>: icmp_seq=9999 ttl=64 time=0.236 ms
+[1500982533.035211] 1208 bytes from <fci-ip>: icmp_seq=10000 ttl=64 time=0.203 ms
+
+--- <fci-ip> ping statistics ---
+10000 packets transmitted, 10000 received, 0% packet loss, time 10057ms
+rtt min/avg/max/mdev = 0.147/0.240/0.502/0.038 ms
+
+
+

The example result shows an average round-trip time of 0.24 ms and a maximum round-trip time of 0.5 +ms. The standard deviation mdev is around 0.04 ms. As explained in the +network requirements section it must be guaranteed that the sum of the +round-trip time and the execution time of the motion generator or control loop is +less than 1 ms. If this constraint is violated for a cycle, the received packet is dropped by +the FCI.

+

If the round-trip time is long even with a direct connection, consider +purchasing a separate, high performance PCI-Express network card for your +workstation PC. See if there are dedicated drivers for your network card, +these usually offer better performance. Lastly, the CPU can also be a limiting +factor for network performance.

+
+
+

Advanced network performance analysis

+

The communication_test executable, which is part of the libfranka examples since +version 0.5, executes a control loop and provides feedback about the lost robot +states as well as the maximum, minimum and average control command success rate.

+

If you installed the ros-noetic-libfranka package or installed libfranka from source, you can execute the test with:

+
source /opt/ros/noetic/setup.sh # only needed when installed with ROS
+communication_test <fci-ip>
+
+
+

If you do not want to install libfranka system-wide you can execute the following command inside your build folder after compiling libfranka +from source:

+
./examples/communication_test <fci-ip>
+
+
+
+
+
+

Running a libfranka executable fails with “Incompatible Library Version”

+

This happens when your version of libfranka is incompatible with the system version of your robot. +The error contains the server version of the robot. You can use that number to +choose the correct libfranka version for your robot.

+
+
+

Running a libfranka executable fails with “command rejected due to activated safety function!” or “command preempted due to activated safety function!”

+

This error occurs when a safety function defined in Watchman (the safety configurator area of the Desk webpage) is active. +For example, there could be an active safety function limiting the robot’s speed to 0.2 m/s. As this cannot be guaranteed when using FCI, the +robot will not move. However, you can still read the robot state. In order to command movements to the robot again, +you either need to disable the safety function or delete the corresponding safety rule in Watchman.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file