Fawkes API Fawkes Development Version
force_feedback.cpp
1
2/***************************************************************************
3 * force_feedback.cpp - Force feedback for joysticks using Linux input API
4 *
5 * Created: Mon Feb 07 01:35:29 2011 (Super Bowl XLV)
6 * Copyright 2006-2011 Tim Niemueller [www.niemueller.de]
7 *
8 ****************************************************************************/
9
10/* This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library General Public License for more details.
19 *
20 * Read the full text in the LICENSE.GPL file in the doc directory.
21 */
22
23#include "force_feedback.h"
24
25#include <core/exception.h>
26#include <sys/stat.h>
27#include <sys/types.h>
28
29#include <cerrno>
30#include <cstdio>
31#include <cstdlib>
32#include <cstring>
33#include <dirent.h>
34#include <fcntl.h>
35#include <fnmatch.h>
36#include <unistd.h>
37
38#define BITS_PER_LONG (sizeof(long) * 8)
39#define NBITS(x) ((((x)-1) / BITS_PER_LONG) + 1)
40#define OFF(x) ((x) % BITS_PER_LONG)
41#define BIT(x) (1UL << OFF(x))
42#define LONG(x) ((x) / BITS_PER_LONG)
43#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
44
45using namespace fawkes;
46
47/** @class JoystickForceFeedback "force_feedback.h"
48 * Cause force feedback on a joystick.
49 * An instance of this class opens an input device which belongs to
50 * the given device name. It searches all input devices to find the
51 * correct device file. Once opened, it detects the available features
52 * of the joystick and provides conventient access to it allowing for
53 * rumbling effects, for instance.
54 * @author Tim Niemueller
55 *
56 * @fn bool JoystickForceFeedback::is_rumbling()
57 * Check if rumbling effect is active.
58 * @return true if effect is active, false otherwise
59 *
60 * @fn bool JoystickForceFeedback::can_rumble()
61 * Check if rumbling effect is supported.
62 * @return true if effect is supported, false otherwise
63 *
64 * @fn bool JoystickForceFeedback::can_periodic()
65 * Check if periodic effect is supported.
66 * @return true if effect is supported, false otherwise
67 *
68 * @fn bool JoystickForceFeedback::can_constant()
69 * Check if constant effect is supported.
70 * @return true if effect is supported, false otherwise
71 *
72 * @fn bool JoystickForceFeedback::can_spring()
73 * Check if spring effect is supported.
74 * @return true if effect is supported, false otherwise
75 *
76 * @fn bool JoystickForceFeedback::can_friction()
77 * Check if friction effect is supported.
78 * @return true if effect is supported, false otherwise
79 *
80 * @fn bool JoystickForceFeedback::can_damper()
81 * Check if damper effect is supported.
82 * @return true if effect is supported, false otherwise
83 *
84 * @fn bool JoystickForceFeedback::can_inertia()
85 * Check if inertia effect is supported.
86 * @return true if effect is supported, false otherwise
87 *
88 * @fn bool JoystickForceFeedback::can_ramp()
89 * Check if ramp effect is supported.
90 * @return true if effect is supported, false otherwise
91 *
92 * @fn bool JoystickForceFeedback::can_square()
93 * Check if square effect is supported.
94 * @return true if effect is supported, false otherwise
95 *
96 * @fn bool JoystickForceFeedback::can_triangle()
97 * Check if triangle effect is supported.
98 * @return true if effect is supported, false otherwise
99 *
100 * @fn bool JoystickForceFeedback::can_sine()
101 * Check if sine effect is supported.
102 * @return true if effect is supported, false otherwise
103 *
104 * @fn bool JoystickForceFeedback::can_saw_up()
105 * Check if upward saw effect is supported.
106 * @return true if effect is supported, false otherwise
107 *
108 * @fn bool JoystickForceFeedback::can_saw_down()
109 * Check if downward saw effect is supported.
110 * @return true if effect is supported, false otherwise
111 *
112 * @fn bool JoystickForceFeedback::can_custom()
113 * Check if custom effect is supported.
114 * @return true if effect is supported, false otherwise
115 *
116 */
117
118/** Constructor.
119 * @param device_name device name, note that this is not the device
120 * file, but rather the event files are tried and the device name is
121 * compared.
122 */
124{
125 fd_ = -1;
126
127 DIR *d = opendir("/dev/input");
128
129 if (d == NULL) {
130 throw Exception("Could not open directory /dev/input");
131 }
132
133 struct dirent *de;
134 while ((de = readdir(d)) != NULL) {
135 if (fnmatch("event*", de->d_name, 0) != FNM_NOMATCH) {
136 char *path;
137 if (asprintf(&path, "/dev/input/%s", de->d_name) == -1) {
138 continue;
139 }
140
141 fd_ = open(path, O_RDWR);
142 if (fd_ == -1) {
143 free(path);
144 continue;
145 }
146 free(path);
147
148 char name[256] = "Unknown";
149 if (ioctl(fd_, EVIOCGNAME(sizeof(name)), name) < 0) {
150 close(fd_);
151 fd_ = -1;
152 continue;
153 }
154
155 if (strcmp(name, device_name) != 0) {
156 close(fd_);
157 fd_ = -1;
158 continue;
159 }
160
161 long features[NBITS(EV_MAX)];
162 memset(features, 0, sizeof(features));
163 if (ioctl(fd_, EVIOCGBIT(0, EV_MAX), features) < 0) {
164 close(fd_);
165 fd_ = -1;
166 throw Exception("Cannot get feedback feature vector");
167 }
168
169 if (!test_bit(EV_FF, features)) {
170 close(fd_);
171 fd_ = -1;
172 throw Exception("Device '%s' does not support force-feedback", device_name);
173 }
174
175 long ff_features[NBITS(FF_MAX)];
176
177 memset(ff_features, 0, sizeof(ff_features));
178 if (ioctl(fd_, EVIOCGBIT(EV_FF, FF_MAX), ff_features) < 0) {
179 close(fd_);
180 fd_ = -1;
181 throw Exception("Cannot get device force feedback feature vector");
182 }
183
184 long no_ff_features[NBITS(FF_MAX)];
185 memset(no_ff_features, 0, sizeof(no_ff_features));
186 if (memcmp(ff_features, no_ff_features, sizeof(no_ff_features)) == 0) {
187 close(fd_);
188 fd_ = -1;
189 throw Exception("Device has no force feedback features");
190 }
191
192 can_rumble_ = test_bit(FF_RUMBLE, ff_features);
193 can_periodic_ = test_bit(FF_PERIODIC, ff_features);
194 can_constant_ = test_bit(FF_CONSTANT, ff_features);
195 can_spring_ = test_bit(FF_SPRING, ff_features);
196 can_friction_ = test_bit(FF_FRICTION, ff_features);
197 can_damper_ = test_bit(FF_DAMPER, ff_features);
198 can_inertia_ = test_bit(FF_INERTIA, ff_features);
199 can_ramp_ = test_bit(FF_RAMP, ff_features);
200 can_square_ = test_bit(FF_SQUARE, ff_features);
201 can_triangle_ = test_bit(FF_TRIANGLE, ff_features);
202 can_sine_ = test_bit(FF_SINE, ff_features);
203 can_saw_up_ = test_bit(FF_SAW_UP, ff_features);
204 can_saw_down_ = test_bit(FF_SAW_DOWN, ff_features);
205 can_custom_ = test_bit(FF_CUSTOM, ff_features);
206
207 if (ioctl(fd_, EVIOCGEFFECTS, &num_effects_) < 0) {
208 num_effects_ = 1;
209 }
210
211 break;
212 }
213 }
214
215 closedir(d);
216
217 if (fd_ == -1) {
218 throw Exception("Force feedback joystick '%s' not found", device_name);
219 }
220
221 memset(&rumble_, 0, sizeof(rumble_));
222 rumble_.type = FF_RUMBLE;
223 rumble_.id = -1;
224}
225
226/** Destructor. */
228{
229 close(fd_);
230}
231
232/** Rumble the joystick.
233
234 * This is the most basic force feedback for example in force feedback
235 * joypads. Often such joysticks provide two effect magnitudes, a
236 * strong heavier motor for larger effects, and a smaller one for
237 * vibrating effects.
238 * @param strong_magnitude magnitude to use on the larger motor
239 * @param weak_magnitude magnitude to use on the smaller motor
240 * @param direction direction of the effect, meaningful on joysticks
241 * (rather than joypads)
242 * @param length length of the effect in ms
243 * @param delay delay before the effect starts in ms
244 */
245void
246JoystickForceFeedback::rumble(uint16_t strong_magnitude,
247 uint16_t weak_magnitude,
248 Direction direction,
249 uint16_t length,
250 uint16_t delay)
251{
252 if ((rumble_.id == -1) || (rumble_.u.rumble.strong_magnitude != strong_magnitude)
253 || (rumble_.u.rumble.weak_magnitude != weak_magnitude) || (rumble_.direction != direction)
254 || (rumble_.replay.length != length) || (rumble_.replay.delay != length)) {
255 // we need to upload
256 rumble_.u.rumble.strong_magnitude = strong_magnitude;
257 rumble_.u.rumble.weak_magnitude = weak_magnitude;
258 rumble_.direction = direction;
259 rumble_.replay.length = length;
260 rumble_.replay.delay = delay;
261
262 if (ioctl(fd_, EVIOCSFF, &rumble_) < 0) {
263 throw Exception("Failed to upload rumble effect");
264 }
265 }
266
267 struct input_event play;
268 play.type = EV_FF;
269 play.code = rumble_.id;
270 play.value = 1;
271
272 if (write(fd_, &play, sizeof(play)) < 0) {
273 throw Exception("Failed to start rumble effect");
274 }
275}
276
277/** Stop rumbling. */
278void
280{
281 if (rumble_.id != -1) {
282 if (ioctl(fd_, EVIOCRMFF, rumble_.id) < 0) {
283 throw Exception("Failed to stop rumble effect");
284 }
285 rumble_.id = -1;
286 }
287}
288
289/** Stop all current effects. */
290void
292{
293 stop_rumble();
294}
Direction
Direction of the effect.
JoystickForceFeedback(const char *device_name)
Constructor.
void rumble(uint16_t strong_magnitude, uint16_t weak_magnitude, Direction direction=DIRECTION_DOWN, uint16_t length=0, uint16_t delay=0)
Rumble the joystick.
void stop_all()
Stop all current effects.
void stop_rumble()
Stop rumbling.
~JoystickForceFeedback()
Destructor.
Base class for exceptions in Fawkes.
Definition: exception.h:36
Fawkes library namespace.