main.c (9131B)
1 #include <assert.h> 2 #include <errno.h> 3 #include <stdbool.h> 4 #include <stdio.h> 5 #include <stdint.h> 6 7 #include <fcntl.h> 8 #include <signal.h> 9 #include <sys/ioctl.h> 10 #include <sys/select.h> 11 #include <sys/stat.h> 12 #include <unistd.h> 13 14 // TODO: Use syslog.h for logging 15 16 // https://www.kernel.org/doc/Documentation/hid/hidraw.txt 17 #include <linux/hidraw.h> 18 19 #define UNREACHABLE assert(false) 20 21 enum State 22 { 23 DEVICE_HANDLE_INACCESSIBLE, 24 DEVICE_HANDLE_ACCESSIBLE, 25 DEVICE_HANDLE_OPEN, 26 DEVICE_SENDING_DATA, 27 FATAL_ERROR, 28 }; 29 30 void print_state(enum State state) 31 { 32 switch (state) 33 { 34 case DEVICE_HANDLE_INACCESSIBLE: 35 printf("DEVICE_HANDLE_INACCESSIBLE\n"); 36 break; 37 case DEVICE_HANDLE_ACCESSIBLE: 38 printf("DEVICE_HANDLE_ACCESSIBLE\n"); 39 break; 40 case DEVICE_HANDLE_OPEN: 41 printf("DEVICE_HANDLE_OPEN\n"); 42 break; 43 case DEVICE_SENDING_DATA: 44 printf("DEVICE_SENDING_DATA\n"); 45 break; 46 case FATAL_ERROR: 47 printf("FATAL_ERROR\n"); 48 break; 49 default: 50 assert(false); 51 break; 52 } 53 } 54 55 volatile static bool running = true; 56 57 static void stop_running(int signal) 58 { 59 (void)signal; 60 running = false; 61 } 62 63 // Made possible by Henryk Plötz' excellent work: 64 // https://hackaday.io/project/5301-reverse-engineering-a-low-cost-usb-co-monitor 65 int main(void) 66 { 67 { 68 struct sigaction act = 69 { 70 .sa_handler = stop_running, 71 }; 72 int failure = sigaction(SIGINT, &act, NULL); 73 failure |= sigaction(SIGTERM, &act, NULL); 74 if (failure) 75 { 76 printf("ERROR: Failed to register signal handler.\n"); 77 return 1; 78 } 79 } 80 81 // NOTE: Included udev rules create this symlink to the appropriate hidraw 82 // entry. 83 const char *hid_file_path = "/dev/co2mini0"; 84 const char *temperature_file_path = "/tmp/co2minimon_temp"; 85 const char *co2_file_path = "/tmp/co2minimon_co2"; 86 87 enum State state = DEVICE_HANDLE_INACCESSIBLE; 88 int device_handle = -1; 89 90 while (running) 91 { 92 switch (state) 93 { 94 case DEVICE_HANDLE_INACCESSIBLE: 95 { 96 if (access(hid_file_path, F_OK) == 0) 97 { 98 state = DEVICE_HANDLE_ACCESSIBLE; 99 } 100 else 101 { 102 sleep(30); 103 } 104 } 105 break; case DEVICE_HANDLE_ACCESSIBLE: 106 { 107 errno = 0; 108 device_handle = open(hid_file_path, O_RDWR); 109 if (device_handle >= 0) 110 { 111 state = DEVICE_HANDLE_OPEN; 112 } 113 else if (errno == ENOENT) 114 { 115 state = DEVICE_HANDLE_INACCESSIBLE; 116 } 117 else if (errno != EINTR) 118 { 119 printf("ERROR: Failed to open HID.\n"); 120 state = FATAL_ERROR; 121 } 122 } 123 break; case DEVICE_HANDLE_OPEN: 124 { 125 uint8_t key[8] = {0}; 126 errno = 0; 127 int result = ioctl(device_handle, HIDIOCSFEATURE(sizeof(key)), key); 128 if (result == sizeof(key)) 129 { 130 state = DEVICE_SENDING_DATA; 131 } 132 else if (result < 0) 133 { 134 if (errno == ENODEV) 135 { 136 // TODO: do we need to do this in any other cases? 137 close(device_handle); 138 unlink(co2_file_path); 139 unlink(temperature_file_path); 140 state = DEVICE_HANDLE_ACCESSIBLE; 141 } 142 else if (errno != EINTR) 143 { 144 printf("ERROR: Failed to send feature report.\n"); 145 state = FATAL_ERROR; 146 } 147 } 148 else 149 { 150 // TODO: when does this happen 151 printf("ERROR: Failed to send feature report.\n"); 152 state = FATAL_ERROR; 153 } 154 } 155 break; case DEVICE_SENDING_DATA: 156 { 157 fd_set read_fds; 158 FD_ZERO(&read_fds); 159 FD_SET(device_handle, &read_fds); 160 errno = 0; 161 int ready = select(device_handle + 1, &read_fds, NULL, NULL, &(struct timeval){ .tv_sec = 15 }); 162 if (ready == -1) 163 { 164 if (errno != EINTR) 165 { 166 printf("ERROR: Failed to select() device handle.\n"); 167 state = FATAL_ERROR; 168 } 169 } 170 else if (ready == 0) 171 { 172 // select() timed out 173 // 174 // If we go too long with an empty pipe, the device may have 175 // stopped sending data. One situation where I've observed this can 176 // happen is when the system resumes from hibernation. In this 177 // case, all calls to read() will block until we resend the feature 178 // report. 179 // 180 // I'm not sure why this happens, but my guess would be that the 181 // device stops sending new data if the data hasn't been read in a 182 // while. 183 state = DEVICE_HANDLE_OPEN; 184 //TODO: why do we ooscillate? 185 } 186 else 187 { 188 uint8_t data[8] = {0}; 189 errno = 0; 190 int bytes_read = read(device_handle, &data, sizeof(data)); 191 if (bytes_read < 0 && errno != EINTR) 192 { 193 if (errno == EIO) 194 { 195 state = DEVICE_HANDLE_OPEN; 196 break; 197 } 198 else 199 { 200 printf("ERROR: Failed to read device handle\n"); 201 state = FATAL_ERROR; 202 break; 203 } 204 } 205 206 uint8_t item = data[0]; 207 uint8_t msb = data[1]; 208 uint8_t lsb = data[2]; 209 uint8_t checksum = data[3]; 210 uint8_t end = data[4]; 211 212 if (bytes_read == sizeof(data) 213 && end == 0x0d 214 && (item == 0x42 || item == 0x50) 215 && ((item + msb + lsb) & 0xFF) == checksum) 216 { 217 uint16_t value = (((uint16_t)msb) << 8) | lsb; 218 char buf[1024] = {0}; 219 int str_len = 0; 220 mode_t create_mode = S_IRUSR | S_IWUSR; 221 int open_mode = O_WRONLY | O_CREAT | O_TRUNC; 222 223 switch(item) 224 { 225 case 0x42: 226 { 227 double t_celsius = value / 16.0 - 273.15; 228 str_len = snprintf(buf, sizeof(buf), "%.2f", t_celsius); 229 assert(str_len > 0); 230 int f = open(temperature_file_path, open_mode, create_mode); 231 if (f == -1) 232 { 233 printf("ERROR: Failed to open output file for temperature.\n"); 234 state = FATAL_ERROR; 235 } 236 write(f, buf, str_len); 237 close(f); 238 } break; 239 240 case 0x50: 241 { 242 str_len = snprintf(buf, sizeof(buf), "%d", value); 243 assert(str_len > 0); 244 int f = open(co2_file_path, open_mode, create_mode); 245 if (f == -1) 246 { 247 printf("ERROR: Failed to open output file for CO2.\n"); 248 state = FATAL_ERROR; 249 } 250 write(f, buf, str_len); 251 close(f); 252 } break; 253 } 254 } 255 } 256 } 257 break; case FATAL_ERROR: 258 running = false; 259 break; default: 260 UNREACHABLE; 261 break; 262 } 263 } 264 265 unlink(co2_file_path); 266 unlink(temperature_file_path); 267 close(device_handle); 268 269 return state == FATAL_ERROR; 270 }