Program Listing for File trossen_arm.hpp

Return to documentation for file (include/libtrossen_arm/trossen_arm.hpp)

// Copyright 2025 Trossen Robotics
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the the copyright holder nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifndef LIBTROSSEN_ARM__TROSSEN_ARM_HPP_
#define LIBTROSSEN_ARM__TROSSEN_ARM_HPP_

#include <atomic>
#include <cstdint>
#include <cstring>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <vector>

#include "libtrossen_arm/trossen_arm_config.hpp"
#include "libtrossen_arm/trossen_arm_interpolate.hpp"
#include "libtrossen_arm/trossen_arm_logging.hpp"
#include "libtrossen_arm/trossen_arm_udp_client.hpp"
#include "yaml-cpp/yaml.h"

namespace trossen_arm
{

enum class Mode : uint8_t {
  idle,
  position,
  velocity,
  external_effort,
};

enum class IPMethod : uint8_t {
  manual,
  dhcp,
};

enum class Model : uint8_t {
  wxai_v0,
};

struct JointCharacteristic
{
  float effort_correction;
  float friction_transition_velocity;
  float friction_constant_term;
  float friction_coulomb_coef;
  float friction_viscous_coef;
  float continuity_factor;
};

struct LinkProperties
{
  float mass;
  std::array<float, 9> inertia;
  std::array<float, 3> origin_xyz;
  std::array<float, 3> origin_rpy;
};

struct EndEffectorProperties
{
  LinkProperties palm;

  LinkProperties finger_left;

  LinkProperties finger_right;

  float offset_finger_left;

  float offset_finger_right;

  float t_max_factor;
};

struct StandardEndEffector {
  static constexpr EndEffectorProperties wxai_v0_base{
    .palm = {
      .mass = 0.53780000f,
      .inertia = {
        0.00079919, -0.00000049, 0.00000010,
        -0.00000049, 0.00047274, 0.00000004,
        0.00000010, 0.00000004, 0.00105293
      },
      .origin_xyz = {0.04572768f, -0.00000726f, 0.00001402f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_left = {
      .mass = 0.05945000f,
      .inertia = {
        0.00001875f, 0.00000309f, -0.00000149f,
        0.00000309f, 0.00002614f, -0.00000124f,
        -0.00000149f, -0.00000124f, 0.00002995f
      },
      .origin_xyz = {0.00169016f, -0.00592796f, -0.00365701f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_right = {
      .mass = 0.05945000f,
      .inertia = {
        0.00001930f, -0.00000309f, 0.00000359f,
        -0.00000309f, 0.00002670f, -0.00000064f,
        0.00000359f, -0.00000064f, 0.00002995f
      },
      .origin_xyz = {0.00169015f, 0.00592793f, 0.00201818f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .offset_finger_left = 0.0227f,
    .offset_finger_right = -0.0227f,
    .t_max_factor = 0.5f
  };

  static constexpr EndEffectorProperties wxai_v0_leader{
    .palm = {
      .mass = 0.59570000f,
      .inertia = {
        0.00117653f, -0.00000040f, -0.00005492f,
        -0.00000040f, 0.00085696f, 0.00000074f,
        -0.00005492f, 0.00000074f, 0.00107685f
      },
      .origin_xyz = {0.04454388f, 0.00000506f, -0.00694150f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_left = {
      .mass = 0.06380000f,
      .inertia = {
        0.00003556f, -0.00000249f, 0.00000167f,
        -0.00000249f, 0.00002700f, 0.00000217f,
        0.00000167f, 0.00000217f, 0.00001726f
      },
      .origin_xyz = {-0.00423580f, -0.00167541f, -0.01050810f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_right = {
      .mass = 0.06380000f,
      .inertia = {
        0.00004133f, 0.00000250f, 0.00000517f,
        0.00000250f, 0.00003277f, -0.00000592f,
        0.00000517f, -0.00000592f, 0.00001727f
      },
      .origin_xyz = {-0.00423309f, 0.00167373f, -0.00451087f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .offset_finger_left = 0.0179f,
    .offset_finger_right = -0.0179f,
    .t_max_factor = 0.5f
  };

  static constexpr EndEffectorProperties wxai_v0_follower{
    .palm = {
      .mass = 0.64230000f,
      .inertia = {
        0.00108484f, 0.00000063f, -0.00004180f,
        0.00000063f, 0.00075170f, -0.00001558f,
        -0.00004180f, -0.00001558f, 0.00110994f
      },
      .origin_xyz = {0.04699592f, 0.00045936f, 0.00827772f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_left = {
      .mass = 0.05945000f,
      .inertia = {
        0.00001875f, 0.00000309f, -0.00000149f,
        0.00000309f, 0.00002614f, -0.00000124f,
        -0.00000149f, -0.00000124f, 0.00002995f
      },
      .origin_xyz = {0.00169016f, -0.00592796f, -0.00365701f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .finger_right = {
      .mass = 0.05945000f,
      .inertia = {
        0.00001930f, -0.00000309f, 0.00000359f,
        -0.00000309f, 0.00002670f, -0.00000064f,
        0.00000359f, -0.00000064f, 0.00002995f
      },
      .origin_xyz = {0.00169015f, 0.00592793f, 0.00201818f},
      .origin_rpy = {0.0f, 0.0f, 0.0f}
    },
    .offset_finger_left = 0.0227f,
    .offset_finger_right = -0.0227f,
    .t_max_factor = 0.5f
  };
};

class TrossenArmDriver
{
public:
  ~TrossenArmDriver();

  void configure(
    Model model,
    EndEffectorProperties end_effector,
    const std::string serv_ip,
    bool clear_error
  );

  void cleanup();

  void set_all_positions(
    const std::vector<float> & goal_positions,
    float goal_time = 2.0f,
    bool blocking = true,
    const std::optional<std::vector<float>> & goal_feedforward_velocities = std::nullopt,
    const std::optional<std::vector<float>> & goal_feedforward_accelerations = std::nullopt);

  void set_arm_positions(
    const std::vector<float> & goal_positions,
    float goal_time = 2.0f,
    bool blocking = true,
    const std::optional<std::vector<float>> & goal_feedforward_velocities = std::nullopt,
    const std::optional<std::vector<float>> & goal_feedforward_accelerations = std::nullopt);

  void set_gripper_position(
    float goal_position,
    float goal_time = 2.0f,
    bool blocking = true,
    float goal_feedforward_velocity = 0.0f,
    float goal_feedforward_acceleration = 0.0f);

  void set_joint_position(
    uint8_t joint_index,
    float goal_position,
    float goal_time = 2.0f,
    bool blocking = true,
    float goal_feedforward_velocity = 0.0f,
    float goal_feedforward_acceleration = 0.0f
  );

  void set_all_velocities(
    const std::vector<float> & goal_velocities,
    float goal_time = 2.0f,
    bool blocking = true,
    const std::optional<std::vector<float>> & goal_feedforward_accelerations = std::nullopt);

  void set_arm_velocities(
    const std::vector<float> & goal_velocities,
    float goal_time = 2.0f,
    bool blocking = true,
    const std::optional<std::vector<float>> & goal_feedforward_accelerations = std::nullopt);

  void set_gripper_velocity(
    float goal_velocity,
    float goal_time = 2.0f,
    bool blocking = true,
    float goal_feedforward_acceleration = 0.0f
  );

  void set_joint_velocity(
    uint8_t joint_index,
    float goal_velocity,
    float goal_time = 2.0f,
    bool blocking = true,
    float goal_feedforward_acceleration = 0.0f
  );

  void set_all_external_efforts(
    const std::vector<float> & goal_external_efforts,
    float goal_time = 2.0f,
    bool blocking = true
  );

  void set_arm_external_efforts(
    const std::vector<float> & goal_external_efforts,
    float goal_time = 2.0f,
    bool blocking = true
  );

  void set_gripper_external_effort(
    float goal_external_effort,
    float goal_time = 2.0f,
    bool blocking = true
  );

  void set_joint_external_effort(
    uint8_t joint_index,
    float goal_external_effort,
    float goal_time = 2.0f,
    bool blocking = true
  );

  void load_configs_from_file(const std::string & file_path);

  void set_factory_reset_flag(bool flag = true);

  void set_ip_method(IPMethod method = IPMethod::manual);

  void set_manual_ip(const std::string manual_ip = "192.168.1.2");

  void set_dns(const std::string dns = "8.8.8.8");

  void set_gateway(const std::string gateway = "192.168.1.1");

  void set_subnet(const std::string subnet = "255.255.255.0");

  void set_joint_characteristics(const std::vector<JointCharacteristic> & joint_characteristics);

  void set_effort_corrections(const std::vector<float> & effort_corrections);

  void set_friction_transition_velocities(
    const std::vector<float> & friction_transition_velocities
  );

  void set_friction_constant_terms(const std::vector<float> & friction_constant_terms);

  void set_friction_coulomb_coefs(const std::vector<float> & friction_coulomb_coefs);

  void set_friction_viscous_coefs(const std::vector<float> & friction_viscous_coefs);

  void set_continuity_factors(const std::vector<float> & continuity_factors);

  void set_joint_modes(const std::vector<Mode> & modes);

  void set_all_modes(Mode mode = Mode::idle);

  void set_arm_modes(Mode mode = Mode::idle);

  void set_gripper_mode(Mode mode = Mode::idle);

  void set_end_effector(const EndEffectorProperties & end_effector);

  void set_gripper_force_limit_scaling_factor(float scaling_factor = 0.5f);

  uint8_t get_num_joints() const;

  std::string get_driver_version() const;

  std::string get_controller_version() const;

  std::vector<float> get_positions();

  std::vector<float> get_velocities();

  std::vector<float> get_efforts();

  std::vector<float> get_external_efforts();

  std::vector<float> get_compensation_efforts();

  void save_configs_to_file(const std::string & file_path);

  bool get_factory_reset_flag();

  IPMethod get_ip_method();

  std::string get_manual_ip();

  std::string get_dns();

  std::string get_gateway();

  std::string get_subnet();

  std::vector<JointCharacteristic> get_joint_characteristics();

  std::vector<float> get_effort_corrections();

  std::vector<float> get_friction_transition_velocities();

  std::vector<float> get_friction_constant_terms();

  std::vector<float> get_friction_coulomb_coefs();

  std::vector<float> get_friction_viscous_coefs();

  std::vector<float> get_continuity_factors();

  std::string get_error_information();

  std::vector<Mode> get_modes();

  EndEffectorProperties get_end_effector();

  float get_gripper_force_limit_scaling_factor();

private:
  // Raw counterparts of LinkProperties and EndEffectorProperties
  struct LinkRaw
  {
    float mass;
    float inertia[9];
    float origin_xyz[3];
    float origin_rpy[3];
  };
  struct EndEffectorRaw
  {
    LinkRaw palm;
    LinkRaw finger_left;
    LinkRaw finger_right;
    float offset_finger_left;
    float offset_finger_right;
    float t_max_factor;
  };

  struct JointInput
  {
    Mode mode{Mode::idle};
    union {
      struct {
        float position;
        float feedforward_velocity;
        float feedforward_acceleration;
      } position{0.0f, 0.0f, 0.0f};
      struct {
        float velocity;
        float feedforward_acceleration;
      } velocity;
      struct {
        float external_effort;
      } external_effort;
    };
  };

  struct JointOutput
  {
    float position;
    float velocity;
    float effort;
    float external_effort;
  };

  // Robot command indicators
  enum class RobotCommandIndicator : uint8_t
  {
    handshake,
    set_joint_inputs,
    get_joint_outputs,
    set_home,
    set_configuration,
    get_configuration,
    get_log,
  };

  // ErrorState
  enum class ErrorState : uint8_t {
    // No error
    none,
    // Controller's UDP interface failed to initialize
    udp_init_failed,
    // Controller's CAN interface failed to initialize
    can_init_failed,
    // Controller's CAN interface failed to send a message
    joint_command_failed,
    // Controller's CAN interface failed to receive a message
    joint_feedback_failed,
    // Joint clear error command failed
    joint_clear_error_failed,
    // Joint enable command failed
    joint_enable_failed,
    // Joint disable command failed
    joint_disable_failed,
    // Joint home calibration command failed
    joint_set_home_failed,
    // Joint disabled unexpectedly
    joint_disabled_unexpectedly,
    // Joint overheated
    joint_overheated,
    // Invalid mode command received
    invalid_mode,
    // Invalid robot command indicator received
    invalid_robot_command,
    // Robot command with unexpected size received
    invalid_robot_command_size,
    // Invalid configuration address
    invalid_configuration_address,
    // Invalid pending command
    invalid_pending_command,
    // Robot input with modes different than configured modes received
    robot_input_mode_mismatch,
    // Discontinuous robot input received
    robot_input_discontinous,
  };

  // Configuration addresses
  enum class ConfigurationAddress : uint8_t {
    factory_reset_flag,
    ip_method,
    manual_ip,
    dns,
    gateway,
    subnet,
    joint_characteristics,
    error_state,
    modes,
    end_effector
  };

  // UDP port
  static constexpr uint16_t PORT{50000};

  // Timeout in microseconds for receiving UDP packets
  static constexpr uint32_t TIMEOUT_US{1000};

  // Maximum retransmission attempts
  static constexpr uint8_t MAX_RETRANSMISSION_ATTEMPTS{100};

  // Model to number of joints mapping
  static const std::map<Model, uint8_t> MODEL_NUM_JOINTS;

  // Error information
  static const std::map<ErrorState, std::string> ERROR_INFORMATION;

  // Model name
  static const std::map<Model, std::string> MODEL_NAME;

  // Mode name
  static const std::map<Mode, std::string> MODE_NAME;

  // Configuration name
  static const std::map<ConfigurationAddress, std::string> CONFIGURATION_NAME;

  // Joint characteristic name
  static const struct JointCharacteristicName
  {
    std::string effort_correction;
    std::string friction_transition_velocity;
    std::string friction_constant_term;
    std::string friction_coulomb_coef;
    std::string friction_viscous_coef;
    std::string continuity_factor;
  } JOINT_CHARACTERISTIC_NAME;

  // Interpolators for joint trajectories
  std::vector<QuinticHermiteInterpolator> trajectories_;

  // Trajectory start time
  std::vector<std::chrono::time_point<std::chrono::steady_clock>> trajectory_start_times_;

  // Robot input
  std::vector<JointInput> joint_inputs_;

  // Joint outputs
  std::vector<JointOutput> joint_outputs_;

  // Number of joints
  uint8_t num_joints_{0};

  // Driver version
  std::string driver_version_;

  // Controller firmware version
  std::string controller_version_;

  // Whether the driver is properly configured for the robot to be controlled
  // true if configured, false if not configured
  bool configured_{false};

  // UDP client
  UDP_Client udp_client_;

  // Atomic flag for maintaining and stopping the daemon thread
  std::atomic<bool> activated_{false};

  // Multithreading design
  //
  // Goal
  //
  // - only one thread can run at a time
  // - another thread cannot cut in until the full communication cycle is completed
  //   for example, set_joint_inputs --nothing-in-between--> receive_joint_outputs
  // - the other thread has priority to run after the current thread finishes
  //
  // Mutex ownership
  //
  // call mutex_preempt_ 1 and mutex_data_ 2 for simplicity
  // daemon: |-|-1-|-12-|-2-|--------|-1-|-12-|-2-|-|
  // main:   |------------|-1-|-12-|-2-|------------|
  //
  // Exception handling
  //
  // - if an exception is thrown in the main thread
  //   - the daemon thread gets std::terminate
  //   - the main thread unwind the stack: ~TrossenArmDriver() -> cleanup()
  // - if an exception is thrown in the daemon thread
  //   - the exception is stored in exception_ptr_
  //   - the daemon thread returns
  //   - the main thread gets the exception and rethrows it at the next operation
  //   - the main thread unwind the stack: ~TrossenArmDriver() -> cleanup()
  //
  // Notes
  //
  // - the mutex claiming cannot be nested or there will be deadlocks, i.e., |-1-|-12-|-2-|-12-|-2-|
  //   is not allowed
  // - when an exception is thrown by the main thread, the program is expected to terminate either
  //   immediately or right after cleaning up the resources not related to the driver

  // Daemon thread
  std::thread daemon_thread_;

  // Mutex for data access
  std::mutex mutex_data_;

  // Mutex for preempting the next slot to run
  std::mutex mutex_preempt_;

  // Shared exception pointer
  std::exception_ptr exception_ptr_;

  void set_joint_inputs();

  bool receive_joint_outputs();

  void get_configuration(ConfigurationAddress configuration_address);

  void check_error_state(bool clear_error);

  void reset_error_state();

  std::string get_detailed_log();

  void daemon();
};

}  // namespace trossen_arm

#endif  // LIBTROSSEN_ARM__TROSSEN_ARM_HPP_