Program Listing for File pendulum_motor.hpp

Return to documentation for file (/tmp/ws/src/demos/pendulum_control/include/pendulum_control/pendulum_motor.hpp)

// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef PENDULUM_CONTROL__PENDULUM_MOTOR_HPP_
#define PENDULUM_CONTROL__PENDULUM_MOTOR_HPP_

// Needed for M_PI on Windows
#ifdef _MSC_VER
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES
#endif
#endif

#include <chrono>
#include <cmath>
#include <memory>

#include "rttest/rttest.h"
#include "rttest/utils.hpp"

#include "pendulum_msgs/msg/joint_command.hpp"
#include "pendulum_msgs/msg/joint_state.hpp"

#ifndef GRAVITY
#define GRAVITY 9.80665
#endif

namespace pendulum_control
{

struct PendulumProperties
{
  double mass = 0.01;
  double length = 0.5;
};

struct PendulumState
{
  double position = 0;
  double velocity = 0;
  double acceleration = 0;
  double torque = 0;
};

class PendulumMotor
{
public:

  PendulumMotor(std::chrono::nanoseconds period, PendulumProperties properties)
  : publish_period_(period), properties_(properties),
    physics_update_period_(std::chrono::nanoseconds(1000000)),
    message_ready_(false), done_(false)
  {
    // Calculate physics engine timestep.
    dt_ = physics_update_period_.count() / (1000.0 * 1000.0 * 1000.0);
    uint64_to_timespec(physics_update_period_.count(), &physics_update_timespec_);

    // Initialize a separate high-priority thread to run the physics update loop.
    pthread_attr_init(&thread_attr_);
    sched_param thread_param;
    thread_param.sched_priority = 90;
    pthread_attr_setschedparam(&thread_attr_, &thread_param);
    pthread_attr_setschedpolicy(&thread_attr_, SCHED_RR);
    pthread_create(
      &physics_update_thread_, &thread_attr_,
      &pendulum_control::PendulumMotor::physics_update_wrapper, this);
  }

  // \param[in] msg Received command.
  void on_command_message(pendulum_msgs::msg::JointCommand::ConstSharedPtr msg)
  {
    ++messages_received;
    // Assume direct, instantaneous position control
    // (It would be more realistic to simulate a motor model)
    state_.position = msg->position;

    // Enforce position limits
    if (state_.position > M_PI) {
      state_.position = M_PI;
    } else if (state_.position < 0) {
      state_.position = 0;
    }

    if (std::isnan(state_.position)) {
      throw std::runtime_error("Tried to set state to NaN in on_command_message callback");
    }
  }

  // \return The sensor message
  const pendulum_msgs::msg::JointState & get_next_sensor_message() const
  {
    return sensor_message_;
  }

  // \return True if the message is ready to be published.
  bool next_message_ready() const
  {
    return message_ready_;
  }

  // \param[in] done True if the physics engine should stop.
  void set_done(bool done)
  {
    done_ = done;
  }

  // \return True if the physics engine is running, false otherwise.
  bool done() const
  {
    return done_;
  }

  // \return The update rate as a std::chrono::duration.
  std::chrono::nanoseconds get_publish_period() const
  {
    return publish_period_;
  }

  // \return Position of the pendulum.
  double get_position() const
  {
    return state_.position;
  }

  // \return State of the pendulum.
  PendulumState get_state() const
  {
    return state_;
  }

  // \param[in] state State to set.
  void set_state(const PendulumState & state)
  {
    state_ = state;
  }

  // \return Properties of the pendulum.
  const PendulumProperties & get_properties() const
  {
    return properties_;
  }

  // \param[in] properties Properties to set.
  void set_properties(const PendulumProperties & properties)
  {
    properties_ = properties;
  }

  size_t messages_received = 0;

private:
  static void * physics_update_wrapper(void * args)
  {
    PendulumMotor * motor = static_cast<PendulumMotor *>(args);
    if (!motor) {
      return NULL;
    }
    return motor->physics_update();
  }
  // Set kinematic and dynamic properties of the pendulum based on state inputs
  void * physics_update()
  {
    rttest_lock_and_prefault_dynamic();
    while (!done_) {
      state_.acceleration = GRAVITY * std::sin(state_.position - M_PI / 2.0) / properties_.length +
        state_.torque / (properties_.mass * properties_.length * properties_.length);
      state_.velocity += state_.acceleration * dt_;
      state_.position += state_.velocity * dt_;
      if (state_.position > M_PI) {
        state_.position = M_PI;
      } else if (state_.position < 0) {
        state_.position = 0;
      }

      if (std::isnan(state_.position)) {
        throw std::runtime_error("Tried to set state to NaN in on_command_message callback");
      }

      sensor_message_.velocity = state_.velocity;
      // Simulate a noisy sensor on position
      sensor_message_.position = state_.position;

      message_ready_ = true;
      // high resolution sleep
      clock_nanosleep(CLOCK_MONOTONIC, 0, &physics_update_timespec_, NULL);
    }
    return 0;
  }

  // motor should publish more frequently than the controller
  std::chrono::nanoseconds publish_period_;

  // Physics should update most frequently, in separate RT thread
  timespec physics_update_timespec_;
  double dt_;

  // Physical qualities of the pendulum
  /*
       M
        \
         \ length
       p  \
     0 ----------- pi
   */

  PendulumProperties properties_;
  PendulumState state_;

  std::chrono::nanoseconds physics_update_period_;
  pendulum_msgs::msg::JointState sensor_message_;
  bool message_ready_;
  bool done_;

  pthread_t physics_update_thread_;
  pthread_attr_t thread_attr_;
};

}  // namespace pendulum_control

#endif  // PENDULUM_CONTROL__PENDULUM_MOTOR_HPP_