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(" AIN%d", channel_list[i]);
  371. info("\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. if (detect) {
  395. info("Autodetecting NerdJack address\n");
  396. free(address);
  397. if (nerdjack_detect(address) < 0) {
  398. info("Error with autodetection\n");
  399. goto printhelp;
  400. } else {
  401. info("Found NerdJack at address: %s\n", address);
  402. }
  403. }
  404. for (;;) {
  405. int ret;
  406. if (donerdjack) {
  407. ret =
  408. nerdDoStream(address, channel_list, channel_count,
  409. precision, period, convert, lines,
  410. showmem);
  411. verb("nerdDoStream returned %d\n", ret);
  412. } else {
  413. ret = doStream(address, scanconfig, scaninterval,
  414. channel_list, channel_count,
  415. timer_mode_list, timer_mode_count, timer_divisor,
  416. convert, lines);
  417. verb("doStream returned %d\n", ret);
  418. }
  419. if (oneshot)
  420. break;
  421. if (ret == 0)
  422. break;
  423. //Neither options specified at command line and first time through.
  424. //Try LabJack
  425. if (ret == -ENOTCONN && donerdjack && !labjack && !nerdjack) {
  426. info("Could not connect NerdJack...Trying LabJack\n");
  427. donerdjack = 0;
  428. goto doneparse;
  429. }
  430. //Neither option supplied, no address, and second time through.
  431. //Try autodetection
  432. if (ret == -ENOTCONN && !donerdjack && !labjack && !nerdjack
  433. && !addressSpecified) {
  434. info("Could not connect LabJack...Trying to autodetect Nerdjack\n");
  435. detect = 1;
  436. donerdjack = 1;
  437. goto doneparse;
  438. }
  439. if (ret == -ENOTCONN && nerdjack && !detect
  440. && !addressSpecified) {
  441. info("Could not reach NerdJack...Trying to autodetect\n");
  442. detect = 1;
  443. goto doneparse;
  444. }
  445. if (ret == -ENOTCONN && !forceretry) {
  446. info("Initial connection failed, giving up\n");
  447. break;
  448. }
  449. if (ret == -EAGAIN || ret == -ENOTCONN) {
  450. /* Some transient error. Wait a tiny bit, then retry */
  451. info("Retrying in 5 secs.\n");
  452. sleep(5);
  453. } else {
  454. info("Retrying now.\n");
  455. }
  456. }
  457. debug("Done loop\n");
  458. return 0;
  459. }
  460. int
  461. nerdDoStream(const char *address, int *channel_list, int channel_count,
  462. int precision, unsigned long period, int convert, int lines,
  463. int showmem)
  464. {
  465. int retval = -EAGAIN;
  466. int fd_data;
  467. static int first_call = 1;
  468. static int started = 0;
  469. static int wasreset = 0;
  470. getPacket command;
  471. static unsigned short currentcount = 0;
  472. tryagain:
  473. //If this is the first time, set up acquisition
  474. //Otherwise try to resume the previous one
  475. if (started == 0) {
  476. if (nerd_generate_command
  477. (&command, channel_list, channel_count, precision,
  478. period) < 0) {
  479. info("Failed to create configuration command\n");
  480. goto out;
  481. }
  482. if (nerd_send_command(address, "STOP", 4) < 0) {
  483. if (first_call) {
  484. retval = -ENOTCONN;
  485. if (verb_count)
  486. info("Failed to send STOP command\n");
  487. } else {
  488. info("Failed to send STOP command\n");
  489. }
  490. goto out;
  491. }
  492. if (nerd_send_command(address, &command, sizeof(command)) < 0) {
  493. info("Failed to send GET command\n");
  494. goto out;
  495. }
  496. } else {
  497. //If we had a transmission in progress, send a command to resume from there
  498. char cmdbuf[10];
  499. sprintf(cmdbuf, "SETC%05hd", currentcount);
  500. retval = nerd_send_command(address, cmdbuf, strlen(cmdbuf));
  501. if (retval == -4) {
  502. info("NerdJack was reset\n");
  503. //Assume we have not started yet, reset on this side.
  504. //If this routine is retried, start over
  505. printf("# NerdJack was reset here\n");
  506. currentcount = 0;
  507. started = 0;
  508. wasreset = 1;
  509. goto tryagain;
  510. } else if (retval < 0) {
  511. info("Failed to send SETC command\n");
  512. goto out;
  513. }
  514. }
  515. //The transmission has begun
  516. started = 1;
  517. /* Open connection */
  518. fd_data = nerd_open(address, NERDJACK_DATA_PORT);
  519. if (fd_data < 0) {
  520. info("Connect failed: %s:%d\n", address, NERDJACK_DATA_PORT);
  521. goto out;
  522. }
  523. retval = nerd_data_stream
  524. (fd_data, channel_count, channel_list, precision, convert, lines,
  525. showmem, &currentcount, period, wasreset);
  526. wasreset = 0;
  527. if (retval == -3) {
  528. retval = 0;
  529. }
  530. if (retval < 0) {
  531. info("Failed to open data stream\n");
  532. goto out1;
  533. }
  534. info("Stream finished\n");
  535. retval = 0;
  536. out1:
  537. nerd_close_conn(fd_data);
  538. out:
  539. //We've tried communicating, so this is not the first call anymore
  540. first_call = 0;
  541. return retval;
  542. }
  543. int
  544. doStream(const char *address, uint8_t scanconfig, uint16_t scaninterval,
  545. int *channel_list, int channel_count,
  546. int *timer_mode_list, int timer_mode_count, int timer_divisor,
  547. int convert, int lines)
  548. {
  549. int retval = -EAGAIN;
  550. int fd_cmd, fd_data;
  551. int ret;
  552. static int first_call = 1;
  553. struct callbackInfo ci = {
  554. .convert = convert,
  555. .maxlines = lines,
  556. };
  557. /* Open command connection. If this fails, and this is the
  558. first attempt, return a different error code so we give up. */
  559. fd_cmd = ue9_open(address, UE9_COMMAND_PORT);
  560. if (fd_cmd < 0) {
  561. info("Connect failed: %s:%d\n", address, UE9_COMMAND_PORT);
  562. if (first_call)
  563. retval = -ENOTCONN;
  564. goto out;
  565. }
  566. first_call = 0;
  567. /* Make sure nothing is left over from a previous stream */
  568. if (ue9_stream_stop(fd_cmd) == 0)
  569. verb("Stopped previous stream.\n");
  570. ue9_buffer_flush(fd_cmd);
  571. /* Open data connection */
  572. fd_data = ue9_open(address, UE9_DATA_PORT);
  573. if (fd_data < 0) {
  574. info("Connect failed: %s:%d\n", address, UE9_DATA_PORT);
  575. goto out1;
  576. }
  577. /* Get calibration */
  578. if (ue9_get_calibration(fd_cmd, &ci.calib) < 0) {
  579. info("Failed to get device calibration\n");
  580. goto out2;
  581. }
  582. /* Set timer configuration */
  583. if (timer_mode_count &&
  584. ue9_timer_config(fd_cmd, timer_mode_list, timer_mode_count,
  585. timer_divisor) < 0) {
  586. info("Failed to set timer configuration\n");
  587. goto out2;
  588. }
  589. /* Set stream configuration */
  590. if (ue9_streamconfig_simple(fd_cmd, channel_list, channel_count,
  591. scanconfig, scaninterval,
  592. UE9_BIPOLAR_GAIN1) < 0) {
  593. info("Failed to set stream configuration\n");
  594. goto out2;
  595. }
  596. /* Start stream */
  597. if (ue9_stream_start(fd_cmd) < 0) {
  598. info("Failed to start stream\n");
  599. goto out2;
  600. }
  601. /* Stream data */
  602. ret =
  603. ue9_stream_data(fd_data, channel_count, data_callback, (void *)&ci);
  604. if (ret < 0) {
  605. info("Data stream failed with error %d\n", ret);
  606. goto out3;
  607. }
  608. info("Stream finished\n");
  609. retval = 0;
  610. out3:
  611. /* Stop stream and clean up */
  612. ue9_stream_stop(fd_cmd);
  613. ue9_buffer_flush(fd_cmd);
  614. out2:
  615. ue9_close(fd_data);
  616. out1:
  617. ue9_close(fd_cmd);
  618. out:
  619. return retval;
  620. }
  621. int data_callback(int channels, uint16_t * data, void *context)
  622. {
  623. int i;
  624. struct callbackInfo *ci = (struct callbackInfo *)context;
  625. static int lines = 0;
  626. columns_left = channels;
  627. for (i = 0; i < channels; i++) {
  628. if (ci->convert == CONVERT_VOLTS &&
  629. i <= UE9_MAX_ANALOG_CHANNEL) {
  630. /* CONVERT_VOLTS */
  631. if (printf("%lf", ue9_binary_to_analog(
  632. &ci->calib, UE9_BIPOLAR_GAIN1,
  633. 12, data[i])) < 0)
  634. goto bad;
  635. break;
  636. } else if (ci->convert == CONVERT_HEX) {
  637. /* CONVERT_HEX */
  638. if (printf("%04X", data[i]) < 0)
  639. goto bad;
  640. break;
  641. } else {
  642. /* CONVERT_DEC */
  643. if (printf("%d", data[i]) < 0)
  644. goto bad;
  645. break;
  646. }
  647. columns_left--;
  648. if (i < (channels - 1)) {
  649. if (ci->convert != CONVERT_HEX && putchar(' ') < 0)
  650. goto bad;
  651. } else {
  652. if (putchar('\n') < 0)
  653. goto bad;
  654. lines++;
  655. if (ci->maxlines && lines >= ci->maxlines)
  656. return -1;
  657. }
  658. }
  659. return 0;
  660. bad:
  661. info("Output error (disk full?)\n");
  662. return -3;
  663. }