0
# Force Feedback
1
2
Support for force feedback effects on compatible devices. The ff module provides ctypes structures and utilities for creating, uploading, and managing haptic feedback effects like rumble, springs, and custom waveforms.
3
4
## Capabilities
5
6
### Effect Structures
7
8
Core ctypes structures for defining force feedback effects.
9
10
```python { .api }
11
class Effect(ctypes.Structure):
12
"""
13
Main force feedback effect structure.
14
15
Fields:
16
- type: Effect type (FF_RUMBLE, FF_PERIODIC, etc.)
17
- id: Effect identifier (set by kernel)
18
- direction: Effect direction (0x0000 to 0xFFFF)
19
- ff_trigger: Trigger conditions (Trigger structure)
20
- ff_replay: Scheduling information (Replay structure)
21
- u: Union of effect-specific data (EffectType)
22
"""
23
24
class EffectType(ctypes.Union):
25
"""
26
Union containing effect-specific parameters.
27
28
Fields:
29
- constant: Constant force effect data
30
- ramp: Ramp effect data
31
- periodic: Periodic effect data
32
- condition: Condition effect data (spring, friction, etc.)
33
- rumble: Rumble effect data
34
"""
35
```
36
37
Effect type union usage patterns:
38
39
```python
40
# Access different effect types through union
41
effect.u.rumble.strong_magnitude = 0x8000 # For rumble effects
42
effect.u.constant.level = 0x4000 # For constant effects
43
effect.u.periodic.waveform = ecodes.FF_SINE # For periodic effects
44
effect.u.condition[0].right_coeff = 0x2000 # For condition effects (array)
45
```
46
47
### Scheduling and Triggers
48
49
Structures for controlling when and how effects are played.
50
51
```python { .api }
52
class Replay(ctypes.Structure):
53
"""
54
Effect scheduling and timing control.
55
56
Fields:
57
- length: Duration of effect in milliseconds (0 = infinite)
58
- delay: Delay before effect starts in milliseconds
59
"""
60
61
class Trigger(ctypes.Structure):
62
"""
63
Effect trigger conditions.
64
65
Fields:
66
- button: Button number that triggers effect (0 = no trigger)
67
- interval: Minimum time between triggers in milliseconds
68
"""
69
70
class Envelope(ctypes.Structure):
71
"""
72
Effect envelope for attack/fade control.
73
74
Fields:
75
- attack_length: Duration of attack phase in milliseconds
76
- attack_level: Starting amplitude level (0x0000-0x7FFF)
77
- fade_length: Duration of fade phase in milliseconds
78
- fade_level: Ending amplitude level (0x0000-0x7FFF)
79
"""
80
```
81
82
### Effect Types
83
84
Specific effect structures for different force feedback types.
85
86
```python { .api }
87
class Rumble(ctypes.Structure):
88
"""
89
Rumble/vibration effect.
90
91
Fields:
92
- strong_magnitude: Strong motor magnitude (0x0000-0xFFFF)
93
- weak_magnitude: Weak motor magnitude (0x0000-0xFFFF)
94
"""
95
96
class Constant(ctypes.Structure):
97
"""
98
Constant force effect.
99
100
Fields:
101
- level: Force level (-0x7FFF to 0x7FFF)
102
- ff_envelope: Envelope for attack/fade (Envelope structure)
103
"""
104
105
class Ramp(ctypes.Structure):
106
"""
107
Force ramp effect (force changes linearly over time).
108
109
Fields:
110
- start_level: Starting force level (-0x7FFF to 0x7FFF)
111
- end_level: Ending force level (-0x7FFF to 0x7FFF)
112
- ff_envelope: Envelope for attack/fade (Envelope structure)
113
"""
114
115
class Periodic(ctypes.Structure):
116
"""
117
Periodic waveform effect.
118
119
Fields:
120
- waveform: Waveform type (FF_SQUARE, FF_SINE, etc.)
121
- period: Period of waveform in milliseconds
122
- magnitude: Peak magnitude (0x0000-0x7FFF)
123
- offset: DC offset (-0x7FFF to 0x7FFF)
124
- phase: Phase offset (0x0000-0x7FFF, 0=0°, 0x4000=90°)
125
- envelope: Envelope structure
126
- custom_len: Length of custom waveform data
127
- custom_data: Pointer to custom waveform data
128
"""
129
130
class Condition(ctypes.Structure):
131
"""
132
Condition effects (spring, friction, damper, inertia).
133
134
Fields:
135
- right_saturation: Right saturation level (0x0000-0xFFFF)
136
- left_saturation: Left saturation level (0x0000-0xFFFF)
137
- right_coeff: Right coefficient (-0x7FFF to 0x7FFF)
138
- left_coeff: Left coefficient (-0x7FFF to 0x7FFF)
139
- deadband: Deadband size (0x0000-0xFFFF)
140
- center: Center position (-0x7FFF to 0x7FFF)
141
"""
142
```
143
144
### UInput Force Feedback
145
146
Structures for managing force feedback effects on virtual devices.
147
148
```python { .api }
149
class UInputUpload(ctypes.Structure):
150
"""
151
Structure for uploading effects to UInput devices.
152
153
Fields:
154
- request_id: Request identifier
155
- retval: Return value/error code
156
- effect: Effect structure to upload
157
- old: Previous effect data
158
"""
159
160
class UInputErase(ctypes.Structure):
161
"""
162
Structure for erasing effects from UInput devices.
163
164
Fields:
165
- request_id: Request identifier
166
- retval: Return value/error code
167
- effect_id: Effect ID to erase
168
"""
169
```
170
171
## Usage Examples
172
173
### Basic Rumble Effect
174
175
```python
176
from evdev import InputDevice, UInput, ecodes
177
from evdev.ff import Effect, Rumble, Replay, Trigger
178
import ctypes
179
import time
180
181
# Create or open device with force feedback capability
182
device = InputDevice('/dev/input/event0') # Must support FF
183
184
# Check if device supports force feedback
185
caps = device.capabilities()
186
if ecodes.EV_FF not in caps:
187
print("Device does not support force feedback")
188
device.close()
189
exit()
190
191
# Create rumble effect
192
effect = Effect()
193
effect.type = ecodes.FF_RUMBLE
194
effect.id = -1 # Let kernel assign ID
195
effect.direction = 0x0000 # Direction (not used for rumble)
196
197
# Set up timing
198
effect.ff_replay.length = 1000 # 1 second duration
199
effect.ff_replay.delay = 0 # No delay
200
201
# Set up trigger (optional)
202
effect.ff_trigger.button = 0 # No button trigger
203
effect.ff_trigger.interval = 0 # No interval
204
205
# Configure rumble parameters
206
effect.u.rumble.strong_magnitude = 0x8000 # 50% strong motor
207
effect.u.rumble.weak_magnitude = 0x4000 # 25% weak motor
208
209
try:
210
# Upload effect to device
211
effect_id = device.upload_effect(effect)
212
print(f"Effect uploaded with ID: {effect_id}")
213
214
# Play the effect
215
device.write(ecodes.EV_FF, effect_id, 1) # Start effect
216
device.syn()
217
218
time.sleep(1.5) # Wait for effect to complete
219
220
# Stop effect (if still playing)
221
device.write(ecodes.EV_FF, effect_id, 0) # Stop effect
222
device.syn()
223
224
# Remove effect from device
225
device.erase_effect(effect_id)
226
print("Effect erased")
227
228
finally:
229
device.close()
230
```
231
232
### Periodic Sine Wave Effect
233
234
```python
235
from evdev import InputDevice, ecodes
236
from evdev.ff import Effect, Periodic, Replay, Envelope
237
import ctypes
238
import time
239
240
device = InputDevice('/dev/input/event0')
241
242
# Create periodic sine wave effect
243
effect = Effect()
244
effect.type = ecodes.FF_PERIODIC
245
effect.id = -1
246
effect.direction = 0x4000 # 90 degrees
247
248
# Timing
249
effect.ff_replay.length = 2000 # 2 seconds
250
effect.ff_replay.delay = 0
251
252
# No trigger
253
effect.ff_trigger.button = 0
254
effect.ff_trigger.interval = 0
255
256
# Periodic parameters
257
effect.u.periodic.waveform = ecodes.FF_SINE
258
effect.u.periodic.period = 100 # 100ms period (10 Hz)
259
effect.u.periodic.magnitude = 0x6000 # 75% magnitude
260
effect.u.periodic.offset = 0 # No DC offset
261
effect.u.periodic.phase = 0 # 0° phase
262
263
# Envelope for smooth start/end
264
effect.u.periodic.envelope.attack_length = 200 # 200ms attack
265
effect.u.periodic.envelope.attack_level = 0x0000
266
effect.u.periodic.envelope.fade_length = 200 # 200ms fade
267
effect.u.periodic.envelope.fade_level = 0x0000
268
269
try:
270
effect_id = device.upload_effect(effect)
271
272
# Play effect
273
device.write(ecodes.EV_FF, effect_id, 1)
274
device.syn()
275
276
time.sleep(2.5) # Wait for completion
277
278
device.erase_effect(effect_id)
279
280
finally:
281
device.close()
282
```
283
284
### Spring Effect
285
286
```python
287
from evdev import InputDevice, ecodes
288
from evdev.ff import Effect, Condition, Replay
289
import ctypes
290
291
device = InputDevice('/dev/input/event0')
292
293
# Create spring effect
294
effect = Effect()
295
effect.type = ecodes.FF_SPRING
296
effect.id = -1
297
effect.direction = 0x0000
298
299
# Long duration (until manually stopped)
300
effect.ff_replay.length = 0 # Infinite
301
effect.ff_replay.delay = 0
302
303
# No trigger
304
effect.ff_trigger.button = 0
305
effect.ff_trigger.interval = 0
306
307
# Spring parameters
308
effect.u.condition[0].right_saturation = 0x7FFF # Maximum right force
309
effect.u.condition[0].left_saturation = 0x7FFF # Maximum left force
310
effect.u.condition[0].right_coeff = 0x2000 # Spring coefficient
311
effect.u.condition[0].left_coeff = 0x2000 # Spring coefficient
312
effect.u.condition[0].deadband = 0x0000 # No deadband
313
effect.u.condition[0].center = 0x0000 # Center position
314
315
try:
316
effect_id = device.upload_effect(effect)
317
print("Spring effect active - move joystick to feel resistance")
318
319
# Start spring effect
320
device.write(ecodes.EV_FF, effect_id, 1)
321
device.syn()
322
323
time.sleep(5) # Let user test spring effect
324
325
# Stop effect
326
device.write(ecodes.EV_FF, effect_id, 0)
327
device.syn()
328
329
device.erase_effect(effect_id)
330
331
finally:
332
device.close()
333
```
334
335
### Constant Force Effect
336
337
```python
338
from evdev import InputDevice, ecodes
339
from evdev.ff import Effect, Constant, Replay, Envelope
340
import ctypes
341
import time
342
343
device = InputDevice('/dev/input/event0')
344
345
# Create constant force effect
346
effect = Effect()
347
effect.type = ecodes.FF_CONSTANT
348
effect.id = -1
349
effect.direction = 0x0000 # North direction
350
351
# Timing
352
effect.ff_replay.length = 1500 # 1.5 seconds
353
effect.ff_replay.delay = 0
354
355
# Constant force parameters
356
effect.u.constant.level = 0x4000 # 50% force level
357
358
# Envelope for smooth transitions
359
effect.u.constant.ff_envelope.attack_length = 300
360
effect.u.constant.ff_envelope.attack_level = 0x0000
361
effect.u.constant.ff_envelope.fade_length = 300
362
effect.u.constant.ff_envelope.fade_level = 0x0000
363
364
try:
365
effect_id = device.upload_effect(effect)
366
367
device.write(ecodes.EV_FF, effect_id, 1)
368
device.syn()
369
370
time.sleep(2)
371
372
device.erase_effect(effect_id)
373
374
finally:
375
device.close()
376
```
377
378
### UInput Force Feedback Device
379
380
```python
381
from evdev import UInput, ecodes
382
from evdev.ff import Effect, Rumble, Replay, UInputUpload
383
import ctypes
384
385
# Create virtual device with force feedback capability
386
ui = UInput({
387
ecodes.EV_FF: [ecodes.FF_RUMBLE, ecodes.FF_PERIODIC, ecodes.FF_CONSTANT]
388
}, name='virtual-ff-device', max_effects=16)
389
390
try:
391
print(f"Virtual FF device created: {ui.device.path}")
392
393
# Handle force feedback upload requests
394
# This would typically be done in an event loop
395
# monitoring the UInput device for upload requests
396
397
# Example: Manual effect creation for testing
398
effect = Effect()
399
effect.type = ecodes.FF_RUMBLE
400
effect.id = 0
401
effect.ff_replay.length = 500
402
effect.u.rumble.strong_magnitude = 0x8000
403
404
# In a real application, you would:
405
# 1. Monitor ui.device for EV_UINPUT events
406
# 2. Handle UI_FF_UPLOAD and UI_FF_ERASE requests
407
# 3. Use ui.begin_upload/end_upload for effect management
408
409
print("Force feedback device ready")
410
time.sleep(5) # Keep device alive for testing
411
412
finally:
413
ui.close()
414
```
415
416
### Effect Management
417
418
```python
419
from evdev import InputDevice, ecodes
420
from evdev.ff import Effect, Rumble, Replay
421
import ctypes
422
import time
423
424
device = InputDevice('/dev/input/event0')
425
426
# Create multiple effects
427
effects = []
428
429
# Light rumble
430
effect1 = Effect()
431
effect1.type = ecodes.FF_RUMBLE
432
effect1.id = -1
433
effect1.ff_replay.length = 500
434
effect1.u.rumble.strong_magnitude = 0x4000
435
effect1.u.rumble.weak_magnitude = 0x2000
436
437
# Strong rumble
438
effect2 = Effect()
439
effect2.type = ecodes.FF_RUMBLE
440
effect2.id = -1
441
effect2.ff_replay.length = 200
442
effect2.u.rumble.strong_magnitude = 0xFFFF
443
effect2.u.rumble.weak_magnitude = 0x8000
444
445
try:
446
# Upload all effects
447
effect_ids = []
448
for i, effect in enumerate([effect1, effect2]):
449
effect_id = device.upload_effect(effect)
450
effect_ids.append(effect_id)
451
print(f"Effect {i+1} uploaded with ID: {effect_id}")
452
453
# Play effects in sequence
454
for i, effect_id in enumerate(effect_ids):
455
print(f"Playing effect {i+1}")
456
device.write(ecodes.EV_FF, effect_id, 1)
457
device.syn()
458
time.sleep(0.8) # Wait between effects
459
460
print("Cleaning up effects...")
461
# Remove all effects
462
for effect_id in effect_ids:
463
device.erase_effect(effect_id)
464
465
finally:
466
device.close()
467
```
468
469
### Device Force Feedback Capabilities
470
471
```python
472
from evdev import InputDevice, ecodes, resolve_ecodes
473
474
device = InputDevice('/dev/input/event0')
475
476
caps = device.capabilities()
477
478
if ecodes.EV_FF in caps:
479
ff_capabilities = caps[ecodes.EV_FF]
480
print(f"Force feedback capabilities: {len(ff_capabilities)} effects")
481
482
# Resolve FF capability names
483
resolved = resolve_ecodes(ecodes.FF, ff_capabilities)
484
for effect_info in resolved:
485
if isinstance(effect_info, tuple):
486
name, code = effect_info
487
print(f" {name} ({code})")
488
else:
489
print(f" {effect_info}")
490
491
# Check specific capabilities
492
if ecodes.FF_RUMBLE in ff_capabilities:
493
print("Device supports rumble effects")
494
if ecodes.FF_PERIODIC in ff_capabilities:
495
print("Device supports periodic effects")
496
if ecodes.FF_CONSTANT in ff_capabilities:
497
print("Device supports constant force effects")
498
if ecodes.FF_SPRING in ff_capabilities:
499
print("Device supports spring effects")
500
501
print(f"Maximum effects: {device.ff_effects_count}")
502
else:
503
print("Device does not support force feedback")
504
505
device.close()
506
```