396 lines
11 KiB
C++
396 lines
11 KiB
C++
|
/*
|
||
|
* Copyright (C) 2014 Open Source Robotics Foundation
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <libusb-1.0/libusb.h>
|
||
|
#include <linux/hidraw.h>
|
||
|
#include <linux/input.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <cstring>
|
||
|
#include "gazebo/physics/physics.hh"
|
||
|
#include "gazebo/transport/transport.hh"
|
||
|
#include "plugins/HydraPlugin.hh"
|
||
|
|
||
|
// Loosely adapted from the following
|
||
|
// https://github.com/ros-drivers/razer_hydra/blob/groovy-devel/src/hydra.cpp
|
||
|
|
||
|
// Ugly hack to work around failing compilation on systems that don't
|
||
|
// yet populate new version of hidraw.h to userspace.
|
||
|
//
|
||
|
// If you need this, please have your distro update the kernel headers.
|
||
|
|
||
|
#ifndef HIDIOCSFEATURE
|
||
|
#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
|
||
|
#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
|
||
|
#endif
|
||
|
|
||
|
// Eventually crawl hidraw file system using this:
|
||
|
// http://www.signal11.us/oss/udev/
|
||
|
|
||
|
using namespace gazebo;
|
||
|
GZ_REGISTER_WORLD_PLUGIN(RazerHydra)
|
||
|
|
||
|
#define HYDRA_RIGHT_BUMPER 7
|
||
|
#define HYDRA_RIGHT_1 8
|
||
|
#define HYDRA_RIGHT_2 9
|
||
|
#define HYDRA_RIGHT_3 10
|
||
|
#define HYDRA_RIGHT_4 11
|
||
|
#define HYDRA_RIGHT_CENTER 12
|
||
|
#define HYDRA_RIGHT_JOY 13
|
||
|
|
||
|
#define HYDRA_LEFT_LB 0
|
||
|
#define HYDRA_LEFT_1 1
|
||
|
#define HYDRA_LEFT_2 2
|
||
|
#define HYDRA_LEFT_3 3
|
||
|
#define HYDRA_LEFT_4 4
|
||
|
#define HYDRA_LEFT_CENTER 5
|
||
|
#define HYDRA_LEFT_JOY 6
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
RazerHydra::RazerHydra()
|
||
|
: hidrawFd(0), lastCycleStart(common::Time::GetWallTime())
|
||
|
{
|
||
|
this->stop = false;
|
||
|
this->pollThread = NULL;
|
||
|
|
||
|
for (auto &v: this->analog)
|
||
|
v = 0;
|
||
|
|
||
|
for (auto &v: this->rawAnalog)
|
||
|
v = 0;
|
||
|
|
||
|
for (auto &v: this->rawButtons)
|
||
|
v = 0;
|
||
|
|
||
|
for (auto &v: this->rawQuat)
|
||
|
v = 0;
|
||
|
|
||
|
for (auto &v: this->rawPos)
|
||
|
v = 0;
|
||
|
|
||
|
for (auto &v: this->buttons)
|
||
|
v = 0;
|
||
|
|
||
|
// magic number for 50% mix at each step
|
||
|
this->periodEstimate.SetFc(0.11, 1.0);
|
||
|
|
||
|
this->periodEstimate.SetValue(0.004);
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
RazerHydra::~RazerHydra()
|
||
|
{
|
||
|
event::Events::DisconnectWorldUpdateBegin(this->updateConnection);
|
||
|
|
||
|
this->stop = true;
|
||
|
if (this->pollThread)
|
||
|
this->pollThread->join();
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void RazerHydra::Load(physics::WorldPtr _world, sdf::ElementPtr /*_sdf*/)
|
||
|
{
|
||
|
int res;
|
||
|
uint8_t buf[256];
|
||
|
struct hidraw_report_descriptor rptDesc;
|
||
|
struct hidraw_devinfo info;
|
||
|
|
||
|
// Find the Razer device.
|
||
|
std::string device;
|
||
|
for (int i = 0; i < 6 && device.empty(); ++i)
|
||
|
{
|
||
|
std::ostringstream stream;
|
||
|
stream << "/sys/class/hidraw/hidraw" << i << "/device/uevent";
|
||
|
std::ifstream fileIn(stream.str().c_str());
|
||
|
if (fileIn.is_open())
|
||
|
{
|
||
|
std::string line;
|
||
|
while (std::getline(fileIn, line) && device.empty())
|
||
|
{
|
||
|
if (line.find("HID_NAME=Razer Razer Hydra") != std::string::npos)
|
||
|
device = "/dev/hidraw" + boost::lexical_cast<std::string>(i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (device.empty())
|
||
|
{
|
||
|
gzerr << "Unable to find Razer device\n";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this->hidrawFd = open(device.c_str(), O_RDWR | O_NONBLOCK);
|
||
|
if (this->hidrawFd < 0)
|
||
|
{
|
||
|
gzerr << "couldn't open hidraw device[" << device << "]\n";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
memset(&rptDesc, 0x0, sizeof(rptDesc));
|
||
|
memset(&info, 0x0, sizeof(info));
|
||
|
memset(buf, 0x0, sizeof(buf));
|
||
|
|
||
|
// Get Raw Name
|
||
|
res = ioctl(this->hidrawFd, HIDIOCGRAWNAME(256), buf);
|
||
|
if (res < 0)
|
||
|
gzerr << "Hydro ioctl error HIDIOCGRAWNAME: " << strerror(errno) << "\n";
|
||
|
|
||
|
// set feature to start it streaming
|
||
|
memset(buf, 0x0, sizeof(buf));
|
||
|
buf[6] = 1;
|
||
|
buf[8] = 4;
|
||
|
buf[9] = 3;
|
||
|
buf[89] = 6;
|
||
|
|
||
|
int attempt = 0;
|
||
|
for (attempt = 0; attempt < 50; ++attempt)
|
||
|
{
|
||
|
res = ioctl(this->hidrawFd, HIDIOCSFEATURE(91), buf);
|
||
|
if (res < 0)
|
||
|
{
|
||
|
gzerr << "Unable to start streaming. HIDIOCSFEATURE: "
|
||
|
<< strerror(errno) << "\n";
|
||
|
common::Time::MSleep(500);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (attempt >= 60)
|
||
|
{
|
||
|
gzerr << "Failed to load hydra\n";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this->updateConnection = event::Events::ConnectWorldUpdateBegin(
|
||
|
boost::bind(&RazerHydra::Update, this, _1));
|
||
|
|
||
|
this->pollThread = new boost::thread(boost::bind(&RazerHydra::Run, this));
|
||
|
|
||
|
this->node = transport::NodePtr(new transport::Node());
|
||
|
this->node->Init(_world->GetName());
|
||
|
this->pub = this->node->Advertise<msgs::Hydra>("~/hydra");
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void RazerHydra::Update(const common::UpdateInfo & /*_info*/)
|
||
|
{
|
||
|
boost::mutex::scoped_lock lock(this->mutex);
|
||
|
math::Pose origRight(this->pos[1], this->quat[1]);
|
||
|
|
||
|
math::Pose pivotRight = origRight;
|
||
|
math::Pose grabRight = origRight;
|
||
|
|
||
|
pivotRight.pos += origRight.rot * math::Vector3(-0.04, 0, 0);
|
||
|
grabRight.pos += origRight.rot * math::Vector3(-0.12, 0, 0);
|
||
|
|
||
|
math::Pose origLeft(this->pos[0], this->quat[0]);
|
||
|
|
||
|
math::Pose pivotLeft = origLeft;
|
||
|
math::Pose grabLeft = origLeft;
|
||
|
|
||
|
pivotLeft.pos += origLeft.rot.RotateVector(math::Vector3(-0.04, 0, 0));
|
||
|
grabLeft.pos += origLeft.rot.RotateVector(math::Vector3(-0.12, 0, 0));
|
||
|
|
||
|
msgs::Hydra msg;
|
||
|
msgs::Hydra::Paddle *rightPaddle = msg.mutable_right();
|
||
|
msgs::Hydra::Paddle *leftPaddle = msg.mutable_left();
|
||
|
|
||
|
// Analog 0: Left right(+) left(-)
|
||
|
// Analog 1: Left forward(+) back(-)
|
||
|
// Analog 2: Left trigger(0-1)
|
||
|
// Analog 3: Right right(+) left(-)
|
||
|
// Analog 4: Right forward(+) back(-)
|
||
|
// Analog 5: Right trigger(0-1)
|
||
|
rightPaddle->set_joy_y(this->analog[3]);
|
||
|
rightPaddle->set_joy_x(this->analog[4]);
|
||
|
rightPaddle->set_trigger(this->analog[5]);
|
||
|
|
||
|
leftPaddle->set_joy_y(this->analog[0]);
|
||
|
leftPaddle->set_joy_x(this->analog[1]);
|
||
|
leftPaddle->set_trigger(this->analog[2]);
|
||
|
|
||
|
leftPaddle->set_button_bumper(this->buttons[0]);
|
||
|
leftPaddle->set_button_1(this->buttons[1]);
|
||
|
leftPaddle->set_button_2(this->buttons[2]);
|
||
|
leftPaddle->set_button_3(this->buttons[3]);
|
||
|
leftPaddle->set_button_4(this->buttons[4]);
|
||
|
|
||
|
leftPaddle->set_button_center(this->buttons[5]);
|
||
|
leftPaddle->set_button_joy(this->buttons[6]);
|
||
|
|
||
|
rightPaddle->set_button_bumper(this->buttons[7]);
|
||
|
rightPaddle->set_button_1(this->buttons[8]);
|
||
|
rightPaddle->set_button_2(this->buttons[9]);
|
||
|
rightPaddle->set_button_3(this->buttons[10]);
|
||
|
rightPaddle->set_button_4(this->buttons[11]);
|
||
|
rightPaddle->set_button_center(this->buttons[12]);
|
||
|
rightPaddle->set_button_joy(this->buttons[13]);
|
||
|
|
||
|
msgs::Set(rightPaddle->mutable_pose(), grabRight.Ign());
|
||
|
msgs::Set(leftPaddle->mutable_pose(), grabLeft.Ign());
|
||
|
|
||
|
this->pub->Publish(msg);
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void RazerHydra::Run()
|
||
|
{
|
||
|
double cornerHz = 2.5;
|
||
|
|
||
|
while (!this->stop)
|
||
|
{
|
||
|
if (!this->Poll(cornerHz))
|
||
|
common::Time::NSleep(250000);
|
||
|
}
|
||
|
|
||
|
if (this->hidrawFd >= 0)
|
||
|
{
|
||
|
uint8_t buf[256];
|
||
|
memset(buf, 0, sizeof(buf));
|
||
|
buf[6] = 1;
|
||
|
buf[8] = 4;
|
||
|
buf[89] = 5;
|
||
|
|
||
|
if (ioctl(this->hidrawFd, HIDIOCSFEATURE(91), buf) < 0)
|
||
|
{
|
||
|
gzerr << "Unable to stop streaming. HIDIOCSFEATURE: "
|
||
|
<< strerror(errno) << "\n";
|
||
|
}
|
||
|
|
||
|
close(this->hidrawFd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
bool RazerHydra::Poll(float _lowPassCornerHz)
|
||
|
{
|
||
|
if (this->hidrawFd < 0)
|
||
|
{
|
||
|
gzerr << "hidraw device is not open, couldn't poll.\n";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (_lowPassCornerHz <= std::numeric_limits<float>::epsilon())
|
||
|
{
|
||
|
gzerr << "Corner frequency for low-pass filter must be greater than 0."
|
||
|
<< "Using a default value of 2.5Hz.\n";
|
||
|
// Set a default value if the value is incorrect.
|
||
|
_lowPassCornerHz = 2.5;
|
||
|
}
|
||
|
|
||
|
uint8_t buf[64];
|
||
|
ssize_t nread = read(this->hidrawFd, buf, sizeof(buf));
|
||
|
|
||
|
// No updates.
|
||
|
if (nread <= 0)
|
||
|
return false;
|
||
|
|
||
|
|
||
|
static bool firstTime = true;
|
||
|
|
||
|
// Update average read period
|
||
|
if (!firstTime)
|
||
|
{
|
||
|
this->periodEstimate.Process(
|
||
|
(common::Time::GetWallTime() - this->lastCycleStart).Double());
|
||
|
}
|
||
|
|
||
|
this->lastCycleStart = common::Time::GetWallTime();
|
||
|
|
||
|
if (firstTime)
|
||
|
firstTime = false;
|
||
|
|
||
|
// Update filter frequencies
|
||
|
float fs = 1.0 / this->periodEstimate.GetValue();
|
||
|
float fc = _lowPassCornerHz;
|
||
|
|
||
|
for (int i = 0; i < 2; ++i)
|
||
|
{
|
||
|
this->filterPos[i].SetFc(fc, fs);
|
||
|
this->filterQuat[i].SetFc(fc, fs);
|
||
|
}
|
||
|
|
||
|
// Read data
|
||
|
this->rawPos[0] = *(reinterpret_cast<int16_t *>(buf+8));
|
||
|
this->rawPos[1] = *(reinterpret_cast<int16_t *>(buf+10));
|
||
|
this->rawPos[2] = *(reinterpret_cast<int16_t *>(buf+12));
|
||
|
this->rawQuat[0] = *(reinterpret_cast<int16_t *>(buf+14));
|
||
|
this->rawQuat[1] = *(reinterpret_cast<int16_t *>(buf+16));
|
||
|
this->rawQuat[2] = *(reinterpret_cast<int16_t *>(buf+18));
|
||
|
this->rawQuat[3] = *(reinterpret_cast<int16_t *>(buf+20));
|
||
|
this->rawButtons[0] = buf[22] & 0x7f;
|
||
|
this->rawAnalog[0] = *(reinterpret_cast<int16_t *>(buf+23));
|
||
|
this->rawAnalog[1] = *(reinterpret_cast<int16_t *>(buf+25));
|
||
|
this->rawAnalog[2] = buf[27];
|
||
|
|
||
|
this->rawPos[3] = *(reinterpret_cast<int16_t *>(buf+30));
|
||
|
this->rawPos[4] = *(reinterpret_cast<int16_t *>(buf+32));
|
||
|
this->rawPos[5] = *(reinterpret_cast<int16_t *>(buf+34));
|
||
|
this->rawQuat[4] = *(reinterpret_cast<int16_t *>(buf+36));
|
||
|
this->rawQuat[5] = *(reinterpret_cast<int16_t *>(buf+38));
|
||
|
this->rawQuat[6] = *(reinterpret_cast<int16_t *>(buf+40));
|
||
|
this->rawQuat[7] = *(reinterpret_cast<int16_t *>(buf+42));
|
||
|
this->rawButtons[1] = buf[44] & 0x7f;
|
||
|
this->rawAnalog[3] = *(reinterpret_cast<int16_t *>(buf+45));
|
||
|
this->rawAnalog[4] = *(reinterpret_cast<int16_t *>(buf+47));
|
||
|
this->rawAnalog[5] = buf[49];
|
||
|
|
||
|
boost::mutex::scoped_lock lock(this->mutex);
|
||
|
// Put the raw position and orientation into Gazebo coordinate frame
|
||
|
for (int i = 0; i < 2; ++i)
|
||
|
{
|
||
|
this->pos[i].x = -this->rawPos[3*i+1] * 0.001;
|
||
|
this->pos[i].y = -this->rawPos[3*i+0] * 0.001;
|
||
|
this->pos[i].z = -this->rawPos[3*i+2] * 0.001;
|
||
|
|
||
|
this->quat[i].w = this->rawQuat[i*4+0] / 32768.0;
|
||
|
this->quat[i].x = -this->rawQuat[i*4+2] / 32768.0;
|
||
|
this->quat[i].y = -this->rawQuat[i*4+1] / 32768.0;
|
||
|
this->quat[i].z = -this->rawQuat[i*4+3] / 32768.0;
|
||
|
}
|
||
|
|
||
|
// Apply filters
|
||
|
for (int i = 0; i < 2; ++i)
|
||
|
{
|
||
|
this->quat[i] = this->filterQuat[i].Process(this->quat[i]);
|
||
|
this->pos[i] = this->filterPos[i].Process(this->pos[i]);
|
||
|
}
|
||
|
|
||
|
this->analog[0] = this->rawAnalog[0] / 32768.0;
|
||
|
this->analog[1] = this->rawAnalog[1] / 32768.0;
|
||
|
this->analog[2] = this->rawAnalog[2] / 255.0;
|
||
|
this->analog[3] = this->rawAnalog[3] / 32768.0;
|
||
|
this->analog[4] = this->rawAnalog[4] / 32768.0;
|
||
|
this->analog[5] = this->rawAnalog[5] / 255.0;
|
||
|
|
||
|
for (int i = 0; i < 2; ++i)
|
||
|
{
|
||
|
this->buttons[i*7 ] = (this->rawButtons[i] & 0x01) ? 1 : 0;
|
||
|
this->buttons[i*7+1] = (this->rawButtons[i] & 0x04) ? 1 : 0;
|
||
|
this->buttons[i*7+2] = (this->rawButtons[i] & 0x08) ? 1 : 0;
|
||
|
this->buttons[i*7+3] = (this->rawButtons[i] & 0x02) ? 1 : 0;
|
||
|
this->buttons[i*7+4] = (this->rawButtons[i] & 0x10) ? 1 : 0;
|
||
|
this->buttons[i*7+5] = (this->rawButtons[i] & 0x20) ? 1 : 0;
|
||
|
this->buttons[i*7+6] = (this->rawButtons[i] & 0x40) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|