| /**************************************************************************** |
| * apps/testing/fftest/fftest.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you 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. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <nuttx/bits.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/input/ff.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define N_EFFECTS 6 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| FAR static const char *g_effect_names[] = |
| { |
| "Sine vibration", |
| "Constant Force", |
| "Spring Condition", |
| "Damping Condition", |
| "Strong Rumble", |
| "Weak Rumble" |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| int main(int argc, FAR char **argv) |
| { |
| unsigned char fffeatures[1 + FF_MAX / 8 / sizeof(unsigned char)]; |
| FAR const char *device_file_name = "/dev/input_ff0"; |
| struct ff_effect effects[N_EFFECTS]; |
| struct ff_event_s play; |
| struct ff_event_s stop; |
| struct ff_event_s gain; |
| int neffects; /* Number of effects the device can play at the same time */ |
| int fd; |
| int i; |
| |
| printf("Force feedback test program.\n"); |
| for (i = 1; i < argc; i++) |
| { |
| if (strncmp(argv[i], "--help", 8) == 0) |
| { |
| printf("Usage: %s /dev/input_ffX\n", argv[0]); |
| printf("Tests the force feedback driver\n"); |
| return -EINVAL; |
| } |
| else |
| { |
| device_file_name = argv[i]; |
| } |
| } |
| |
| fd = open(device_file_name, O_WRONLY); |
| if (fd < 0) |
| { |
| perror("Open device file"); |
| return -errno; |
| } |
| |
| printf("Device %s opened\n", device_file_name); |
| |
| /* Query device */ |
| |
| printf("features:\n"); |
| |
| /* Force feedback effects */ |
| |
| memset(fffeatures, 0, sizeof(fffeatures)); |
| if (ioctl(fd, EVIOCGBIT, fffeatures) < 0) |
| { |
| perror("Ioctl force feedback features query"); |
| goto out; |
| } |
| |
| printf(" * Force feedback effects types: "); |
| if (test_bit(FF_CONSTANT, fffeatures)) |
| { |
| printf("Constant, "); |
| } |
| |
| if (test_bit(FF_PERIODIC, fffeatures)) |
| { |
| printf("Periodic, "); |
| } |
| |
| if (test_bit(FF_RAMP, fffeatures)) |
| { |
| printf("Ramp, "); |
| } |
| |
| if (test_bit(FF_SPRING, fffeatures)) |
| { |
| printf("Spring, "); |
| } |
| |
| if (test_bit(FF_FRICTION, fffeatures)) |
| { |
| printf("Friction, "); |
| } |
| |
| if (test_bit(FF_DAMPER, fffeatures)) |
| { |
| printf("Damper, "); |
| } |
| |
| if (test_bit(FF_RUMBLE, fffeatures)) |
| { |
| printf("Rumble, "); |
| } |
| |
| if (test_bit(FF_INERTIA, fffeatures)) |
| { |
| printf("Inertia, "); |
| } |
| |
| if (test_bit(FF_GAIN, fffeatures)) |
| { |
| printf("Gain, "); |
| } |
| |
| if (test_bit(FF_AUTOCENTER, fffeatures)) |
| { |
| printf("Autocenter, "); |
| } |
| |
| printf("\n Force feedback periodic effects: "); |
| if (test_bit(FF_SQUARE, fffeatures)) |
| { |
| printf("Square, "); |
| } |
| |
| if (test_bit(FF_TRIANGLE, fffeatures)) |
| { |
| printf("Triangle, "); |
| } |
| |
| if (test_bit(FF_SINE, fffeatures)) |
| { |
| printf("Sine, "); |
| } |
| |
| if (test_bit(FF_SAW_UP, fffeatures)) |
| { |
| printf("Saw up, "); |
| } |
| |
| if (test_bit(FF_SAW_DOWN, fffeatures)) |
| { |
| printf("Saw down, "); |
| } |
| |
| if (test_bit(FF_CUSTOM, fffeatures)) |
| { |
| printf("Custom, "); |
| } |
| |
| printf("\n ["); |
| for (i = 0; i < sizeof(fffeatures) / sizeof(unsigned char); i++) |
| { |
| printf("%02X ", fffeatures[i]); |
| } |
| |
| printf("]\n"); |
| printf(" * Number of simultaneous effects: "); |
| if (ioctl(fd, EVIOCGEFFECTS, &neffects) < 0) |
| { |
| perror("Ioctl number of effects"); |
| goto out; |
| } |
| |
| printf("%d\n\n", neffects); |
| |
| /* Set master gain to 75% if supported */ |
| |
| if (test_bit(FF_GAIN, fffeatures)) |
| { |
| memset(&gain, 0, sizeof(gain)); |
| gain.code = FF_GAIN; |
| gain.value = 0xc000; /* [0, 0xFFFF] */ |
| |
| printf("Setting master gain to 75%% ... "); |
| fflush(stdout); |
| |
| if (write(fd, &gain, sizeof(gain)) != sizeof(gain)) |
| { |
| perror("Error: write fd failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK\n"); |
| } |
| } |
| |
| /* Download a periodic sinusoidal effect */ |
| |
| memset(&effects[0], 0, sizeof(effects[0])); |
| effects[0].type = FF_PERIODIC; |
| effects[0].id = -1; |
| effects[0].u.periodic.waveform = FF_SINE; |
| effects[0].u.periodic.period = 100; /* 0.1 second */ |
| effects[0].u.periodic.magnitude = 0x7fff; |
| effects[0].u.periodic.offset = 0; |
| effects[0].u.periodic.phase = 0; |
| effects[0].direction = 0x4000; /* Along X axis */ |
| effects[0].u.periodic.envelope.attack_length = 1000; |
| effects[0].u.periodic.envelope.attack_level = 0x7fff; |
| effects[0].u.periodic.envelope.fade_length = 1000; |
| effects[0].u.periodic.envelope.fade_level = 0x7fff; |
| effects[0].trigger.button = 0; |
| effects[0].trigger.interval = 0; |
| effects[0].replay.length = 20000; /* 20 seconds */ |
| effects[0].replay.delay = 1000; |
| |
| printf("Uploading effect #0 (Periodic sinusoidal) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[0]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[0].id); |
| } |
| |
| /* Download a constant effect */ |
| |
| effects[1].type = FF_CONSTANT; |
| effects[1].id = -1; |
| effects[1].u.constant.level = 0x2000; /* Strength : 25 % */ |
| effects[1].direction = 0x6000; |
| effects[1].u.constant.envelope.attack_length = 1000; |
| effects[1].u.constant.envelope.attack_level = 0x1000; |
| effects[1].u.constant.envelope.fade_length = 1000; |
| effects[1].u.constant.envelope.fade_level = 0x1000; |
| effects[1].trigger.button = 0; |
| effects[1].trigger.interval = 0; |
| effects[1].replay.length = 20000; /* 20 seconds */ |
| effects[1].replay.delay = 0; |
| |
| printf("Uploading effect #1 (Constant) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[1]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[1].id); |
| } |
| |
| /* Download a condition spring effect */ |
| |
| effects[2].type = FF_SPRING; |
| effects[2].id = -1; |
| effects[2].u.condition[0].right_saturation = 0x7fff; |
| effects[2].u.condition[0].left_saturation = 0x7fff; |
| effects[2].u.condition[0].right_coeff = 0x2000; |
| effects[2].u.condition[0].left_coeff = 0x2000; |
| effects[2].u.condition[0].deadband = 0x0; |
| effects[2].u.condition[0].center = 0x0; |
| effects[2].u.condition[1] = effects[2].u.condition[0]; |
| effects[2].trigger.button = 0; |
| effects[2].trigger.interval = 0; |
| effects[2].replay.length = 20000; /* 20 seconds */ |
| effects[2].replay.delay = 0; |
| |
| printf("Uploading effect #2 (Spring) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[2]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[2].id); |
| } |
| |
| /* Download a condition damper effect */ |
| |
| effects[3].type = FF_DAMPER; |
| effects[3].id = -1; |
| effects[3].u.condition[0].right_saturation = 0x7fff; |
| effects[3].u.condition[0].left_saturation = 0x7fff; |
| effects[3].u.condition[0].right_coeff = 0x2000; |
| effects[3].u.condition[0].left_coeff = 0x2000; |
| effects[3].u.condition[0].deadband = 0x0; |
| effects[3].u.condition[0].center = 0x0; |
| effects[3].u.condition[1] = effects[3].u.condition[0]; |
| effects[3].trigger.button = 0; |
| effects[3].trigger.interval = 0; |
| effects[3].replay.length = 20000; /* 20 seconds */ |
| effects[3].replay.delay = 0; |
| |
| printf("Uploading effect #3 (Damper) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[3]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[3].id); |
| } |
| |
| /* A strong rumbling effect */ |
| |
| effects[4].type = FF_RUMBLE; |
| effects[4].id = -1; |
| effects[4].u.rumble.strong_magnitude = 0x8000; |
| effects[4].u.rumble.weak_magnitude = 0; |
| effects[4].replay.length = 5000; |
| effects[4].replay.delay = 1000; |
| |
| printf("Uploading effect #4 (Strong rumble, with heavy motor) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[4]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[4].id); |
| } |
| |
| /* A weak rumbling effect */ |
| |
| effects[5].type = FF_RUMBLE; |
| effects[5].id = -1; |
| effects[5].u.rumble.strong_magnitude = 0; |
| effects[5].u.rumble.weak_magnitude = 0xc000; |
| effects[5].replay.length = 5000; |
| effects[5].replay.delay = 0; |
| |
| printf("Uploading effect #5 (Weak rumble, with light motor) ... "); |
| fflush(stdout); |
| |
| if (ioctl(fd, EVIOCSFF, &effects[5]) < 0) |
| { |
| perror("Error: ioctl failed"); |
| goto out; |
| } |
| else |
| { |
| printf("OK (id %d)\n", effects[5].id); |
| } |
| |
| /* Ask user what effects to play */ |
| |
| do |
| { |
| printf("Enter effect number, -1 to exit\n"); |
| i = -1; |
| if (scanf("%d", &i) == EOF) |
| { |
| printf("Read error\n"); |
| } |
| else if (i >= 0 && i < N_EFFECTS) |
| { |
| memset(&play, 0, sizeof(play)); |
| play.code = effects[i].id; |
| play.value = 1; |
| if (write(fd, &play, sizeof(play)) < 0) |
| { |
| perror("Play effect failed"); |
| goto out; |
| } |
| |
| printf("Now Playing: %s\n", g_effect_names[i]); |
| } |
| else if (i == -2) |
| { |
| /* Crash test */ |
| |
| i = *((FAR int *)0); |
| printf("Crash test: %d\n", i); |
| } |
| else if (i != -1) |
| { |
| printf("No such effect\n"); |
| } |
| } |
| while (i >= 0); |
| |
| /* Stop the effects */ |
| |
| printf("Stopping effects\n"); |
| for (i = 0; i < N_EFFECTS; i++) |
| { |
| memset(&stop, 0, sizeof(stop)); |
| stop.code = effects[i].id; |
| stop.value = 0; |
| |
| if (write(fd, &stop, sizeof(stop)) < 0) |
| { |
| perror("Error write fd failed"); |
| goto out; |
| } |
| } |
| |
| out: |
| close(fd); |
| return errno > 0 ? -errno : 0; |
| } |