Mac OS X gamepad emulator for serial RC transmitters
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Thread.m 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. //
  2. // Thread.m
  3. // SerialGamepad
  4. //
  5. // Created by Thomas Buck on 15.12.15.
  6. // Copyright © 2015 xythobuz. All rights reserved.
  7. //
  8. #import <termios.h>
  9. #import <fcntl.h>
  10. #import <unistd.h>
  11. #import "Thread.h"
  12. #import "fooHID.h"
  13. #import "MainWindow.h"
  14. #define CHANNELS 6
  15. #define TESTCHANNEL 2
  16. #define PACKETSIZE 18
  17. #define HEADERBYTES 2
  18. #define CHECKSUMBYTES 2
  19. #define PAYLOADBYTES (PACKETSIZE - HEADERBYTES - CHECKSUMBYTES)
  20. #define HEADERBYTE_A 85
  21. #define HEADERBYTE_B 252
  22. enum ThreadState {
  23. READ_FIRST_BYTE,
  24. READ_SECOND_BYTE,
  25. READ_PAYLOAD,
  26. READ_CHECKSUM
  27. };
  28. @implementation Thread
  29. @synthesize running, fd, portName, mainWindow;
  30. - (id)initWithWindow:(MainWindow *)window {
  31. self = [super init];
  32. if (self != nil) {
  33. mainWindow = window;
  34. }
  35. return self;
  36. }
  37. - (NSInteger)openPort {
  38. if (portName == nil) {
  39. return 1;
  40. }
  41. // Open port read-only, without controlling terminal, non-blocking
  42. fd = open([portName UTF8String], O_RDONLY | O_NOCTTY | O_NONBLOCK);
  43. if (fd == -1) {
  44. NSLog(@"Error opening serial port \"%@\"!\n", portName);
  45. return 1;
  46. }
  47. fcntl(fd, F_SETFL, 0); // Enable blocking I/O
  48. // Read current settings
  49. struct termios options;
  50. tcgetattr(fd, &options);
  51. options.c_lflag = 0;
  52. options.c_oflag = 0;
  53. options.c_iflag = 0;
  54. options.c_cflag = 0;
  55. options.c_cflag |= CS8; // 8 data bits
  56. options.c_cflag |= CREAD; // Enable receiver
  57. options.c_cflag |= CLOCAL; // Ignore modem status lines
  58. cfsetispeed(&options, B115200);
  59. cfsetospeed(&options, B115200);
  60. options.c_cc[VMIN] = 0; // Return even with zero bytes...
  61. options.c_cc[VTIME] = 1; // ...but only after .1 seconds
  62. // Set new settings
  63. tcsetattr(fd, TCSANOW, &options);
  64. tcflush(fd, TCIOFLUSH);
  65. return 0;
  66. }
  67. - (void)main {
  68. enum ThreadState state = READ_FIRST_BYTE;
  69. unsigned char c = 0;
  70. unsigned char buffer[PAYLOADBYTES];
  71. unsigned char checksum[CHECKSUMBYTES];
  72. uint16_t channels[CHANNELS + 1];
  73. int received = 0;
  74. NSLog(@"Connection running...\n");
  75. running = YES;
  76. while (running) {
  77. if (state == READ_FIRST_BYTE) {
  78. if (read(fd, &c, 1) == 1) {
  79. if (c == HEADERBYTE_A) {
  80. state = READ_SECOND_BYTE;
  81. }
  82. }
  83. } else if (state == READ_SECOND_BYTE) {
  84. if (read(fd, &c, 1) == 1) {
  85. if (c == HEADERBYTE_B) {
  86. state = READ_PAYLOAD;
  87. received = 0;
  88. } else {
  89. state = READ_FIRST_BYTE;
  90. }
  91. }
  92. } else if (state == READ_PAYLOAD) {
  93. ssize_t ret = read(fd, buffer + received, PAYLOADBYTES - received);
  94. if (ret >= 0) received += ret;
  95. if (received >= PAYLOADBYTES) {
  96. state = READ_CHECKSUM;
  97. received = 0;
  98. }
  99. } else if (state == READ_CHECKSUM) {
  100. ssize_t ret = read(fd, checksum + received, CHECKSUMBYTES - received);
  101. if (ret >= 0) received += ret;
  102. if (received >= CHECKSUMBYTES) {
  103. state = READ_FIRST_BYTE;
  104. uint16_t sum = 0;
  105. for (int i = 0; i < PAYLOADBYTES; i++) {
  106. sum += buffer[i];
  107. }
  108. if (sum != ((checksum[0] << 8) | checksum[1])) {
  109. NSLog(@"Wrong checksum: %d != %d\n", sum, ((checksum[0] << 8) | checksum[1]));
  110. } else {
  111. for (int i = 0; i < (CHANNELS + 1); i++) {
  112. channels[i] = buffer[2 * i] << 8;
  113. channels[i] |= buffer[(2 * i) + 1];
  114. if (i < CHANNELS) {
  115. channels[i] -= 1000;
  116. }
  117. }
  118. if (channels[CHANNELS] != channels[TESTCHANNEL]) {
  119. // This channel contains the throttle value even if it has been disabled using the switches
  120. // on the transmitter. Therefore, there's not really a warning required here.
  121. //NSLog(@"Wrong test channel value: %d != %d\n", channels[CHANNELS], channels[TESTCHANNEL]);
  122. }
  123. NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:CHANNELS];
  124. for (int i = 0; i < CHANNELS; i++) {
  125. [arr addObject:[[NSNumber alloc] initWithInteger:(NSInteger)channels[i]]];
  126. }
  127. [mainWindow performSelectorOnMainThread:@selector(setChannels:) withObject:arr waitUntilDone:NO];
  128. }
  129. }
  130. } else {
  131. NSLog(@"Invalid state?!\n");
  132. state = READ_FIRST_BYTE;
  133. }
  134. }
  135. // Ensure the GUI is always reset after clicking Disconnect
  136. NSArray *zero = [NSArray arrayWithObjects:[NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], nil];
  137. [mainWindow performSelectorOnMainThread:@selector(setChannels:) withObject:zero waitUntilDone:NO];
  138. close(fd);
  139. NSLog(@"Connection closed...\n");
  140. fd = -1;
  141. }
  142. @end