or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

device-operations.mdevent-codes.mdevent-processing.mdforce-feedback.mdindex.mdvirtual-devices.md

force-feedback.mddocs/

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

```