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.
 
 
 

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