Browse Source

Emulating virtual gamepad from userspace using foohid

Thomas Buck 8 years ago
parent
commit
95ffc21508
8 changed files with 274 additions and 18 deletions
  1. 1
    1
      .gitignore
  2. 28
    0
      Makefile
  3. 3
    3
      README.md
  4. 0
    10
      protocol/Makefile
  5. 238
    0
      src/foohid.c
  6. 4
    4
      src/protocol.c
  7. 0
    0
      src/serial.c
  8. 0
    0
      src/serial.h

+ 1
- 1
.gitignore View File

@@ -1,3 +1,3 @@
1 1
 .DS_Store
2 2
 *.o
3
-protocol/proto
3
+bin

+ 28
- 0
Makefile View File

@@ -0,0 +1,28 @@
1
+# ----------------------------------------------------------------------------
2
+# "THE BEER-WARE LICENSE" (Revision 42):
3
+# <xythobuz@xythobuz.de> wrote this file.  As long as you retain this notice
4
+# you can do whatever you want with this stuff. If we meet some day, and you
5
+# think this stuff is worth it, you can buy me a beer in return.   Thomas Buck
6
+# ----------------------------------------------------------------------------
7
+
8
+CFLAGS ?= -Wall -pedantic -std=c11
9
+
10
+ifneq ("$(wildcard /Library/Extensions/foohid.kext/Contents/Info.plist)","")
11
+all: bin/protocol bin/foohid
12
+else
13
+all: bin/protocol
14
+	@echo "Could not find foohid.kext installed in /Library/Extensions!"
15
+endif
16
+
17
+bin/protocol: src/serial.o src/protocol.o
18
+	@mkdir -p bin
19
+	$(CC) -o bin/protocol src/serial.o src/protocol.o
20
+
21
+bin/foohid: src/serial.o src/foohid.o
22
+	@mkdir -p bin
23
+	$(CC) -o bin/foohid -framework IOKit src/serial.o src/foohid.o
24
+
25
+clean:
26
+	rm -rf bin
27
+	rm -rf src/*.o
28
+

+ 3
- 3
README.md View File

@@ -1,10 +1,10 @@
1 1
 # Mac OS X Gamepad Driver for Flysky compatible transmitters
2 2
 
3
+This project emulates a virtual Gamepad using input data from a serial port. This can be used to enable Flysky CT6A / CT6B compatible transmitters (Turbobrix, Exceed, Modelcraft) in games or simulators.
3 4
 
5
+**You need to install the virtual userspace IOKit HID driver *foohid*:**
4 6
 
5
-## Protocol Test Utility
6
-
7
-In the `protocol` subdirectory you can find a simple command-line app to test the proper decoding of the transmitters serial protocol.
7
+[GitHub page](https://github.com/unbit/foohid), [Binary releases](https://github.com/unbit/foohid/releases).
8 8
 
9 9
 ## Other Resources
10 10
 

+ 0
- 10
protocol/Makefile View File

@@ -1,10 +0,0 @@
1
-# Copyright 2015 Thomas Buck <xythobuz@xythobuz.de>
2
-
3
-all: proto
4
-
5
-proto: serial.o main.o
6
-	$(CC) -o proto serial.o main.o
7
-
8
-clean:
9
-	rm -rf proto main.o serial.o
10
-

+ 238
- 0
src/foohid.c View File

@@ -0,0 +1,238 @@
1
+/*
2
+ * ----------------------------------------------------------------------------
3
+ * "THE BEER-WARE LICENSE" (Revision 42):
4
+ * <xythobuz@xythobuz.de> wrote this file.  As long as you retain this notice
5
+ * you can do whatever you want with this stuff. If we meet some day, and you
6
+ * think this stuff is worth it, you can buy me a beer in return.   Thomas Buck
7
+ * ----------------------------------------------------------------------------
8
+ */
9
+
10
+#include <stdint.h>
11
+#include <stdio.h>
12
+#include <signal.h>
13
+#include <string.h>
14
+
15
+#include <IOKit/IOKitLib.h>
16
+
17
+#include "serial.h"
18
+
19
+#define BAUDRATE 115200
20
+#define PACKETSIZE 18
21
+#define HEADERBYTES 2
22
+#define HEADERBYTE_A 85
23
+#define HEADERBYTE_B 252
24
+#define CHECKSUMBYTES 2
25
+#define PAYLOADBYTES (PACKETSIZE - HEADERBYTES - CHECKSUMBYTES)
26
+#define CHANNELS 6
27
+#define CHANNELMAXIMUM 1022
28
+
29
+#define FOOHID_NAME "it_unbit_foohid"
30
+#define FOOHID_CREATE 0
31
+#define FOOHID_DESTROY 1
32
+#define FOOHID_SEND 2
33
+#define FOOHID_LIST 3
34
+#define VIRTUAL_DEVICE_NAME "Virtual Serial Transmitter"
35
+
36
+struct gamepad_report_t {
37
+    int16_t leftX;
38
+    int16_t leftY;
39
+    int16_t rightX;
40
+    int16_t rightY;
41
+    int16_t aux1;
42
+    int16_t aux2;
43
+};
44
+
45
+static int running = 1;
46
+static io_iterator_t iterator;
47
+static io_service_t service;
48
+static io_connect_t connect;
49
+static uint64_t input[4];
50
+static struct gamepad_report_t gamepad;
51
+
52
+/*
53
+ * This is my USB HID Descriptor for this emulated Gamepad.
54
+ * For more informations refer to:
55
+ * http://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
56
+ * http://www.usb.org/developers/hidpage#HID%20Descriptor%20Tool
57
+ */
58
+static char report_descriptor[36] = {
59
+    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
60
+    0x09, 0x05,                    // USAGE (Game Pad)
61
+    0xa1, 0x01,                    // COLLECTION (Application)
62
+    0xa1, 0x00,                    //   COLLECTION (Physical)
63
+    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
64
+    0x09, 0x30,                    //     USAGE (X)
65
+    0x09, 0x31,                    //     USAGE (Y)
66
+    0x09, 0x32,                    //     USAGE (Z)
67
+    0x09, 0x33,                    //     USAGE (Rx)
68
+    0x09, 0x34,                    //     USAGE (Ry)
69
+    0x09, 0x35,                    //     USAGE (Rz)
70
+    0x16, 0x01, 0xfe,              //     LOGICAL_MINIMUM (-511)
71
+    0x26, 0xff, 0x01,              //     LOGICAL_MAXIMUM (511)
72
+    0x75, 0x10,                    //     REPORT_SIZE (16)
73
+    0x95, 0x06,                    //     REPORT_COUNT (6)
74
+    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
75
+    0xc0,                          //     END_COLLECTION
76
+    0xc0                           // END_COLLECTION
77
+};
78
+
79
+static int foohidInit() {
80
+    // get a reference to the IOService
81
+    kern_return_t ret = IOServiceGetMatchingServices(kIOMasterPortDefault,
82
+                            IOServiceMatching("it_unbit_foohid"), &iterator);
83
+    if (ret != KERN_SUCCESS) {
84
+        printf("Unable to access foohid IOService\n");
85
+        return 1;
86
+    }
87
+
88
+    int found = 0;
89
+    while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
90
+        ret = IOServiceOpen(service, mach_task_self(), 0, &connect);
91
+        if (ret == KERN_SUCCESS) {
92
+            found = 1;
93
+            break;
94
+        }
95
+    }
96
+    IOObjectRelease(iterator);
97
+    if (!found) {
98
+        printf("Unable to open foohid IOService\n");
99
+        return 1;
100
+    }
101
+
102
+    input[0] = (uint64_t)strdup(VIRTUAL_DEVICE_NAME);
103
+    input[1] = strlen((char*)input[0]);
104
+
105
+    input[2] = (uint64_t)report_descriptor;
106
+    input[3] = sizeof(report_descriptor);
107
+
108
+    uint32_t output_count = 1;
109
+    uint64_t output = 0;
110
+    ret = IOConnectCallScalarMethod(connect, FOOHID_CREATE, input, 4, &output, &output_count);
111
+    if (ret != KERN_SUCCESS) {
112
+        printf("Unable to create virtual HID device\n");
113
+        return 1;
114
+    }
115
+
116
+    return 0;
117
+}
118
+
119
+static void foohidClose() {
120
+    uint32_t output_count = 1;
121
+    uint64_t output = 0;
122
+    kern_return_t ret = IOConnectCallScalarMethod(connect, FOOHID_DESTROY, input, 2, &output, &output_count);
123
+    if (ret != KERN_SUCCESS) {
124
+        printf("Unable to destroy virtual HID device\n");
125
+    }
126
+}
127
+
128
+static void foohidSend(uint16_t *data) {
129
+    input[2] = (uint64_t)&gamepad;
130
+    input[3] = sizeof(struct gamepad_report_t);
131
+
132
+    for (int i = 0; i < CHANNELS; i++) {
133
+        if (data[i] > CHANNELMAXIMUM) {
134
+            data[i] = CHANNELMAXIMUM;
135
+        }
136
+    }
137
+
138
+    gamepad.leftX = data[0] - 511;
139
+    gamepad.leftY = data[1] - 511;
140
+    gamepad.rightX = data[2] - 511;
141
+    gamepad.rightY = data[3] - 511;
142
+    gamepad.aux1 = data[4] - 511;
143
+    gamepad.aux2 = data[5] - 511;
144
+
145
+    uint32_t output_count = 1;
146
+    uint64_t output = 0;
147
+    kern_return_t ret = IOConnectCallScalarMethod(connect, FOOHID_SEND, input, 4, &output, &output_count);
148
+    if (ret != KERN_SUCCESS) {
149
+        printf("Unable to send packet to virtual HID device\n");
150
+    }
151
+}
152
+
153
+static void signalHandler(int signo) {
154
+    running = 0;
155
+}
156
+
157
+int main(int argc, char* argv[]) {
158
+    if (argc != 2) {
159
+        printf("Usage:\n\t%s /dev/serial_port\n", argv[0]);
160
+        return 1;
161
+    }
162
+
163
+    int fd = serialOpen(argv[1], BAUDRATE);
164
+    if (fd == -1) {
165
+        return 1;
166
+    }
167
+
168
+    if (foohidInit() != 0) {
169
+        serialClose(fd);
170
+        return 1;
171
+    }
172
+
173
+    if (signal(SIGINT, signalHandler) == SIG_ERR) {
174
+        perror("Couldn't register signal handler");
175
+        return 1;
176
+    }
177
+
178
+    while (running != 0) {
179
+        if (serialHasChar(fd)) {
180
+            unsigned char c1;
181
+            serialReadChar(fd, (char*)&c1);
182
+            if (c1 == HEADERBYTE_A) {
183
+                // Found first byte of protocol start
184
+                while (!serialHasChar(fd)) {
185
+                    if (running == 0) {
186
+                        serialClose(fd);
187
+                        return 0;
188
+                    }
189
+                }
190
+
191
+                unsigned char c2;
192
+                serialReadChar(fd, (char*)&c2);
193
+                if (c2 == HEADERBYTE_B) {
194
+                    // Protocol start has been found, read payload
195
+                    unsigned char data[PAYLOADBYTES];
196
+                    int read = 0;
197
+                    while ((read < PAYLOADBYTES) && (running != 0)) {
198
+                        read += serialReadRaw(fd, (char*)data + read, PAYLOADBYTES - read);
199
+                    }
200
+
201
+                    // Read 16bit checksum
202
+                    unsigned char checksumData[CHECKSUMBYTES];
203
+                    read = 0;
204
+                    while ((read < CHECKSUMBYTES) && (running != 0)) {
205
+                        read += serialReadRaw(fd, (char*)checksumData + read,
206
+                                CHECKSUMBYTES - read);
207
+                    }
208
+
209
+                    // Check if checksum matches
210
+                    uint16_t checksum = 0;
211
+                    for (int i = 0; i < PAYLOADBYTES; i++) {
212
+                        checksum += data[i];
213
+                    }
214
+
215
+                    if (checksum != ((checksumData[0] << 8) | checksumData[1])) {
216
+                        printf("Wrong checksum: %d != %d\n",
217
+                                checksum, ((checksumData[0] << 8) | checksumData[1]));
218
+                    } else {
219
+                        // Decode channel values
220
+                        uint16_t buff[CHANNELS + 1];
221
+                        for (int i = 0; i < (CHANNELS + 1); i++) {
222
+                            buff[i] = data[2 * i] << 8;
223
+                            buff[i] |= data[(2 * i) + 1];
224
+                        }
225
+
226
+                        foohidSend(buff);
227
+                    }
228
+                }
229
+            }
230
+        }
231
+    }
232
+
233
+    serialClose(fd);
234
+    foohidClose();
235
+
236
+    return 0;
237
+}
238
+

protocol/main.c → src/protocol.c View File


protocol/serial.c → src/serial.c View File


protocol/serial.h → src/serial.h View File


Loading…
Cancel
Save