Program Listing for File pendulum_controller.hpp

Return to documentation for file (/tmp/ws/src/demos/pendulum_control/include/pendulum_control/pendulum_controller.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_CONTROLLER_HPP_
#define PENDULUM_CONTROL__PENDULUM_CONTROLLER_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 "pendulum_msgs/msg/joint_command.hpp"
#include "pendulum_msgs/msg/joint_state.hpp"

namespace pendulum_control
{

struct PIDProperties
{
  double p = 1;
  double i = 0;
  double d = 0;
  double command = M_PI / 2;
};

class PendulumController
{
public:

  PendulumController(std::chrono::nanoseconds period, PIDProperties pid)
  : publish_period_(period), pid_(pid),
    message_ready_(false)
  {
    command_message_.position = pid_.command;
    // Calculate the controller timestep (for discrete differentiation/integration).
    dt_ = publish_period_.count() / (1000.0 * 1000.0 * 1000.0);
    if (std::isnan(dt_) || dt_ == 0) {
      throw std::runtime_error("Invalid dt_ calculated in PendulumController constructor");
    }
  }

  // \param[in] msg Received sensor message.
  void on_sensor_message(pendulum_msgs::msg::JointState::ConstSharedPtr msg)
  {
    ++messages_received;

    if (std::isnan(msg->position)) {
      throw std::runtime_error("Sensor value was NaN in on_sensor_message callback");
    }
    // PID controller algorithm
    double error = pid_.command - msg->position;
    // Proportional gain is proportional to error
    double p_gain = pid_.p * error;
    // Integral gain is proportional to the accumulation of error
    i_gain_ = pid_.i * (i_gain_ + error * dt_);
    // Differential gain is proportional to the change in error
    double d_gain = pid_.d * (error - last_error_) / dt_;
    last_error_ = error;

    // Calculate the message based on PID gains
    command_message_.position = msg->position + p_gain + i_gain_ + d_gain;
    // Enforce positional limits
    if (command_message_.position > M_PI) {
      command_message_.position = M_PI;
    } else if (command_message_.position < 0) {
      command_message_.position = 0;
    }

    if (std::isnan(command_message_.position)) {
      throw std::runtime_error("Resulting command was NaN in on_sensor_message callback");
    }
    message_ready_ = true;
  }

  // \param[in] msg The incoming message containing the position.
  void on_pendulum_setpoint(pendulum_msgs::msg::JointCommand::ConstSharedPtr msg)
  {
    set_command(msg->position);
    printf("Pendulum set to: %f\n", msg->position);
    fflush(stdout);
  }

  // \return Command message.
  const pendulum_msgs::msg::JointCommand & get_next_command_message() const
  {
    return command_message_;
  }

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

  // \return Duration struct representing the update period in nanoseconds.
  std::chrono::nanoseconds get_publish_period() const
  {
    return publish_period_;
  }

  // \param[in] properties Struct representing the desired properties.
  void set_pid_properties(const PIDProperties & properties)
  {
    pid_ = properties;
  }

  // \return Struct representing the properties of the controller.
  const PIDProperties & get_pid_properties() const
  {
    return pid_;
  }

  // \param[in] command The new commanded position (in radians).
  void set_command(double command)
  {
    pid_.command = command;
  }

  // \return The commanded position.
  double get_command() const
  {
    return pid_.command;
  }

  size_t messages_received = 0;

private:
  // controller should publish less frequently than the motor
  std::chrono::nanoseconds publish_period_;
  PIDProperties pid_;
  pendulum_msgs::msg::JointCommand command_message_;
  bool message_ready_;

  // state for PID controller
  double last_error_ = 0;
  double i_gain_ = 0;
  double dt_;
};

}  // namespace pendulum_control

#endif  // PENDULUM_CONTROL__PENDULUM_CONTROLLER_HPP_