Skip to content
Permalink
dc4787b56f
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
304 lines (274 sloc) 11 KB
// -*- mode:c++; fill-column: 100; -*-
#include "vesc_driver/vesc_driver.h"
#include <cassert>
#include <cmath>
#include <sstream>
#include <boost/bind.hpp>
#include <vesc_msgs/VescStateStamped.h>
namespace vesc_driver
{
VescDriver::VescDriver(ros::NodeHandle nh,
ros::NodeHandle private_nh) :
vesc_(std::string(),
boost::bind(&VescDriver::vescPacketCallback, this, _1),
boost::bind(&VescDriver::vescErrorCallback, this, _1)),
duty_cycle_limit_(private_nh, "duty_cycle", 0.0, 1.0), current_limit_(private_nh, "current"),
brake_limit_(private_nh, "brake"), speed_limit_(private_nh, "speed"),
position_limit_(private_nh, "position"), servo_limit_(private_nh, "servo", 0.0, 1.0),
driver_mode_(MODE_INITIALIZING), fw_version_major_(-1), fw_version_minor_(-1)
{
// get vesc serial port address
std::string port;
if (!private_nh.getParam("port", port)) {
ROS_FATAL("VESC communication port parameter required.");
ros::shutdown();
return;
}
// attempt to connect to the serial port
try {
vesc_.connect(port);
}
catch (SerialException e) {
ROS_FATAL("Failed to connect to the VESC, %s.", e.what());
ros::shutdown();
return;
}
// create vesc state (telemetry) publisher
state_pub_ = nh.advertise<vesc_msgs::VescStateStamped>("sensors/core", 10);
// since vesc state does not include the servo position, publish the commanded
// servo position as a "sensor"
servo_sensor_pub_ = nh.advertise<std_msgs::Float64>("sensors/servo_position_command", 10);
// subscribe to motor and servo command topics
duty_cycle_sub_ = nh.subscribe("commands/motor/duty_cycle", 10,
&VescDriver::dutyCycleCallback, this);
current_sub_ = nh.subscribe("commands/motor/current", 10, &VescDriver::currentCallback, this);
brake_sub_ = nh.subscribe("commands/motor/brake", 10, &VescDriver::brakeCallback, this);
speed_sub_ = nh.subscribe("commands/motor/speed", 10, &VescDriver::speedCallback, this);
position_sub_ = nh.subscribe("commands/motor/position", 10, &VescDriver::positionCallback, this);
servo_sub_ = nh.subscribe("commands/servo/position", 10, &VescDriver::servoCallback, this);
// create a 50Hz timer, used for state machine & polling VESC telemetry
timer_ = nh.createTimer(ros::Duration(1.0/50.0), &VescDriver::timerCallback, this);
}
/* TODO or TO-THINKABOUT LIST
- what should we do on startup? send brake or zero command?
- what to do if the vesc interface gives an error?
- check version number against know compatable?
- should we wait until we receive telemetry before sending commands?
- should we track the last motor command
- what to do if no motor command received recently?
- what to do if no servo command received recently?
- what is the motor safe off state (0 current?)
- what to do if a command parameter is out of range, ignore?
- try to predict vesc bounds (from vesc config) and command detect bounds errors
*/
void VescDriver::timerCallback(const ros::TimerEvent& event)
{
// VESC interface should not unexpectedly disconnect, but test for it anyway
if (!vesc_.isConnected()) {
ROS_FATAL("Unexpectedly disconnected from serial port.");
timer_.stop();
ros::shutdown();
return;
}
/*
* Driver state machine, modes:
* INITIALIZING - request and wait for vesc version
* OPERATING - receiving commands from subscriber topics
*/
if (driver_mode_ == MODE_INITIALIZING) {
// request version number, return packet will update the internal version numbers
vesc_.requestFWVersion();
if (fw_version_major_ >= 0 && fw_version_minor_ >= 0) {
ROS_INFO("Connected to VESC with firmware version %d.%d",
fw_version_major_, fw_version_minor_);
driver_mode_ = MODE_OPERATING;
}
}
else if (driver_mode_ == MODE_OPERATING) {
// poll for vesc state (telemetry)
vesc_.requestState();
}
else {
// unknown mode, how did that happen?
assert(false && "unknown driver mode");
}
}
void VescDriver::vescPacketCallback(const boost::shared_ptr<VescPacket const>& packet)
{
if (packet->name() == "Values") {
boost::shared_ptr<VescPacketValues const> values =
boost::dynamic_pointer_cast<VescPacketValues const>(packet);
vesc_msgs::VescStateStamped::Ptr state_msg(new vesc_msgs::VescStateStamped);
state_msg->header.stamp = ros::Time::now();
state_msg->state.voltage_input = values->v_in();
state_msg->state.temperature_pcb = values->temp_pcb();
state_msg->state.current_motor = values->current_motor();
state_msg->state.current_input = values->current_in();
state_msg->state.speed = values->rpm();
state_msg->state.duty_cycle = values->duty_now();
state_msg->state.charge_drawn = values->amp_hours();
state_msg->state.charge_regen = values->amp_hours_charged();
state_msg->state.energy_drawn = values->watt_hours();
state_msg->state.energy_regen = values->watt_hours_charged();
state_msg->state.displacement = values->tachometer();
state_msg->state.distance_traveled = values->tachometer_abs();
state_msg->state.fault_code = values->fault_code();
state_pub_.publish(state_msg);
}
else if (packet->name() == "FWVersion") {
boost::shared_ptr<VescPacketFWVersion const> fw_version =
boost::dynamic_pointer_cast<VescPacketFWVersion const>(packet);
// todo: might need lock here
fw_version_major_ = fw_version->fwMajor();
fw_version_minor_ = fw_version->fwMinor();
}
}
void VescDriver::vescErrorCallback(const std::string& error)
{
ROS_ERROR("%s", error.c_str());
}
/**
* @param duty_cycle Commanded VESC duty cycle. Valid range for this driver is -1 to +1. However,
* note that the VESC may impose a more restrictive bounds on the range depending
* on its configuration, e.g. absolute value is between 0.05 and 0.95.
*/
void VescDriver::dutyCycleCallback(const std_msgs::Float64::ConstPtr& duty_cycle)
{
if (driver_mode_ = MODE_OPERATING) {
vesc_.setDutyCycle(duty_cycle_limit_.clip(duty_cycle->data));
}
}
/**
* @param current Commanded VESC current in Amps. Any value is accepted by this driver. However,
* note that the VESC may impose a more restrictive bounds on the range depending on
* its configuration.
*/
void VescDriver::currentCallback(const std_msgs::Float64::ConstPtr& current)
{
if (driver_mode_ = MODE_OPERATING) {
vesc_.setCurrent(current_limit_.clip(current->data));
}
}
/**
* @param brake Commanded VESC braking current in Amps. Any value is accepted by this driver.
* However, note that the VESC may impose a more restrictive bounds on the range
* depending on its configuration.
*/
void VescDriver::brakeCallback(const std_msgs::Float64::ConstPtr& brake)
{
if (driver_mode_ = MODE_OPERATING) {
vesc_.setBrake(brake_limit_.clip(brake->data));
}
}
/**
* @param speed Commanded VESC speed in electrical RPM. Electrical RPM is the mechanical RPM
* multiplied by the number of motor poles. Any value is accepted by this
* driver. However, note that the VESC may impose a more restrictive bounds on the
* range depending on its configuration.
*/
void VescDriver::speedCallback(const std_msgs::Float64::ConstPtr& speed)
{
if (driver_mode_ = MODE_OPERATING) {
vesc_.setSpeed(speed_limit_.clip(speed->data));
}
}
/**
* @param position Commanded VESC motor position in radians. Any value is accepted by this driver.
* Note that the VESC must be in encoder mode for this command to have an effect.
*/
void VescDriver::positionCallback(const std_msgs::Float64::ConstPtr& position)
{
if (driver_mode_ = MODE_OPERATING) {
// ROS uses radians but VESC seems to use degrees. Convert to degrees.
double position_deg = position_limit_.clip(position->data) * M_PI / 180.0;
vesc_.setPosition(position_deg);
}
}
/**
* @param servo Commanded VESC servo output position. Valid range is 0 to 1.
*/
void VescDriver::servoCallback(const std_msgs::Float64::ConstPtr& servo)
{
if (driver_mode_ = MODE_OPERATING) {
double servo_clipped(servo_limit_.clip(servo->data));
vesc_.setServo(servo_clipped);
// publish clipped servo value as a "sensor"
std_msgs::Float64::Ptr servo_sensor_msg(new std_msgs::Float64);
servo_sensor_msg->data = servo_clipped;
servo_sensor_pub_.publish(servo_sensor_msg);
}
}
VescDriver::CommandLimit::CommandLimit(const ros::NodeHandle& nh, const std::string& str,
const boost::optional<double>& min_lower,
const boost::optional<double>& max_upper) :
name(str)
{
// check if user's minimum value is outside of the range min_lower to max_upper
double param_min;
if (nh.getParam(name + "_min", param_min)) {
if (min_lower && param_min < *min_lower) {
lower = *min_lower;
ROS_WARN_STREAM("Parameter " << name << "_min (" << param_min <<
") is less than the feasible minimum (" << *min_lower << ").");
}
else if (max_upper && param_min > *max_upper) {
lower = *max_upper;
ROS_WARN_STREAM("Parameter " << name << "_min (" << param_min <<
") is greater than the feasible maximum (" << *max_upper << ").");
}
else {
lower = param_min;
}
}
else if (min_lower) {
lower = *min_lower;
}
// check if the uers' maximum value is outside of the range min_lower to max_upper
double param_max;
if (nh.getParam(name + "_max", param_max)) {
if (min_lower && param_max < *min_lower) {
upper = *min_lower;
ROS_WARN_STREAM("Parameter " << name << "_max (" << param_max <<
") is less than the feasible minimum (" << *min_lower << ").");
}
else if (max_upper && param_max > *max_upper) {
upper = *max_upper;
ROS_WARN_STREAM("Parameter " << name << "_max (" << param_max <<
") is greater than the feasible maximum (" << *max_upper << ").");
}
else {
upper = param_max;
}
}
else if (max_upper) {
upper = *max_upper;
}
// check for min > max
if (upper && lower && *lower > *upper) {
ROS_WARN_STREAM("Parameter " << name << "_max (" << upper
<< ") is less than parameter " << name << "_min (" << lower << ").");
double temp(*lower);
lower = *upper;
upper = temp;
}
std::ostringstream oss;
oss << " " << name << " limit: ";
if (lower) oss << *lower << " "; else oss << "(none) ";
if (upper) oss << *upper; else oss << "(none)";
ROS_DEBUG_STREAM(oss.str());
}
double VescDriver::CommandLimit::clip(double value)
{
if (lower && value < lower) {
ROS_INFO_THROTTLE(10, "%s command value (%f) below minimum limit (%f), clipping.",
name.c_str(), value, *lower);
return *lower;
}
if (upper && value > upper) {
ROS_INFO_THROTTLE(10, "%s command value (%f) above maximum limit (%f), clipping.",
name.c_str(), value, *upper);
return *upper;
}
return value;
}
} // namespace vesc_driver