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.
 
 
 

730 lines
18 KiB

  1. /*
  2. * Labjack Tools
  3. * Copyright (c) 2003-2007 Jim Paris <jim@jtan.com>
  4. *
  5. * This is free software; you can redistribute it and/or modify it and
  6. * it is provided under the terms of version 2 of the GNU General Public
  7. * License as published by the Free Software Foundation; see COPYING.
  8. */
  9. #include <stdint.h>
  10. #include <stdlib.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <errno.h>
  14. #include <sys/time.h>
  15. #include <time.h>
  16. #include <sys/stat.h>
  17. #include <signal.h>
  18. #include <unistd.h>
  19. #include "debug.h"
  20. #include "ue9.h"
  21. #include "ue9error.h"
  22. #include "nerdjack.h"
  23. #include "opt.h"
  24. #include "version.h"
  25. #include "compat.h"
  26. #include "ethstream.h"
  27. #include "example.inc"
  28. #define DEFAULT_HOST "192.168.1.209"
  29. #define UE9_COMMAND_PORT 52360
  30. #define UE9_DATA_PORT 52361
  31. #define MAX_CHANNELS 256
  32. struct callbackInfo {
  33. struct ue9Calibration calib;
  34. int convert;
  35. int maxlines;
  36. };
  37. struct options opt[] = {
  38. {'a', "address", "string", "host/address of device (192.168.1.209)"},
  39. {'n', "numchannels", "n", "sample the first N ADC channels (2)"},
  40. {'C', "channels", "a,b,c", "sample channels a, b, and c"},
  41. {'r', "rate", "hz", "sample each channel at this rate (8000.0)"},
  42. {'L', "labjack", NULL, "Force LabJack device"},
  43. {'t', "timers", "a,b,c", "set LabJack timer modes to a, b, and c"},
  44. {'T', "timerdivisor", "n", "set LabJack timer divisor to n"},
  45. {'N', "nerdjack", NULL, "Force NerdJack device"},
  46. {'d', "detect", NULL, "Detect NerdJack IP address"},
  47. {'R', "range", "a,b",
  48. "Set range on NerdJack for channels 0-5,6-11 to either 5 or 10 (10,10)"},
  49. {'o', "oneshot", NULL, "don't retry in case of errors"},
  50. {'f', "forceretry", NULL, "retry no matter what happens"},
  51. {'c', "convert", NULL, "convert output to volts"},
  52. {'H', "converthex", NULL, "convert output to hex"},
  53. {'m', "showmem", NULL, "output memory stats with data (NJ only)"},
  54. {'l', "lines", "num", "if set, output this many lines and quit"},
  55. {'h', "help", NULL, "this help"},
  56. {'v', "verbose", NULL, "be verbose"},
  57. {'V', "version", NULL, "show version number and exit"},
  58. {'i', "info", NULL, "get info from device (NJ only)"},
  59. {'X', "examples", NULL, "show ethstream examples and exit"},
  60. {0, NULL, NULL, NULL}
  61. };
  62. int doStream(const char *address, uint8_t scanconfig, uint16_t scaninterval,
  63. int *channel_list, int channel_count,
  64. int *timer_mode_list, int timer_mode_count, int timer_divisor,
  65. int convert, int maxlines);
  66. int nerdDoStream(const char *address, int *channel_list, int channel_count,
  67. int precision, unsigned long period, int convert, int lines,
  68. int showmem);
  69. int data_callback(int channels, uint16_t * data, void *context);
  70. int columns_left = 0;
  71. void handle_sig(int sig)
  72. {
  73. while (columns_left--) {
  74. printf(" 0");
  75. }
  76. fflush(stdout);
  77. exit(0);
  78. }
  79. int main(int argc, char *argv[])
  80. {
  81. int optind;
  82. char *optarg, *endp;
  83. char c;
  84. int tmp, i;
  85. FILE *help = stderr;
  86. char *address = strdup(DEFAULT_HOST);
  87. double desired_rate = 8000.0;
  88. int lines = 0;
  89. double actual_rate;
  90. int oneshot = 0;
  91. int forceretry = 0;
  92. int convert = CONVERT_DEC;
  93. int showmem = 0;
  94. int inform = 0;
  95. uint8_t scanconfig;
  96. uint16_t scaninterval;
  97. int timer_mode_list[UE9_TIMERS];
  98. int timer_mode_count = 0;
  99. int timer_divisor = 1;
  100. int channel_list[MAX_CHANNELS];
  101. int channel_count = 0;
  102. int nerdjack = 0;
  103. int labjack = 0;
  104. int detect = 0;
  105. int precision = 0;
  106. int addressSpecified = 0;
  107. int donerdjack = 0;
  108. unsigned long period = NERDJACK_CLOCK_RATE / desired_rate;
  109. /* Parse arguments */
  110. opt_init(&optind);
  111. while ((c = opt_parse(argc, argv, &optind, &optarg, opt)) != 0) {
  112. switch (c) {
  113. case 'a':
  114. free(address);
  115. address = strdup(optarg);
  116. addressSpecified = 1;
  117. break;
  118. case 'n':
  119. channel_count = 0;
  120. tmp = strtol(optarg, &endp, 0);
  121. if (*endp || tmp < 1 || tmp > MAX_CHANNELS) {
  122. info("bad number of channels: %s\n", optarg);
  123. goto printhelp;
  124. }
  125. for (i = 0; i < tmp; i++)
  126. channel_list[channel_count++] = i;
  127. break;
  128. case 'C':
  129. channel_count = 0;
  130. do {
  131. tmp = strtol(optarg, &endp, 0);
  132. if (*endp != '\0' && *endp != ',') {
  133. info("bad channel number: %s\n",
  134. optarg);
  135. goto printhelp;
  136. }
  137. //We do not want to overflow channel_list, so we need the check here
  138. //The rest of the sanity checking can come later after we know
  139. //whether this is a
  140. //LabJack or a NerdJack
  141. if (channel_count >= MAX_CHANNELS) {
  142. info("error: too many channels specified\n");
  143. goto printhelp;
  144. }
  145. channel_list[channel_count++] = tmp;
  146. optarg = endp + 1;
  147. }
  148. while (*endp);
  149. break;
  150. case 't': /* labjack only */
  151. timer_mode_count = 0;
  152. do {
  153. tmp = strtol(optarg, &endp, 0);
  154. if (*endp != '\0' && *endp != ',') {
  155. info("bad timer mode: %s\n", optarg);
  156. goto printhelp;
  157. }
  158. if (timer_mode_count >= UE9_TIMERS) {
  159. info("error: too many timers specified\n");
  160. goto printhelp;
  161. }
  162. timer_mode_list[timer_mode_count++] = tmp;
  163. optarg = endp + 1;
  164. }
  165. while (*endp);
  166. break;
  167. case 'T': /* labjack only */
  168. timer_divisor = strtod(optarg, &endp);
  169. if (*endp || timer_divisor < 0 || timer_divisor > 255) {
  170. info("bad timer divisor: %s\n", optarg);
  171. goto printhelp;
  172. }
  173. break;
  174. case 'r':
  175. desired_rate = strtod(optarg, &endp);
  176. if (*endp || desired_rate <= 0) {
  177. info("bad rate: %s\n", optarg);
  178. goto printhelp;
  179. }
  180. break;
  181. case 'l':
  182. lines = strtol(optarg, &endp, 0);
  183. if (*endp || lines <= 0) {
  184. info("bad number of lines: %s\n", optarg);
  185. goto printhelp;
  186. }
  187. break;
  188. case 'R':
  189. tmp = strtol(optarg, &endp, 0);
  190. if (*endp != ',') {
  191. info("bad range number: %s\n", optarg);
  192. goto printhelp;
  193. }
  194. if (tmp != 5 && tmp != 10) {
  195. info("valid choices for range are 5 or 10\n");
  196. goto printhelp;
  197. }
  198. if (tmp == 5)
  199. precision = precision + 1;
  200. optarg = endp + 1;
  201. if (*endp == '\0') {
  202. info("Range needs two numbers, one for channels 0-5 and another for 6-11\n");
  203. goto printhelp;
  204. }
  205. tmp = strtol(optarg, &endp, 0);
  206. if (*endp != '\0') {
  207. info("Range needs only two numbers, one for channels 0-5 and another for 6-11\n");
  208. goto printhelp;
  209. }
  210. if (tmp != 5 && tmp != 10) {
  211. info("valid choices for range are 5 or 10\n");
  212. goto printhelp;
  213. }
  214. if (tmp == 5)
  215. precision = precision + 2;
  216. break;
  217. case 'N':
  218. nerdjack++;
  219. break;
  220. case 'L':
  221. labjack++;
  222. break;
  223. case 'd':
  224. detect++;
  225. break;
  226. case 'o':
  227. oneshot++;
  228. break;
  229. case 'f':
  230. forceretry++;
  231. break;
  232. case 'c':
  233. if (convert != 0) {
  234. info("specify only one conversion type\n");
  235. goto printhelp;
  236. }
  237. convert = CONVERT_VOLTS;
  238. break;
  239. case 'H':
  240. if (convert != 0) {
  241. info("specify only one conversion type\n");
  242. goto printhelp;
  243. }
  244. convert = CONVERT_HEX;
  245. break;
  246. case 'm':
  247. showmem++;
  248. case 'v':
  249. verb_count++;
  250. break;
  251. case 'X':
  252. printf("%s", examplestring);
  253. return 0;
  254. break;
  255. case 'V':
  256. printf("etherstream " VERSION "\n");
  257. printf("Written by Jim Paris <jim@jtan.com>\n");
  258. printf("and Zachary Clifford <zacharyc@mit.edu>.\n");
  259. printf("This program comes with no warranty and is "
  260. "provided under the GPLv2.\n");
  261. return 0;
  262. break;
  263. case 'i':
  264. inform++;
  265. break;
  266. case 'h':
  267. help = stdout;
  268. default:
  269. printhelp:
  270. fprintf(help, "Usage: %s [options]\n", *argv);
  271. opt_help(opt, help);
  272. fprintf(help, "Read data from the specified Labjack UE9"
  273. " via Ethernet. See README for details.\n");
  274. return (help == stdout) ? 0 : 1;
  275. }
  276. }
  277. if (detect && labjack) {
  278. info("The LabJack does not support autodetection\n");
  279. goto printhelp;
  280. }
  281. if (detect && !nerdjack) {
  282. info("Only the NerdJack supports autodetection - assuming -N option\n");
  283. nerdjack = 1;
  284. }
  285. if (detect && addressSpecified) {
  286. info("Autodetection and specifying address are mutually exclusive\n");
  287. goto printhelp;
  288. }
  289. if (nerdjack && labjack) {
  290. info("Nerdjack and Labjack options are mutually exclusive\n");
  291. goto printhelp;
  292. }
  293. donerdjack = nerdjack;
  294. //First if no options were supplied try the Nerdjack
  295. //The second time through, donerdjack will be true and this will not fire
  296. if (!nerdjack && !labjack) {
  297. info("No device specified...Defaulting to Nerdjack\n");
  298. donerdjack = 1;
  299. }
  300. doneparse:
  301. if (inform) {
  302. //We just want information from NerdJack
  303. if (!detect) {
  304. if (nerd_get_version(address) < 0) {
  305. info("Could not find NerdJack at specified address\n");
  306. } else {
  307. return 0;
  308. }
  309. }
  310. info("Autodetecting NerdJack address\n");
  311. free(address);
  312. if (nerdjack_detect(address) < 0) {
  313. info("Error with autodetection\n");
  314. goto printhelp;
  315. } else {
  316. info("Found NerdJack at address: %s\n", address);
  317. if (nerd_get_version(address) < 0) {
  318. info("Error getting NerdJack version\n");
  319. goto printhelp;
  320. }
  321. return 0;
  322. }
  323. }
  324. if (donerdjack) {
  325. if (channel_count > NERDJACK_CHANNELS) {
  326. info("Too many channels for NerdJack\n");
  327. goto printhelp;
  328. }
  329. for (i = 0; i < channel_count; i++) {
  330. if (channel_list[i] >= NERDJACK_CHANNELS) {
  331. info("Channel is out of NerdJack range: %d\n",
  332. channel_list[i]);
  333. goto printhelp;
  334. }
  335. }
  336. } else {
  337. if (channel_count > UE9_MAX_CHANNEL_COUNT) {
  338. info("Too many channels for LabJack\n");
  339. goto printhelp;
  340. }
  341. for (i = 0; i < channel_count; i++) {
  342. if (channel_list[i] > UE9_MAX_CHANNEL) {
  343. info("Channel is out of LabJack range: %d\n",
  344. channel_list[i]);
  345. goto printhelp;
  346. }
  347. }
  348. }
  349. /* Timer requires Labjack */
  350. if (timer_mode_count && !labjack) {
  351. info("Can't use timers on NerdJack\n");
  352. goto printhelp;
  353. }
  354. if (optind < argc) {
  355. info("error: too many arguments (%s)\n\n", argv[optind]);
  356. goto printhelp;
  357. }
  358. if (forceretry && oneshot) {
  359. info("forceretry and oneshot options are mutually exclusive\n");
  360. goto printhelp;
  361. }
  362. /* Two channels if none specified */
  363. if (channel_count == 0) {
  364. channel_list[channel_count++] = 0;
  365. channel_list[channel_count++] = 1;
  366. }
  367. if (verb_count) {
  368. info("Scanning channels:");
  369. for (i = 0; i < channel_count; i++)
  370. info_no_timestamp(" AIN%d", channel_list[i]);
  371. info_no_timestamp("\n");
  372. }
  373. /* Figure out actual rate. */
  374. if (donerdjack) {
  375. if (nerdjack_choose_scan(desired_rate, &actual_rate, &period) <
  376. 0) {
  377. info("error: can't achieve requested scan rate (%lf Hz)\n", desired_rate);
  378. }
  379. } else {
  380. if (ue9_choose_scan(desired_rate, &actual_rate,
  381. &scanconfig, &scaninterval) < 0) {
  382. info("error: can't achieve requested scan rate (%lf Hz)\n", desired_rate);
  383. }
  384. }
  385. if ((desired_rate != actual_rate) || verb_count) {
  386. info("Actual scanrate is %lf Hz\n", actual_rate);
  387. info("Period is %ld\n", period);
  388. }
  389. if (verb_count && lines) {
  390. info("Stopping capture after %d lines\n", lines);
  391. }
  392. signal(SIGINT, handle_sig);
  393. signal(SIGTERM, handle_sig);
  394. /* Ignore SIGPIPE so I/O errors to the network device won't kill the process */
  395. signal(SIGPIPE, SIG_IGN);
  396. if (detect) {
  397. info("Autodetecting NerdJack address\n");
  398. free(address);
  399. if (nerdjack_detect(address) < 0) {
  400. info("Error with autodetection\n");
  401. goto printhelp;
  402. } else {
  403. info("Found NerdJack at address: %s\n", address);
  404. }
  405. }
  406. for (;;) {
  407. int ret;
  408. if (donerdjack) {
  409. ret =
  410. nerdDoStream(address, channel_list, channel_count,
  411. precision, period, convert, lines,
  412. showmem);
  413. verb("nerdDoStream returned %d\n", ret);
  414. } else {
  415. ret = doStream(address, scanconfig, scaninterval,
  416. channel_list, channel_count,
  417. timer_mode_list, timer_mode_count, timer_divisor,
  418. convert, lines);
  419. verb("doStream returned %d\n", ret);
  420. }
  421. if (oneshot)
  422. break;
  423. if (ret == 0)
  424. break;
  425. //Neither options specified at command line and first time through.
  426. //Try LabJack
  427. if (ret == -ENOTCONN && donerdjack && !labjack && !nerdjack) {
  428. info("Could not connect NerdJack...Trying LabJack\n");
  429. donerdjack = 0;
  430. goto doneparse;
  431. }
  432. //Neither option supplied, no address, and second time through.
  433. //Try autodetection
  434. if (ret == -ENOTCONN && !donerdjack && !labjack && !nerdjack
  435. && !addressSpecified) {
  436. info("Could not connect LabJack...Trying to autodetect Nerdjack\n");
  437. detect = 1;
  438. donerdjack = 1;
  439. goto doneparse;
  440. }
  441. if (ret == -ENOTCONN && nerdjack && !detect
  442. && !addressSpecified) {
  443. info("Could not reach NerdJack...Trying to autodetect\n");
  444. detect = 1;
  445. goto doneparse;
  446. }
  447. if (ret == -ENOTCONN && !forceretry) {
  448. info("Initial connection failed, giving up\n");
  449. break;
  450. }
  451. if (ret == -EAGAIN || ret == -ENOTCONN) {
  452. /* Some transient error. Wait a tiny bit, then retry */
  453. info("Retrying in 5 secs.\n");
  454. sleep(5);
  455. } else {
  456. info("Retrying now.\n");
  457. }
  458. }
  459. debug("Done loop\n");
  460. return 0;
  461. }
  462. int
  463. nerdDoStream(const char *address, int *channel_list, int channel_count,
  464. int precision, unsigned long period, int convert, int lines,
  465. int showmem)
  466. {
  467. int retval = -EAGAIN;
  468. int fd_data;
  469. static int first_call = 1;
  470. static int started = 0;
  471. static int wasreset = 0;
  472. getPacket command;
  473. static unsigned short currentcount = 0;
  474. tryagain:
  475. //If this is the first time, set up acquisition
  476. //Otherwise try to resume the previous one
  477. if (started == 0) {
  478. if (nerd_generate_command
  479. (&command, channel_list, channel_count, precision,
  480. period) < 0) {
  481. info("Failed to create configuration command\n");
  482. goto out;
  483. }
  484. if (nerd_send_command(address, "STOP", 4) < 0) {
  485. if (first_call) {
  486. retval = -ENOTCONN;
  487. if (verb_count)
  488. info("Failed to send STOP command\n");
  489. } else {
  490. info("Failed to send STOP command\n");
  491. }
  492. goto out;
  493. }
  494. if (nerd_send_command(address, &command, sizeof(command)) < 0) {
  495. info("Failed to send GET command\n");
  496. goto out;
  497. }
  498. } else {
  499. //If we had a transmission in progress, send a command to resume from there
  500. char cmdbuf[10];
  501. sprintf(cmdbuf, "SETC%05hd", currentcount);
  502. retval = nerd_send_command(address, cmdbuf, strlen(cmdbuf));
  503. if (retval == -4) {
  504. info("NerdJack was reset\n");
  505. //Assume we have not started yet, reset on this side.
  506. //If this routine is retried, start over
  507. printf("# NerdJack was reset here\n");
  508. currentcount = 0;
  509. started = 0;
  510. wasreset = 1;
  511. goto tryagain;
  512. } else if (retval < 0) {
  513. info("Failed to send SETC command\n");
  514. goto out;
  515. }
  516. }
  517. //The transmission has begun
  518. started = 1;
  519. /* Open connection */
  520. fd_data = nerd_open(address, NERDJACK_DATA_PORT);
  521. if (fd_data < 0) {
  522. info("Connect failed: %s:%d\n", address, NERDJACK_DATA_PORT);
  523. goto out;
  524. }
  525. retval = nerd_data_stream
  526. (fd_data, channel_count, channel_list, precision, convert, lines,
  527. showmem, &currentcount, period, wasreset);
  528. wasreset = 0;
  529. if (retval == -3) {
  530. retval = 0;
  531. }
  532. if (retval < 0) {
  533. info("Failed to open data stream\n");
  534. goto out1;
  535. }
  536. info("Stream finished\n");
  537. retval = 0;
  538. out1:
  539. nerd_close_conn(fd_data);
  540. out:
  541. //We've tried communicating, so this is not the first call anymore
  542. first_call = 0;
  543. return retval;
  544. }
  545. int
  546. doStream(const char *address, uint8_t scanconfig, uint16_t scaninterval,
  547. int *channel_list, int channel_count,
  548. int *timer_mode_list, int timer_mode_count, int timer_divisor,
  549. int convert, int lines)
  550. {
  551. int retval = -EAGAIN;
  552. int fd_cmd, fd_data;
  553. int ret;
  554. static int first_call = 1;
  555. struct callbackInfo ci = {
  556. .convert = convert,
  557. .maxlines = lines,
  558. };
  559. /* Open command connection. If this fails, and this is the
  560. first attempt, return a different error code so we give up. */
  561. fd_cmd = ue9_open(address, UE9_COMMAND_PORT);
  562. if (fd_cmd < 0) {
  563. info("Connect failed: %s:%d\n", address, UE9_COMMAND_PORT);
  564. if (first_call)
  565. retval = -ENOTCONN;
  566. goto out;
  567. }
  568. first_call = 0;
  569. /* Make sure nothing is left over from a previous stream */
  570. if (ue9_stream_stop(fd_cmd) == 0)
  571. verb("Stopped previous stream.\n");
  572. ue9_buffer_flush(fd_cmd);
  573. /* Open data connection */
  574. fd_data = ue9_open(address, UE9_DATA_PORT);
  575. if (fd_data < 0) {
  576. info("Connect failed: %s:%d\n", address, UE9_DATA_PORT);
  577. goto out1;
  578. }
  579. /* Get calibration */
  580. if (ue9_get_calibration(fd_cmd, &ci.calib) < 0) {
  581. info("Failed to get device calibration\n");
  582. goto out2;
  583. }
  584. /* Set timer configuration */
  585. if (timer_mode_count &&
  586. ue9_timer_config(fd_cmd, timer_mode_list, timer_mode_count,
  587. timer_divisor) < 0) {
  588. info("Failed to set timer configuration\n");
  589. goto out2;
  590. }
  591. /* Set stream configuration */
  592. if (ue9_streamconfig_simple(fd_cmd, channel_list, channel_count,
  593. scanconfig, scaninterval,
  594. UE9_BIPOLAR_GAIN1) < 0) {
  595. info("Failed to set stream configuration\n");
  596. goto out2;
  597. }
  598. /* Start stream */
  599. if (ue9_stream_start(fd_cmd) < 0) {
  600. info("Failed to start stream\n");
  601. goto out2;
  602. }
  603. /* Stream data */
  604. ret =
  605. ue9_stream_data(fd_data, channel_count, data_callback, (void *)&ci);
  606. if (ret < 0) {
  607. info("Data stream failed with error %d\n", ret);
  608. goto out3;
  609. }
  610. info("Stream finished\n");
  611. retval = 0;
  612. out3:
  613. /* Stop stream and clean up */
  614. ue9_stream_stop(fd_cmd);
  615. ue9_buffer_flush(fd_cmd);
  616. out2:
  617. ue9_close(fd_data);
  618. out1:
  619. ue9_close(fd_cmd);
  620. out:
  621. return retval;
  622. }
  623. int data_callback(int channels, uint16_t * data, void *context)
  624. {
  625. int i;
  626. struct callbackInfo *ci = (struct callbackInfo *)context;
  627. static int lines = 0;
  628. columns_left = channels;
  629. for (i = 0; i < channels; i++) {
  630. if (ci->convert == CONVERT_VOLTS &&
  631. i <= UE9_MAX_ANALOG_CHANNEL) {
  632. /* CONVERT_VOLTS */
  633. if (printf("%lf", ue9_binary_to_analog(
  634. &ci->calib, UE9_BIPOLAR_GAIN1,
  635. 12, data[i])) < 0)
  636. goto bad;
  637. } else if (ci->convert == CONVERT_HEX) {
  638. /* CONVERT_HEX */
  639. if (printf("%04X", data[i]) < 0)
  640. goto bad;
  641. } else {
  642. /* CONVERT_DEC */
  643. if (printf("%d", data[i]) < 0)
  644. goto bad;
  645. }
  646. columns_left--;
  647. if (i < (channels - 1)) {
  648. if (ci->convert != CONVERT_HEX && putchar(' ') < 0)
  649. goto bad;
  650. } else {
  651. if (putchar('\n') < 0)
  652. goto bad;
  653. lines++;
  654. if (ci->maxlines && lines >= ci->maxlines)
  655. return -1;
  656. }
  657. }
  658. return 0;
  659. bad:
  660. info("Output error (disk full?)\n");
  661. return -3;
  662. }