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.
 
 
 

632 lines
14 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 UE9 (192.168.1.209)"},
  42. {'n', "numchannels", "n", "sample the first N ADC channels (2)"},
  43. {'N', "nerdjack", NULL, "Use NerdJack device instead"},
  44. {'d', "detect", NULL, "Detect NerdJack IP address"},
  45. {'p', "precision", "0-3",
  46. "Set precision on NerdJack (0 - max range, 1 - max precision)"},
  47. {'C', "channels", "a,b,c", "sample channels a, b, and c"},
  48. {'r', "rate", "hz", "sample each channel at this rate (8000.0)"},
  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. {0, NULL, NULL, NULL}
  59. };
  60. int doStream (const char *address, uint8_t scanconfig, uint16_t scaninterval,
  61. int *channel_list, int channel_count, int convert,
  62. int maxlines);
  63. int nerdDoStream (const char *address, int *channel_list, int channel_count,
  64. int precision, unsigned long period, int convert, int lines,
  65. int showmem);
  66. int data_callback (int channels, uint16_t * data, void *context);
  67. int columns_left = 0;
  68. void
  69. handle_sig (int sig)
  70. {
  71. while (columns_left--)
  72. {
  73. printf (" 0");
  74. }
  75. fflush (stdout);
  76. exit (0);
  77. }
  78. int
  79. 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. uint8_t scanconfig;
  95. uint16_t scaninterval;
  96. #if UE9_CHANNELS > NERDJACK_CHANNELS
  97. int channel_list[UE9_CHANNELS];
  98. #else
  99. int channel_list[NERDJACK_CHANNELS];
  100. #endif
  101. int channel_count = 0;
  102. int nerdjack = 0;
  103. int detect = 0;
  104. int precision = 0;
  105. unsigned long period = NERDJACK_CLOCK_RATE / desired_rate;
  106. /* Parse arguments */
  107. opt_init (&optind);
  108. while ((c = opt_parse (argc, argv, &optind, &optarg, opt)) != 0)
  109. {
  110. switch (c)
  111. {
  112. case 'a':
  113. free (address);
  114. address = strdup (optarg);
  115. break;
  116. case 'n':
  117. channel_count = 0;
  118. tmp = strtol (optarg, &endp, 0);
  119. if (*endp || tmp < 1 || tmp > UE9_CHANNELS)
  120. {
  121. info ("bad number of channels: %s\n", optarg);
  122. goto printhelp;
  123. }
  124. for (i = 0; i < tmp; i++)
  125. channel_list[channel_count++] = i;
  126. break;
  127. case 'C':
  128. channel_count = 0;
  129. do
  130. {
  131. tmp = strtol (optarg, &endp, 0);
  132. if (*endp != '\0' && *endp != ',')
  133. {
  134. //|| tmp < 0 || tmp >= UE9_CHANNELS) {
  135. info ("bad channel number: %s\n", optarg);
  136. goto printhelp;
  137. }
  138. //We do not want to overflow channel_list, so we need the check here
  139. //The rest of the sanity checking can come later after we know whether this is a
  140. //LabJack or a NerdJack
  141. #if UE9_CHANNELS > NERDJACK_CHANNELS
  142. if (channel_count >= UE9_CHANNELS)
  143. {
  144. #else
  145. if (channel_count >= NERDJACK_CHANNELS)
  146. {
  147. #endif
  148. info ("error: too many channels specified\n");
  149. goto printhelp;
  150. }
  151. channel_list[channel_count++] = tmp;
  152. optarg = endp + 1;
  153. }
  154. while (*endp);
  155. break;
  156. case 'r':
  157. desired_rate = strtod (optarg, &endp);
  158. if (*endp || desired_rate <= 0)
  159. {
  160. info ("bad rate: %s\n", optarg);
  161. goto printhelp;
  162. }
  163. break;
  164. case 'l':
  165. lines = strtol (optarg, &endp, 0);
  166. if (*endp || lines <= 0)
  167. {
  168. info ("bad number of lines: %s\n", optarg);
  169. goto printhelp;
  170. }
  171. break;
  172. case 'p':
  173. tmp = strtol (optarg, &endp, 0);
  174. if (tmp <= 3 && tmp >= 0)
  175. {
  176. precision = tmp;
  177. }
  178. else
  179. {
  180. info ("Bad argument to p: %s\n", optarg);
  181. goto printhelp;
  182. }
  183. break;
  184. case 'N':
  185. nerdjack++;
  186. break;
  187. case 'd':
  188. detect++;
  189. break;
  190. case 'o':
  191. oneshot++;
  192. break;
  193. case 'f':
  194. forceretry++;
  195. break;
  196. case 'c':
  197. if (convert != 0)
  198. {
  199. info ("specify only one conversion type\n");
  200. goto printhelp;
  201. }
  202. convert = CONVERT_VOLTS;
  203. break;
  204. case 'H':
  205. if (convert != 0)
  206. {
  207. info ("specify only one conversion type\n");
  208. goto printhelp;
  209. }
  210. convert = CONVERT_HEX;
  211. break;
  212. case 'm':
  213. showmem++;
  214. case 'v':
  215. verb_count++;
  216. break;
  217. case 'V':
  218. printf ("ljstream " VERSION "\n");
  219. printf ("Written by Jim Paris <jim@jtan.com>\n");
  220. printf ("This program comes with no warranty and is "
  221. "provided under the GPLv2.\n");
  222. return 0;
  223. break;
  224. case 'h':
  225. help = stdout;
  226. default:
  227. printhelp:
  228. fprintf (help, "Usage: %s [options]\n", *argv);
  229. opt_help (opt, help);
  230. fprintf (help, "Read data from the specified Labjack UE9"
  231. " via Ethernet. See README for details.\n");
  232. return (help == stdout) ? 0 : 1;
  233. }
  234. }
  235. doneparse:
  236. if (nerdjack)
  237. {
  238. if (channel_count > NERDJACK_CHANNELS)
  239. {
  240. info ("Too many channels for NerdJack\n");
  241. goto printhelp;
  242. }
  243. for (i = 0; i < channel_count; i++)
  244. {
  245. if (channel_list[i] >= NERDJACK_CHANNELS)
  246. {
  247. info ("Channel is out of NerdJack range: %d\n",
  248. channel_list[i]);
  249. goto printhelp;
  250. }
  251. }
  252. }
  253. else
  254. {
  255. if (channel_count > UE9_CHANNELS)
  256. {
  257. info ("Too many channels for LabJack\n");
  258. goto printhelp;
  259. }
  260. for (i = 0; i < channel_count; i++)
  261. {
  262. if (channel_list[i] >= UE9_CHANNELS)
  263. {
  264. info ("Channel is out of LabJack range: %d\n", channel_list[i]);
  265. goto printhelp;
  266. }
  267. }
  268. }
  269. if (optind < argc)
  270. {
  271. info ("error: too many arguments (%s)\n\n", argv[optind]);
  272. goto printhelp;
  273. }
  274. if (forceretry && oneshot)
  275. {
  276. info ("forceretry and oneshot options are mutually exclusive\n");
  277. goto printhelp;
  278. }
  279. /* Two channels if none specified */
  280. if (channel_count == 0)
  281. {
  282. channel_list[channel_count++] = 0;
  283. channel_list[channel_count++] = 1;
  284. }
  285. if (verb_count)
  286. {
  287. info ("Scanning channels:");
  288. for (i = 0; i < channel_count; i++)
  289. info (" AIN%d", channel_list[i]);
  290. info ("\n");
  291. }
  292. /* Figure out actual rate. */
  293. if (nerdjack)
  294. {
  295. if (nerdjack_choose_scan (desired_rate, &actual_rate, &period) < 0)
  296. {
  297. info ("error: can't achieve requested scan rate (%lf Hz)\n",
  298. desired_rate);
  299. //return 1;
  300. }
  301. }
  302. else
  303. {
  304. if (ue9_choose_scan (desired_rate, &actual_rate,
  305. &scanconfig, &scaninterval) < 0)
  306. {
  307. info ("error: can't achieve requested scan rate (%lf Hz)\n",
  308. desired_rate);
  309. //return 1;
  310. }
  311. }
  312. if ((desired_rate != actual_rate) || verb_count)
  313. {
  314. info ("Actual scanrate is %lf Hz\n", actual_rate);
  315. info ("Period is %ld\n", period);
  316. }
  317. if (verb_count && lines)
  318. {
  319. info ("Stopping capture after %d lines\n", lines);
  320. }
  321. signal (SIGINT, handle_sig);
  322. signal (SIGTERM, handle_sig);
  323. if (detect)
  324. {
  325. info ("Autodetecting NerdJack address\n");
  326. free (address);
  327. if (nerdjack_detect (address) < 0)
  328. {
  329. info ("Error with autodetection\n");
  330. }
  331. else
  332. {
  333. info ("Found NerdJack at address: %s\n", address);
  334. }
  335. }
  336. for (;;)
  337. {
  338. int ret;
  339. if (nerdjack)
  340. {
  341. ret =
  342. nerdDoStream (address, channel_list, channel_count, precision,
  343. period, convert, lines, showmem);
  344. verb ("nerdDoStream returned %d\n", ret);
  345. }
  346. else
  347. {
  348. ret = doStream (address, scanconfig, scaninterval,
  349. channel_list, channel_count, convert, lines);
  350. verb ("doStream returned %d\n", ret);
  351. }
  352. if (oneshot)
  353. break;
  354. if (ret == 0)
  355. break;
  356. if (ret == -ENOTCONN && !nerdjack)
  357. {
  358. info ("Could not connect LabJack...Trying NerdJack\n");
  359. nerdjack = 1;
  360. goto doneparse;
  361. }
  362. if (ret == -ENOTCONN && !forceretry)
  363. {
  364. info ("Initial connection failed, giving up\n");
  365. break;
  366. }
  367. if (ret == -EAGAIN || ret == -ENOTCONN)
  368. {
  369. /* Some transient error. Wait a tiny bit, then retry */
  370. info ("Retrying in 5 secs.\n");
  371. sleep (5);
  372. }
  373. else
  374. {
  375. info ("Retrying now.\n");
  376. }
  377. }
  378. debug ("Done loop\n");
  379. return 0;
  380. }
  381. int
  382. nerdDoStream (const char *address, int *channel_list, int channel_count,
  383. int precision, unsigned long period, int convert, int lines,
  384. int showmem)
  385. {
  386. int retval = -EAGAIN;
  387. int fd_data;
  388. static int first_call = 1;
  389. static int started = 0;
  390. getPacket command;
  391. static unsigned short currentcount = 0;
  392. //usleep(1000000);
  393. if (first_call)
  394. {
  395. if (nerd_generate_command
  396. (&command, channel_list, channel_count, precision, period) < 0)
  397. {
  398. info ("Failed to create configuration command\n");
  399. goto out;
  400. }
  401. if (nerd_send_command (address, "STOP", 4) < 0)
  402. {
  403. if (first_call)
  404. retval = -ENOTCONN;
  405. info ("Failed to send STOP command\n");
  406. goto out;
  407. }
  408. if (nerd_send_command (address, &command, sizeof (command)) < 0)
  409. {
  410. info ("Failed to send GET command\n");
  411. goto out;
  412. }
  413. }
  414. //We have sent the configuration commands. If we retry later, don't resend them. We would like
  415. //to resume the interrupted transmission
  416. first_call = 0;
  417. //If we had a transmission in progress, send a command to resume from there
  418. if (started == 1)
  419. {
  420. char cmdbuf[10];
  421. sprintf (cmdbuf, "SETC%05hd", currentcount);
  422. if (nerd_send_command (address, cmdbuf, strlen (cmdbuf)) < 0)
  423. {
  424. info ("Failed to send SETC command\n");
  425. goto out;
  426. }
  427. }
  428. started = 1;
  429. /* Open connection */
  430. fd_data = nerd_open (address, NERDJACK_DATA_PORT);
  431. if (fd_data < 0)
  432. {
  433. info ("Connect failed: %s:%d\n", address, NERDJACK_DATA_PORT);
  434. goto out;
  435. }
  436. if (nerd_data_stream
  437. (fd_data, channel_count, channel_list, precision, convert, lines,
  438. showmem, &currentcount, period) < 0)
  439. {
  440. info ("Failed to open data stream\n");
  441. goto out1;
  442. }
  443. info ("Stream finished\n");
  444. retval = 0;
  445. out1:
  446. nerd_close_conn (fd_data);
  447. out:
  448. return retval;
  449. }
  450. int
  451. doStream (const char *address, uint8_t scanconfig, uint16_t scaninterval,
  452. int *channel_list, int channel_count, int convert, int lines)
  453. {
  454. int retval = -EAGAIN;
  455. int fd_cmd, fd_data;
  456. int ret;
  457. static int first_call = 1;
  458. struct callbackInfo ci = {
  459. .convert = convert,
  460. .maxlines = lines,
  461. };
  462. /* Open command connection. If this fails, and this is the
  463. first attempt, return a different error code so we give up. */
  464. fd_cmd = ue9_open (address, UE9_COMMAND_PORT);
  465. if (fd_cmd < 0)
  466. {
  467. info ("Connect failed: %s:%d\n", address, UE9_COMMAND_PORT);
  468. if (first_call)
  469. retval = -ENOTCONN;
  470. goto out;
  471. }
  472. first_call = 0;
  473. /* Make sure nothing is left over from a previous stream */
  474. if (ue9_stream_stop (fd_cmd) == 0)
  475. verb ("Stopped previous stream.\n");
  476. ue9_buffer_flush (fd_cmd);
  477. /* Open data connection */
  478. fd_data = ue9_open (address, UE9_DATA_PORT);
  479. if (fd_data < 0)
  480. {
  481. info ("Connect failed: %s:%d\n", address, UE9_DATA_PORT);
  482. goto out1;
  483. }
  484. /* Get calibration */
  485. if (ue9_get_calibration (fd_cmd, &ci.calib) < 0)
  486. {
  487. info ("Failed to get device calibration\n");
  488. goto out2;
  489. }
  490. /* Set stream configuration */
  491. if (ue9_streamconfig_simple (fd_cmd, channel_list, channel_count,
  492. scanconfig, scaninterval,
  493. UE9_BIPOLAR_GAIN1) < 0)
  494. {
  495. info ("Failed to set stream configuration\n");
  496. goto out2;
  497. }
  498. /* Start stream */
  499. if (ue9_stream_start (fd_cmd) < 0)
  500. {
  501. info ("Failed to start stream\n");
  502. goto out2;
  503. }
  504. /* Stream data */
  505. ret = ue9_stream_data (fd_data, channel_count, data_callback, (void *) &ci);
  506. if (ret < 0)
  507. {
  508. info ("Data stream failed with error %d\n", ret);
  509. goto out3;
  510. }
  511. info ("Stream finished\n");
  512. retval = 0;
  513. out3:
  514. /* Stop stream and clean up */
  515. ue9_stream_stop (fd_cmd);
  516. ue9_buffer_flush (fd_cmd);
  517. out2:
  518. ue9_close (fd_data);
  519. out1:
  520. ue9_close (fd_cmd);
  521. out:
  522. return retval;
  523. }
  524. int
  525. data_callback (int channels, uint16_t * data, void *context)
  526. {
  527. int i;
  528. struct callbackInfo *ci = (struct callbackInfo *) context;
  529. static int lines = 0;
  530. columns_left = channels;
  531. for (i = 0; i < channels; i++)
  532. {
  533. switch (ci->convert)
  534. {
  535. case CONVERT_VOLTS:
  536. if (printf
  537. ("%lf",
  538. ue9_binary_to_analog (&ci->calib, UE9_BIPOLAR_GAIN1, 12,
  539. data[i])) < 0)
  540. goto bad;
  541. break;
  542. case CONVERT_HEX:
  543. if (printf ("%04X", data[i]) < 0)
  544. goto bad;
  545. break;
  546. default:
  547. case CONVERT_DEC:
  548. if (printf ("%d", data[i]) < 0)
  549. goto bad;
  550. break;
  551. }
  552. columns_left--;
  553. if (i < (channels - 1))
  554. {
  555. if (ci->convert != CONVERT_HEX && putchar (' ') < 0)
  556. goto bad;
  557. }
  558. else
  559. {
  560. if (putchar ('\n') < 0)
  561. goto bad;
  562. lines++;
  563. if (ci->maxlines && lines >= ci->maxlines)
  564. return -1;
  565. }
  566. }
  567. return 0;
  568. bad:
  569. info ("Output error (disk full?)\n");
  570. return -1;
  571. }