commit a9200acc15d81d51c1a098a6067073f2790272d5 Author: zacharyc Date: Wed Jan 21 15:21:57 2009 +0000 Combine ljstream with nerjack tools to make ethstream git-svn-id: https://bucket.mit.edu/svn/nilm/acquisition/ethstream@7089 ddd99763-3ecb-0310-9145-efcb8ce7c51f diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b01dae9 --- /dev/null +++ b/COPYING @@ -0,0 +1,12 @@ +This program is free software; you can redistribute it and/or modify +it under the terms of version 2 of the GNU General Public License as +published by the Free Software Foundation, 59 Temple Place, Suite 330, +Boston, MA 02111-1307 USA + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +For the complete license, please see http://www.fsf.org/licenses/gpl.txt +or request a copy from the author of this program. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..add9945 --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +# +# Labjack/Nerdjack Tools +# Copyright (c) 2007-2009 +# Jim Paris , Zach Clifford +# +# This is free software; you can redistribute it and/or modify it and +# it is provided under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation; see COPYING. +# + +# For Solaris, use: gmake CC=gcc LDFLAGS="-lsocket -lnsl" +# For Windows, build with "make win" + +# Build options + +CFLAGS += -Wall -g #-pg +LDFLAGS += -lm #-pg +PREFIX = /usr/local +MANPATH = ${PREFIX}/man/man1 +BINPATH = ${PREFIX}/bin + +WINCC = i386-mingw32-gcc +WINCFLAGS += $(CFLAGS) +WINLDFLAGS += $(LDFLAGS) -lws2_32 -s + +# Targets + +.PHONY: default +default: lin + +.PHONY: all +all: lin win + +.PHONY: lin +lin: ljtest ethstream ljconfig \ + ethstream.1 ljconfig.1 + +.PHONY: win +win: ljtest.exe ethstream.exe ljconfig.exe + + +version.h: VERSION + echo "/* This file was automatically generated. */" >version.h + echo "#define VERSION \"`cat VERSION` (`date +%Y-%m-%d`)\"" >>version.h + +# Object files for each executable + +obj-common = opt.o ue9.o ue9error.o netutil.o debug.o nerdjack.o +obj-ljconfig = ljconfig.o $(obj-common) +obj-ethstream = ethstream.o $(obj-common) +obj-ljtest = ljtest.o $(obj-common) + +ljconfig: $(obj-ljconfig) +ethstream: $(obj-ethstream) +ljtest: $(obj-ljtest) + +ljconfig.exe: $(obj-ljconfig:.o=.obj) compat-win32.obj +ethstream.exe: $(obj-ethstream:.o=.obj) compat-win32.obj +ljtest.exe: $(obj-ljtest:.o=.obj) compat-win32.obj + +# Manpages + +%.1: % + if ! help2man -N --output=$@ ./$< ; then \ + echo "No manual page available." > $@ ; fi + +%.txt: %.1 + nroff -man $< | colcrt | sed s/$$/\\r/ > $@ + +# Install/uninstall targets for Linux + +.PHONY: install +install: ethstream.1 ethstream + install -m 0755 ethstream ${BINPATH} + install -m 0644 ethstream.1 ${MANPATH} + +.PHONY: uninstall +uninstall: + rm -f ${BINPATH}/ethstream ${MANPATH}/ethstream.1 + +# Packaging + +PACKAGE=labjack-`cat VERSION` +.PHONY: dist +dist: version.h + mkdir -p ${PACKAGE} + cp [A-Z]* *.[ch] ${PACKAGE} + tar cvzf ${PACKAGE}.tar.gz ${PACKAGE} + rm -r ${PACKAGE} + +# Maintenance + +.PHONY: clean distclean +clean distclean: + rm -f *.o *.obj *.exe ethstream ljtest ljconfig core *.d *.1 *.txt + +# Dependency tracking: + +allsources = $(wildcard *.c) +-include $(allsources:.c=.d) +%.o : %.c + $(COMPILE.c) -MP -MMD -MT '$*.obj' -o $@ $< +%.obj : %.c + $(WINCC) $(WINCFLAGS) -MP -MMD -MT '$*.o' -c -o $@ $< + +# Win32 executable + +%.exe : %.obj + $(WINCC) -o $@ $^ $(WINLDFLAGS) diff --git a/README b/README new file mode 100644 index 0000000..c7403b8 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +Labjack/Nerdjack Tools +by Jim Paris +with modifications by Zach Clifford + +These tools are for interacting with the LabJack UE9 or the NerdJack over the Ethernet interface. More information about the UE9 device: + http://www.labjack.com/labjack_ue9.php diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0 diff --git a/compat-win32.c b/compat-win32.c new file mode 100644 index 0000000..ae173be --- /dev/null +++ b/compat-win32.c @@ -0,0 +1,83 @@ +#include +#include +#include "compat.h" +#include + +unsigned int sleep(unsigned int seconds) +{ + Sleep(seconds * 1000); + return 0; +} + +static struct { + int num; + char *msg; +} win32_error[] = { + /* Errors that we might vaguely expect to see */ + { WSAEINTR, "Winsock: Interrupted system call" }, + { WSAEBADF, "Winsock: Bad file number" }, + { WSAEFAULT, "Winsock: Bad address" }, + { WSAEINVAL, "Winsock: Invalid argument" }, + { WSAEMFILE, "Winsock: Too many open files" }, + { WSAEWOULDBLOCK, "Winsock: Operation would block" }, + { WSAEINPROGRESS, "Winsock: Operation now in progress" }, + { WSAEALREADY, "Winsock: Operation already in progress" }, + { WSAENOTSOCK, "Winsock: Socket operation on nonsocket" }, + { WSAEADDRINUSE, "Winsock: Address already in use" }, + { WSAEADDRNOTAVAIL, "Winsock: Cannot assign requested address" }, + { WSAENETDOWN, "Winsock: Network is down" }, + { WSAENETUNREACH, "Winsock: Network is unreachable" }, + { WSAENETRESET, "Winsock: Network dropped connection on reset" }, + { WSAECONNABORTED, "Winsock: Software caused connection abort" }, + { WSAECONNRESET, "Winsock: Connection reset by peer" }, + { WSAETIMEDOUT, "Winsock: Connection timed out" }, + { WSAECONNREFUSED, "Winsock: Connection refused" }, + { WSAEHOSTDOWN, "Winsock: Host is down" }, + { WSAEHOSTUNREACH, "Winsock: No route to host" }, + { WSAVERNOTSUPPORTED, "Winsock: Unsupported Winsock version" }, + { ETIMEDOUT, "Connection timed out" }, + { ENOTCONN, "Not connected" }, + { -1, NULL }, +}; +char *compat_strerror(int errnum) +{ + int i; + static char buf[128]; + + for (i = 0; win32_error[i].num != -1; i++) + if (errnum == win32_error[i].num) + return win32_error[i].msg; + if (errnum >= 10000) { + sprintf(buf, "Winsock: unknown error %d\n", errnum); + return buf; + } + return strerror(errnum); +} + +#ifdef __WIN32__ + +/*const char *compat_inet_ntop(int af, const void *src, char *dst, socklen_t cnt) +{ + if (af == AF_INET) + { + struct sockaddr_in in; + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + memcpy(&in.sin_addr, src, sizeof(struct in_addr)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + else if (af == AF_INET6) + { + struct sockaddr_in6 in; + memset(&in, 0, sizeof(in)); + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + return NULL; +} +*/ +#endif + diff --git a/compat.h b/compat.h new file mode 100644 index 0000000..eee8d53 --- /dev/null +++ b/compat.h @@ -0,0 +1,15 @@ +#ifndef COMPAT_H +#define COMPAT_H + +#ifdef __WIN32__ +unsigned int sleep(unsigned int seconds); +char *compat_strerror(int errnum); +//const char *inet_ntop(int af, void *src, const char *dst, socklen_t cnt); +#define INET_ADDRSTRLEN 16 +#define ETIMEDOUT 110 +#define ENOTCONN 107 +#else +#define compat_strerror strerror +#endif + +#endif diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..a2d5b71 --- /dev/null +++ b/debug.c @@ -0,0 +1,18 @@ +#include "debug.h" +#include +#include + +int verb_count = 0; + +int func_fprintf(const char *func, FILE *stream, const char *format, ...) +{ + va_list ap; + int ret; + + fprintf(stream, "%s: ", func); + va_start(ap, format); + ret = vfprintf(stream, format, ap); + va_end(ap); + return ret; +} + diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..7e6c58b --- /dev/null +++ b/debug.h @@ -0,0 +1,35 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#ifndef DEBUG_H +#define DEBUG_H + +extern int verb_count; + +#include + +int func_fprintf(const char *func, FILE *stream, const char *format, + ...) __attribute__ ((format (printf, 3, 4))); + +#define debug(x...) ({ \ + if(verb_count >= 2) \ + func_fprintf(__func__, stderr,x); \ +}) + +#define verb(x...) ({ \ + if(verb_count >= 1) \ + func_fprintf(__func__, stderr,x); \ +}) + +#define info(x...) ({ \ + if(verb_count >= 0) \ + fprintf(stderr,x); \ +}) + +#endif diff --git a/ethstream.c b/ethstream.c new file mode 100644 index 0000000..8555805 --- /dev/null +++ b/ethstream.c @@ -0,0 +1,465 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +/* ljstream: Stream data from the first N (1-14) analog inputs. + Resolution is set to 12-bit and all channels are in bipolar (-5 to + +5V) mode. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "ue9.h" +#include "ue9error.h" +#include "nerdjack.h" +#include "opt.h" +#include "version.h" +#include "compat.h" + +#define DEFAULT_HOST "192.168.1.209" +#define UE9_COMMAND_PORT 52360 +#define UE9_DATA_PORT 52361 + +struct callbackInfo { + struct ue9Calibration calib; + int convert; + int maxlines; +}; + +struct options opt[] = { + { 'a', "address", "string", "host/address of UE9 (192.168.1.209)" }, + { 'n', "numchannels", "n", "sample the first N ADC channels (2)" }, + { 'N', "nerdjack", NULL, "Use NerdJack device instead" }, + { 'd', "detect", NULL, "Detect NerdJack IP address" }, + { 'p', "precision", "0-3", "Set precision on NerdJack (0 - max range, 1 - max precision)"}, + { 'C', "channels", "a,b,c", "sample channels a, b, and c" }, + { 'r', "rate", "hz", "sample each channel at this rate (8000.0)" }, + { 'o', "oneshot", NULL, "don't retry in case of errors" }, + { 'f', "forceretry", NULL, "retry no matter what happens" }, + { 'c', "convert", NULL, "display output in volts" }, + { 'l', "lines", "num", "if set, output this many lines and quit" }, + { 'h', "help", NULL, "this help" }, + { 'v', "verbose", NULL, "be verbose" }, + { 'V', "version", NULL, "show version number and exit" }, + { 0, NULL, NULL, NULL } +}; + +int doStream(const char *address, uint8_t scanconfig, uint16_t scaninterval, + int *channel_list, int channel_count, int convert, int maxlines); +int nerdDoStream(const char *address, int *channel_list, int channel_count, int precision, + unsigned short period, int convert, int lines); +int data_callback(int channels, uint16_t *data, void *context); + +int columns_left = 0; +void handle_sig(int sig) +{ + while (columns_left--) { + printf(" 0"); + } + fflush(stdout); + exit(0); +} + +int main(int argc, char *argv[]) +{ + int optind; + char *optarg, *endp; + char c; + int tmp, i; + FILE *help = stderr; + char *address = strdup(DEFAULT_HOST); + double desired_rate = 8000.0; + int lines = 0; + double actual_rate; + int oneshot = 0; + int forceretry = 0; + int convert = 0; + uint8_t scanconfig; + uint16_t scaninterval; +#if UE9_CHANNELS > NERDJACK_CHANNELS + int channel_list[UE9_CHANNELS]; +#else + int channel_list[NERDJACK_CHANNELS]; +#endif + int channel_count = 0; + int nerdjack = 0; + int detect = 0; + int precision = 0; + int period = NERDJACK_CLOCK_RATE / desired_rate; + + /* Parse arguments */ + opt_init(&optind); + while ((c = opt_parse(argc, argv, &optind, &optarg, opt)) != 0) { + switch (c) { + case 'a': + free(address); + address = strdup(optarg); + break; + case 'n': + channel_count = 0; + tmp = strtol(optarg, &endp, 0); + if (*endp || tmp < 1 || tmp > UE9_CHANNELS) { + info("bad number of channels: %s\n", optarg); + goto printhelp; + } + for (i = 0; i < tmp; i++) + channel_list[channel_count++] = i; + break; + case 'C': + channel_count = 0; + do { + tmp = strtol(optarg, &endp, 0); + if (*endp != '\0' && *endp != ',') { + //|| tmp < 0 || tmp >= UE9_CHANNELS) { + info("bad channel number: %s\n", optarg); + goto printhelp; + } + //We do not want to overflow channel_list, so we need the check here + //The rest of the sanity checking can come later after we know whether this is a + //LabJack or a NerdJack +#if UE9_CHANNELS > NERDJACK_CHANNELS + if (channel_count >= UE9_CHANNELS) { +#else + if (channel_count >= NERDJACK_CHANNELS) { +#endif + info("error: too many channels specified\n"); + goto printhelp; + } + channel_list[channel_count++] = tmp; + optarg = endp + 1; + } while (*endp); + break; + case 'r': + desired_rate = strtod(optarg, &endp); + if(*endp || desired_rate <= 0) { + info("bad rate: %s\n", optarg); + goto printhelp; + } + break; + case 'l': + lines = strtol(optarg, &endp, 0); + if (*endp || lines <= 0) { + info("bad number of lines: %s\n", optarg); + goto printhelp; + } + break; + case 'p': + precision++; + break; + case 'N': + nerdjack++; + break; + case 'd': + detect++; + break; + case 'o': + oneshot++; + break; + case 'f': + forceretry++; + break; + case 'c': + convert++; + break; + case 'v': + verb_count++; + break; + case 'V': + printf("ljstream " VERSION "\n"); + printf("Written by Jim Paris \n"); + printf("This program comes with no warranty and is " + "provided under the GPLv2.\n"); + return 0; + break; + case 'h': + help = stdout; + default: + printhelp: + fprintf(help, "Usage: %s [options]\n", *argv); + opt_help(opt, help); + fprintf(help, "Read data from the specified Labjack UE9" + " via Ethernet. See README for details.\n"); + return (help == stdout) ? 0 : 1; + } + } + + if (nerdjack) { + if (channel_count > NERDJACK_CHANNELS) { + info("Too many channels for NerdJack\n"); + goto printhelp; + } + for (i = 0; i < channel_count; i++) { + if (channel_list[i] >= NERDJACK_CHANNELS) { + info("Channel is out of NerdJack range: %d\n",channel_list[i]); + goto printhelp; + } + } + } else { + if (channel_count > UE9_CHANNELS) { + info("Too many channels for LabJack\n"); + goto printhelp; + } + for (i = 0; i < channel_count; i++) { + if (channel_list[i] >= UE9_CHANNELS) { + info("Channel is out of LabJack range: %d\n",channel_list[i]); + goto printhelp; + } + } + } + + + + if (optind < argc) { + info("error: too many arguments (%s)\n\n", argv[optind]); + goto printhelp; + } + + if (forceretry && oneshot) { + info("forceretry and oneshot options are mutually exclusive\n"); + goto printhelp; + } + + /* Two channels if none specified */ + if (channel_count == 0) { + channel_list[channel_count++] = 0; + channel_list[channel_count++] = 1; + } + + if (verb_count) { + info("Scanning channels:"); + for (i = 0; i < channel_count; i++) + info(" AIN%d", channel_list[i]); + info("\n"); + } + + /* Figure out actual rate. */ + if (nerdjack) { + if (nerdjack_choose_scan(desired_rate, &actual_rate, &period) < 0) { + info("error: can't achieve requested scan rate (%lf Hz)\n", + desired_rate); + return 1; + } + } else { + if (ue9_choose_scan(desired_rate, &actual_rate, + &scanconfig, &scaninterval) < 0) { + info("error: can't achieve requested scan rate (%lf Hz)\n", + desired_rate); + return 1; + } + } + + + if ((desired_rate != actual_rate) || verb_count) + info("Actual scanrate is %lf Hz\n", actual_rate); + + if (verb_count && lines) { + info("Stopping capture after %d lines\n", lines); + } + + signal(SIGINT, handle_sig); + signal(SIGTERM, handle_sig); + + if (detect) { + info("Autodetecting NerdJack address\n"); + free(address); + if(nerdjack_detect(address) < 0) { + info("Error with autodetection\n"); + } else { + info("Found NerdJack at address: %s\n",address); + } + } + + + for (;;) { + int ret; + if(nerdjack) { + ret = nerdDoStream(address, channel_list, channel_count, precision, period, convert, lines); + verb("nerdDoStream returned %d\n", ret); + + } else { + ret = doStream(address, scanconfig, scaninterval, + channel_list, channel_count, convert, + lines); + verb("doStream returned %d\n", ret); + } + if (oneshot) + break; + + if (ret == 0) + break; + + if (ret == -ENOTCONN && !forceretry) { + info("Initial connection failed, giving up\n"); + break; + } + + if (ret == -EAGAIN || ret == -ENOTCONN) { + /* Some transient error. Wait a tiny bit, then retry */ + info("Retrying in 5 secs.\n"); + sleep(5); + } else { + info("Retrying now.\n"); + } + } + + debug("Done loop\n"); + + return 0; +} + +int nerdDoStream(const char *address, int *channel_list, int channel_count, int precision, + unsigned short period, int convert, int lines) +{ + int retval = -EAGAIN; + int fd_data; + static int first_call = 1; + char command[13]; + + /* Open connection. If this fails, and this is the + first attempt, return a different error code so we give up. */ + fd_data = nerd_open(address, NERDJACK_DATA_PORT); + if (fd_data < 0) { + info("Connect failed: %s:%d\n", address, NERDJACK_DATA_PORT); + if (first_call) + retval = -ENOTCONN; + goto out; + } + first_call = 0; + + if (nerd_generate_command(command, channel_list, channel_count, precision, period) < 0) { + info("Failed to create configuration command\n"); + goto out1; + } + + if (nerd_data_stream(fd_data, command, channel_count, channel_list, precision, convert, lines) < 0) { + info("Failed to open data stream\n"); + goto out1; + } + + info("Stream finished\n"); + retval = 0; + + out1: + nerd_close_conn(fd_data); + out: + return retval; +} + +int doStream(const char *address, uint8_t scanconfig, uint16_t scaninterval, + int *channel_list, int channel_count, int convert, int lines) +{ + int retval = -EAGAIN; + int fd_cmd, fd_data; + int ret; + static int first_call = 1; + struct callbackInfo ci = { + .convert = convert, + .maxlines = lines, + }; + + /* Open command connection. If this fails, and this is the + first attempt, return a different error code so we give up. */ + fd_cmd = ue9_open(address, UE9_COMMAND_PORT); + if (fd_cmd < 0) { + info("Connect failed: %s:%d\n", address, UE9_COMMAND_PORT); + if (first_call) + retval = -ENOTCONN; + goto out; + } + first_call = 0; + + /* Make sure nothing is left over from a previous stream */ + if (ue9_stream_stop(fd_cmd) == 0) + verb("Stopped previous stream.\n"); + ue9_buffer_flush(fd_cmd); + + /* Open data connection */ + fd_data = ue9_open(address, UE9_DATA_PORT); + if (fd_data < 0) { + info("Connect failed: %s:%d\n", address, UE9_DATA_PORT); + goto out1; + } + + /* Get calibration */ + if (ue9_get_calibration(fd_cmd, &ci.calib) < 0) { + info("Failed to get device calibration\n"); + goto out2; + } + + /* Set stream configuration */ + if (ue9_streamconfig_simple(fd_cmd, channel_list, channel_count, + scanconfig, scaninterval, + UE9_BIPOLAR_GAIN1) < 0) { + info("Failed to set stream configuration\n"); + goto out2; + } + + /* Start stream */ + if (ue9_stream_start(fd_cmd) < 0) { + info("Failed to start stream\n"); + goto out2; + } + + /* Stream data */ + ret = ue9_stream_data(fd_data, channel_count, data_callback, (void *)&ci); + if (ret < 0) { + info("Data stream failed with error %d\n", ret); + goto out3; + } + + info("Stream finished\n"); + retval = 0; + + out3: + /* Stop stream and clean up */ + ue9_stream_stop(fd_cmd); + ue9_buffer_flush(fd_cmd); + out2: + ue9_close(fd_data); + out1: + ue9_close(fd_cmd); + out: + return retval; +} + +int data_callback(int channels, uint16_t *data, void *context) +{ + int i; + struct callbackInfo *ci = (struct callbackInfo *)context; + static int lines = 0; + + columns_left = channels; + for (i = 0; i < channels; i++) { + if (ci->convert) + printf("%lf", ue9_binary_to_analog( + &ci->calib, UE9_BIPOLAR_GAIN1, 12, + data[i])); + else + printf("%d", data[i]); + columns_left--; + if (i < (channels - 1)) { + putchar(' '); + } else { + putchar('\n'); + lines++; + if (ci->maxlines && lines >= ci->maxlines) + return -1; + } + } + + return 0; +} diff --git a/ljconfig.c b/ljconfig.c new file mode 100644 index 0000000..a5e9f1d --- /dev/null +++ b/ljconfig.c @@ -0,0 +1,98 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +/* ljconfig: display/change comm/control processor configuration */ + +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "ue9.h" +#include "ue9error.h" +#include "opt.h" +#include "version.h" + +#define DEFAULT_HOST "192.168.1.209" +#define UE9_COMMAND_PORT 52360 + +struct options opt[] = { + { 'a', "address", "string", "host/address of UE9 (192.168.1.209)" }, + { 'h', "help", NULL, "this help" }, + { 'v', "verbose", NULL, "be verbose" }, + { 'V', "version", NULL, "show version number and exit" }, + { 0, NULL, NULL, NULL } +}; + +int main(int argc, char *argv[]) +{ + int optind; + char *optarg; + char c; + FILE *help = stderr; + char *address = strdup(DEFAULT_HOST); + int fd; + int ret; + + /* Parse arguments */ + opt_init(&optind); + while ((c = opt_parse(argc, argv, &optind, &optarg, opt)) != 0) { + switch (c) { + case 'a': + free(address); + address = strdup(optarg); + break; + case 'v': + verb_count++; + break; + case 'V': + printf("ljconfig " VERSION "\n"); + printf("Written by Jim Paris \n"); + printf("This program comes with no warranty and is " + "provided under the GPLv2.\n"); + return 0; + break; + case 'h': + help = stdout; + default: + printhelp: + fprintf(help, "Usage: %s [options]\n", *argv); + opt_help(opt, help); + fprintf(help, "Displays/changes Labjack UE9 config.\n"); + return (help == stdout) ? 0 : 1; + } + } + + if(optind + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#include +#include +#include +#include +#include +#include "debug.h" +#include "ue9.h" +#include "compat.h" + +int main(int argc, char *argv[]) +{ + int fd_cmd; + struct ue9Calibration calib; + + verb_count = 2; + + fd_cmd = ue9_open("192.168.1.209", 52360); + if (fd_cmd < 0) { + fprintf(stderr, "ue9_open: %s\n", + compat_strerror(errno)); + return 1; + } + + if (ue9_get_calibration(fd_cmd, &calib) < 0) { + fprintf(stderr, "ue9_get_calibration: %s\n", + compat_strerror(errno)); + return 1; + } + + printf("double unipolarSlope[0] = %lf\n", calib.unipolarSlope[0]); + printf("double unipolarSlope[1] = %lf\n", calib.unipolarSlope[1]); + printf("double unipolarSlope[2] = %lf\n", calib.unipolarSlope[2]); + printf("double unipolarSlope[3] = %lf\n", calib.unipolarSlope[3]); + printf("double unipolarOffset[0] = %lf\n", calib.unipolarOffset[0]); + printf("double unipolarOffset[1] = %lf\n", calib.unipolarOffset[1]); + printf("double unipolarOffset[2] = %lf\n", calib.unipolarOffset[2]); + printf("double unipolarOffset[3] = %lf\n", calib.unipolarOffset[3]); + printf("double bipolarSlope = %lf\n", calib.bipolarSlope); + printf("double bipolarOffset = %lf\n", calib.bipolarOffset); + printf("double DACSlope[0] = %lf\n", calib.DACSlope[0]); + printf("double DACSlope[1] = %lf\n", calib.DACSlope[1]); + printf("double DACOffset[0] = %lf\n", calib.DACOffset[0]); + printf("double DACOffset[1] = %lf\n", calib.DACOffset[1]); + printf("double tempSlope = %lf\n", calib.tempSlope); + printf("double tempSlopeLow = %lf\n", calib.tempSlopeLow); + printf("double calTemp = %lf\n", calib.calTemp); + printf("double Vref = %lf\n", calib.Vref); + printf("double VrefDiv2 = %lf\n", calib.VrefDiv2); + printf("double VsSlope = %lf\n", calib.VsSlope); + printf("double hiResUnipolarSlope = %lf\n", calib.hiResUnipolarSlope); + printf("double hiResUnipolarOffset = %lf\n", calib.hiResUnipolarOffset); + printf("double hiResBipolarSlope = %lf\n", calib.hiResBipolarSlope); + printf("double hiResBipolarOffset = %lf\n", calib.hiResBipolarOffset); + + ue9_close(fd_cmd); + + return 0; +} diff --git a/nerdjack.c b/nerdjack.c new file mode 100644 index 0000000..420f02e --- /dev/null +++ b/nerdjack.c @@ -0,0 +1,331 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netutil.h" +#include "compat.h" + +#include "debug.h" +#include "nerdjack.h" +#include "util.h" +#include "netutil.h" + +#define NERDJACK_TIMEOUT 5 /* Timeout for connect/send/recv, in seconds */ + +/* Choose the best ScanConfig and ScanInterval parameters for the + desired scanrate. Returns -1 if no valid config found */ +int nerdjack_choose_scan(double desired_rate, double *actual_rate, int *period) +{ + + *period = round((double) NERDJACK_CLOCK_RATE / desired_rate); + * actual_rate = (double) NERDJACK_CLOCK_RATE / (double) *period; + if(*actual_rate != desired_rate) { + return -1; + } + return 0; +} + +int nerdjack_detect(char * ipAddress) { + int32_t sock, receivesock; + struct sockaddr_in sa, receiveaddr, sFromAddr; + int bytes_sent, buffer_length; + char buffer[200]; + char incomingData[10]; + unsigned int lFromLen; + + sprintf(buffer, "TEST"); + buffer_length = strlen(buffer) + 1; + + net_init(); + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + receivesock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + + /* Set nonblocking */ + if (soblock(sock, 0) < 0) { + verb("can't set nonblocking\n"); + return -1; + } + + /* Set nonblocking */ + if (soblock(receivesock, 0) < 0) { + verb("can't set nonblocking\n"); + return -1; + } + + + int opt = 1; + setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(void *) &opt,sizeof(int)); + + if((-1 == sock) || (-1 == receivesock)) /* if socket failed to initialize, exit */ + { + printf("Error Creating Socket\n"); + return -1; + } + + //Setup family for both sockets + sa.sin_family = PF_INET; + receiveaddr.sin_family = PF_INET; + + //Setup ports to send on DATA and receive on RECEIVE + receiveaddr.sin_port = htons(NERDJACK_UDP_RECEIVE_PORT); + sa.sin_port = htons(NERDJACK_DATA_PORT); + + //Receive from any IP address, Will send to broadcast + receiveaddr.sin_addr.s_addr = INADDR_ANY; + sa.sin_addr.s_addr = INADDR_BROADCAST; + + bind(receivesock,(struct sockaddr*) &receiveaddr, sizeof(struct sockaddr_in)); + + bytes_sent = sendto(sock, buffer, buffer_length, 0,(struct sockaddr*) &sa, sizeof(struct sockaddr_in) ); + if(bytes_sent < 0) { + printf("Error sending packet: %s\n", strerror(errno) ); + return -1; + } + + lFromLen = sizeof(sFromAddr); + + if(0 > recvfrom_timeout(receivesock, incomingData, sizeof(incomingData),0,(struct sockaddr *) &sFromAddr, &lFromLen, + & (struct timeval) { .tv_sec = NERDJACK_TIMEOUT })) { + + return -1; + } + + ipAddress = malloc(INET_ADDRSTRLEN); + + //It isn't ipv6 friendly, but inet_ntop isn't on Windows... + strcpy(ipAddress, inet_ntoa(sFromAddr.sin_addr)); + + close(sock); /* close the socket */ + close(receivesock); + return 0; +} + +int nerd_data_stream(int data_fd, char * command, int numChannels, int *channel_list, int precision, int convert, int lines) +{ + unsigned char buf[NERDJACK_PACKET_SIZE]; + + int numGroups = NERDJACK_NUM_SAMPLES / numChannels; + + int index = 0; + int totalread = 0; + int ret = 0; + int alignment = 0; + signed short datapoint = 0; + signed short dataline[NERDJACK_CHANNELS]; + long double voltline[NERDJACK_CHANNELS]; + int destination[NERDJACK_CHANNELS]; + unsigned short currentcount = 0; + unsigned long memused = 0; + unsigned short packetsready = 0; + unsigned short adcused = 0; + unsigned short tempshort = 0; + int charsread = 0; + int charsleft = 0; + int additionalread = 0; + int linesleft = lines; + + int numgroups = 0; + long double volts; + + int channels_left = numChannels; + int channelprocessing = 0; + int currentalign = 0; + int i; + + //Loop through channel_list until all channels recognized + //destination holds the index where each channel should go for reordering + do { + for(i = 0; i < numChannels; i++) { + if(channelprocessing == channel_list[i]) { + destination[currentalign] = i; + currentalign++; + channels_left--; + break; + } + } + channelprocessing++; + } while(channels_left > 0); + + /* Send request */ + ret = send_all_timeout(data_fd, command, strlen(command), 0, + & (struct timeval) { .tv_sec = NERDJACK_TIMEOUT }); + if (ret < 0 || ret != strlen(command)) { + verb("short send %d\n", (int)ret); + return -1; + } + + //Loop forever to grab data + while((charsread = recv_all_timeout(data_fd,buf,NERDJACK_PACKET_SIZE,0, + & (struct timeval) { .tv_sec = NERDJACK_TIMEOUT }))){ + + //We want a complete packet, so take the chars so far and keep waiting + if(charsread != NERDJACK_PACKET_SIZE) { + charsleft = NERDJACK_PACKET_SIZE - charsread; + while(charsleft != 0){ + additionalread = recv_all_timeout(data_fd,buf+charsread,charsleft,0, + & (struct timeval) { .tv_sec = NERDJACK_TIMEOUT }); + charsread = charsread + additionalread; + charsleft = NERDJACK_PACKET_SIZE - charsread; + } + } + + //First check the header info + if(buf[0] != 0xF0 || buf[1] != 0xAA) { + printf("No Header info\n"); + return -1; + } + + //Check counter info to make sure not out of order + tempshort = (buf[2] << 8) | buf[3]; + if(tempshort != currentcount ){ + printf("Count wrong. Expected %hd but got %hd\n", currentcount, tempshort); + return -1; + } + + //Increment number of packets received + currentcount++; + + //Process the rest of the header and update the index value to be pointing after it + index = 12; + memused = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | (buf[7]); + adcused = (buf[8] << 8) | (buf[9]); + packetsready = (buf[10] << 8) | (buf[11]); + alignment = 0; + numgroups = 0; + + //While there is still more data in the packet, process it + while(charsread > index) { + datapoint = (buf[index] << 8 | buf[index+1]); + if(convert) { + if(alignment <= 5) { + volts = (long double) ( datapoint / 32767.0 ) * ((precision & 0x01) ? 5.0 : 10.0); + } else { + volts = (long double) (datapoint / 32767.0 ) * ((precision & 0x02) ? 5.0 : 10.0); + } + voltline[destination[alignment]] = volts; + } else { + dataline[destination[alignment]] = datapoint; + } + + //Each point is two bytes, so increment index and total bytes read + index++; + index++; + totalread++; + alignment++; + + //Since channel data is packed, we need to know when to insert a newline + if(alignment == numChannels){ + if(convert) { + for(i = 0; i < numChannels; i++) { + printf("%Lf ",voltline[i]); + } + } else { + for(i = 0; i < numChannels; i++) { + printf("%hd ",dataline[i]); + } + } + printf("\n"); + alignment = 0; + numgroups++; + if(lines != 0) { + linesleft--; + if(linesleft == 0) { + return 0; + } + } + //If numgroups so far is equal to the numGroups in a packet, this packet is done + if(numgroups == numGroups) { + break; + } + } + } + index = 0; + } + + return 0; + +} + +int nerd_open(const char *address,int port) { + + struct hostent *he; + + net_init(); + + int32_t i32SocketFD = socket(PF_INET, SOCK_STREAM, 0); + + if(-1 == i32SocketFD) + { + printf("cannot create socket"); + return -1; + } + + /* Set nonblocking */ + if (soblock(i32SocketFD, 0) < 0) { + verb("can't set nonblocking\n"); + return -1; + } + + struct sockaddr_in stSockAddr; + memset(&stSockAddr, 0, sizeof(stSockAddr)); + + stSockAddr.sin_family = AF_INET; + stSockAddr.sin_port = htons(port); + + he = gethostbyname(address); + if (he == NULL) { + verb("gethostbyname(\"%s\") failed\n", address); + return -1; + } + stSockAddr.sin_addr = *((struct in_addr *) he->h_addr); + + debug("Resolved %s -> %s\n", address, inet_ntoa(stSockAddr.sin_addr)); + + /* Connect */ + if (connect_timeout(i32SocketFD, (struct sockaddr *) &stSockAddr, sizeof(stSockAddr), + & (struct timeval) { .tv_sec = NERDJACK_TIMEOUT }) < 0) { + verb("connection to %s:%d failed: %s\n", + inet_ntoa(stSockAddr.sin_addr), port, compat_strerror(errno)); + return -1; + } + + return i32SocketFD; +} + +int nerd_generate_command(char * command, int * channel_list, int channel_count, int precision, + unsigned short period) { + + int channelbit = 0; + int i; + + for( i = 0; i < channel_count; i++) { + channelbit = channelbit | (0x1 << channel_list[i]); + } + + sprintf(command,"GET%3.3X%d%5.5d", channelbit,precision,period); + + return 0; + +} + +int nerd_close_conn(int data_fd) +{ + shutdown(data_fd, 2); + close(data_fd); + return 0; +} diff --git a/nerdjack.h b/nerdjack.h new file mode 100644 index 0000000..d72943a --- /dev/null +++ b/nerdjack.h @@ -0,0 +1,44 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#ifndef NERDJACK_H +#define NERDJACK_H + +#include +#include + +#include "netutil.h" + +#define NERDJACK_CHANNELS 12 +#define NERDJACK_CLOCK_RATE 54000000 +#define NERDJACK_DATA_PORT 49155 +#define NERDJACK_UDP_RECEIVE_PORT 49156 + +#define NERDJACK_PACKET_SIZE 1460 +#define NERDJACK_NUM_SAMPLES 724 + +/* Open/close TCP/IP connection to the NerdJack */ +int nerd_open(const char *address,int port); +int nerd_close_conn(int data_fd); + +/* Generate the command word for the NerdJack */ +int nerd_generate_command(char * command, int * channel_list, int channel_count, int precision, + unsigned short period); + +/* Stream data out of the NerdJack */ +int nerd_data_stream(int data_fd, char * command, int numChannels, int * channel_list, int precision, int convert, int lines); + +/* Detect the IP Address of the NerdJack and return in ipAddress */ +int nerdjack_detect(char * ipAddress); + +/* Choose the best ScanConfig and ScanInterval parameters for the + desired scanrate. Returns -1 if no valid config found */ +int nerdjack_choose_scan(double desired_rate, double *actual_rate, int *period); + +#endif diff --git a/netutil.c b/netutil.c new file mode 100644 index 0000000..7b8490d --- /dev/null +++ b/netutil.c @@ -0,0 +1,243 @@ +#include "netutil.h" +#include "compat.h" +#include +#include +#include + +/* Initialize networking */ +void net_init(void) +{ +#ifdef __WIN32__ + WSADATA blah; + WSAStartup(0x0101, &blah); +#endif +} + +/* Set socket blocking/nonblocking */ +int soblock(int socket, int blocking) +{ +#ifdef __WIN32__ + unsigned long arg = blocking ? 0 : 1; + if (ioctlsocket(socket, FIONBIO, &arg) != 0) + return -1; + return 0; +#else + int sockopt; + + /* Get flags */ + sockopt = fcntl(socket, F_GETFL); + if (sockopt == -1) { + return -1; + } + + /* Modify */ + if (blocking) + sockopt &= ~O_NONBLOCK; + else + sockopt |= O_NONBLOCK; + + /* Set flags */ + if (fcntl(socket, F_SETFL, sockopt) != 0) + return -1; + + return 0; +#endif +} + + +/* Like connect(2), but with a timeout. Socket must be non-blocking. */ +int connect_timeout(int s, const struct sockaddr *serv_addr, socklen_t addrlen, + struct timeval *timeout) +{ + int ret; + fd_set writefds; + fd_set exceptfds; + int optval; + socklen_t optlen; + + /* Start connect */ + ret = connect(s, serv_addr, addrlen); + + if (ret == 0) { + /* Success */ + return 0; + } + + /* Check for immediate failure */ +#ifdef __WIN32__ + errno = WSAGetLastError(); + if (ret < 0 && errno != WSAEWOULDBLOCK && errno != WSAEINVAL) + return -1; +#else + if (ret < 0 && errno != EINPROGRESS && errno != EALREADY) + return -1; +#endif + + /* In progress, wait for result. */ + FD_ZERO(&writefds); + FD_SET(s, &writefds); + FD_ZERO(&exceptfds); + FD_SET(s, &exceptfds); + ret = select(s + 1, NULL, &writefds, &exceptfds, timeout); + if (ret < 0) { + /* Error */ + return -1; + } + if (ret == 0) { + /* Timed out */ + errno = ETIMEDOUT; + return -1; + } + + /* Check the socket state */ + optlen = sizeof(optval); + if (getsockopt(s, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen) != 0) + return -1; + + if (optval != 0) { + /* Connection failed. */ + errno = optval; + return -1; + } + + /* On Windows, SO_ERROR sometimes shows no error but the connection + still failed. Sigh. */ + if (FD_ISSET(s, &exceptfds) || !FD_ISSET(s, &writefds)) { + errno = EIO; + return -1; + } + + /* Success */ + return 0; +} + +/* Like send(2), but with a timeout. Socket must be non-blocking. + The timeout only applies if no data at all is sent -- this function + may still send less than requested. */ +ssize_t send_timeout(int s, const void *buf, size_t len, int flags, + struct timeval *timeout) +{ + fd_set writefds; + int ret; + + FD_ZERO(&writefds); + FD_SET(s, &writefds); + ret = select(s + 1, NULL, &writefds, NULL, timeout); + if (ret == 0) { + /* Timed out */ + errno = ETIMEDOUT; + return -1; + } + if (ret != 1) { + /* Error */ + return -1; + } + + return send(s, buf, len, flags); +} + +/* Like recv(2), but with a timeout. Socket must be non-blocking. + The timeout only applies if no data at all is received -- this + function may still return less than requested. */ +ssize_t recv_timeout(int s, void *buf, size_t len, int flags, + struct timeval *timeout) +{ + fd_set readfds; + int ret; + + FD_ZERO(&readfds); + FD_SET(s, &readfds); + ret = select(s + 1, &readfds, NULL, NULL, timeout); + if (ret == 0) { + /* Timed out */ + errno = ETIMEDOUT; + return -1; + } + if (ret != 1) { + /* Error */ + return -1; + } + + return recv(s, buf, len, flags); +} + +/* Like recvfrom(2), but with a timeout. Socket must be non-blocking. + The timeout only applies if no data at all is received -- this + function may still return less than requested. */ +ssize_t recvfrom_timeout(int s, void *buf, size_t len, int flags, struct sockaddr *address, socklen_t *address_len, + struct timeval *timeout) +{ + fd_set readfds; + int ret; + + FD_ZERO(&readfds); + FD_SET(s, &readfds); + ret = select(s + 1, &readfds, NULL, NULL, timeout); + if (ret == 0) { + /* Timed out */ + errno = ETIMEDOUT; + return -1; + } + if (ret != 1) { + /* Error */ + return -1; + } + + return recvfrom(s, buf, len, flags, address, address_len); +} + +/* Like send_timeout, but retries (with the same timeout) in case of + partial transfers. This is a stronger attempt to send all + requested data. */ +ssize_t send_all_timeout(int s, const void *buf, size_t len, int flags, + struct timeval *timeout) +{ + struct timeval tv; + size_t left = len; + ssize_t ret; + + while (left > 0) { + tv.tv_sec = timeout->tv_sec; + tv.tv_usec = timeout->tv_usec; + ret = send_timeout(s, buf, left, flags, &tv); + + if (ret < 0) + return ret; + + if (ret == 0) + break; + + left -= ret; + buf += ret; + } + + return len - left; +} + +/* Like recv_timeout, but retries (with the same timeout) in case of + partial transfers. This is a stronger attempt to recv all + requested data. */ +ssize_t recv_all_timeout(int s, void *buf, size_t len, int flags, + struct timeval *timeout) +{ + struct timeval tv; + size_t left = len; + ssize_t ret; + + while (left > 0) { + tv.tv_sec = timeout->tv_sec; + tv.tv_usec = timeout->tv_usec; + ret = recv_timeout(s, buf, left, flags, &tv); + + if (ret < 0) + return ret; + + if (ret == 0) + break; + + left -= ret; + buf += ret; + } + + return len - left; +} diff --git a/netutil.h b/netutil.h new file mode 100644 index 0000000..3681fbb --- /dev/null +++ b/netutil.h @@ -0,0 +1,45 @@ +#ifndef NETUTIL_H +#define NETUTIL_H + +#include +#include +#include + +#ifdef __WIN32__ +# include +# include +# define socklen_t int +# define in_addr_t uint32_t +# define in_port_t uint16_t +#else +# include +# include +# include +# include +#endif + +/* Initialize networking */ +void net_init(void); + +/* Set socket blocking/nonblocking */ +int soblock(int socket, int blocking); + +/* Like send(2), recv(2), connect(2), but with timeouts. + Socket must be O_NONBLOCK. */ +int connect_timeout(int s, const struct sockaddr *serv_addr, socklen_t addrlen, + struct timeval *timeout); +ssize_t send_timeout(int s, const void *buf, size_t len, int flags, + struct timeval *timeout); +ssize_t recv_timeout(int s, void *buf, size_t len, int flags, + struct timeval *timeout); +ssize_t recvfrom_timeout(int s, void *buf, size_t len, int flags, struct sockaddr *address, socklen_t *address_len, + struct timeval *timeout); + +/* Like send_timeout and recv_timeout, but they retry (with the same timeout) + in case of partial transfers, in order to try to transfer all data. */ +ssize_t send_all_timeout(int s, const void *buf, size_t len, int flags, + struct timeval *timeout); +ssize_t recv_all_timeout(int s, void *buf, size_t len, int flags, + struct timeval *timeout); + +#endif diff --git a/opt.c b/opt.c new file mode 100644 index 0000000..408537e --- /dev/null +++ b/opt.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#include +#include +#include +#include "opt.h" + +void opt_init(int *optind) { + *optind=0; +} + +char opt_parse(int argc, char **argv, int *optind, char **optarg, + struct options *opt) { + char c; + int i; + (*optind)++; + if(*optind>=argc) + return 0; + + if(argv[*optind][0]=='-' && + argv[*optind][1]!='-' && + argv[*optind][1]!=0) { + /* Short option (or a bunch of 'em) */ + /* Save this and shift others over */ + c=argv[*optind][1]; + for(i=2;argv[*optind][i]!=0;i++) + argv[*optind][i-1]=argv[*optind][i]; + argv[*optind][i-1]=0; + if(argv[*optind][1]!=0) + (*optind)--; + /* Now find it */ + for(i=0;opt[i].shortopt!=0;i++) + if(opt[i].shortopt==c) + break; + if(opt[i].shortopt==0) { + fprintf(stderr,"Error: unknown option '-%c'\n",c); + return '?'; + } + if(opt[i].arg==NULL) + return c; + (*optind)++; + if(*optind>=argc || (argv[*optind][0]=='-' && + argv[*optind][1]!=0)) { + fprintf(stderr,"Error: option '-%c' requires an " + "argument\n",c); + return '?'; + } + (*optarg)=argv[*optind]; + return c; + } else if(argv[*optind][0]=='-' && + argv[*optind][1]=='-' && + argv[*optind][2]!=0) { + /* Long option */ + for(i=0;(c=opt[i].shortopt)!=0;i++) + if(strcmp(opt[i].longopt,argv[*optind]+2)==0) + break; + if(opt[i].shortopt==0) { + fprintf(stderr,"Error: unknown option '%s'\n", + argv[*optind]); + return '?'; + } + if(opt[i].arg==NULL) + return c; + (*optind)++; + if(*optind>=argc || (argv[*optind][0]=='-' && + argv[*optind][1]!=0)) { + fprintf(stderr,"Error: option '%s' requires an " + "argument\n",argv[*optind-1]); + return '?'; + } + (*optarg)=argv[*optind]; + return c; + } else { + /* End of options */ + return 0; + } +} + +void opt_help(struct options *opt, FILE *out) { + int i; + int printed; + + for(i=0;opt[i].shortopt!=0;i++) { + fprintf(out," -%c, --%s%n",opt[i].shortopt, + opt[i].longopt,&printed); + fprintf(out," %-*s%s\n",30-printed, + opt[i].arg?opt[i].arg:"",opt[i].help); + } +} diff --git a/opt.h b/opt.h new file mode 100644 index 0000000..7dbabdf --- /dev/null +++ b/opt.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#ifndef OPT_H +#define OPT_H + +#include + +struct options { + char shortopt; + char *longopt; + char *arg; + char *help; +}; + +void opt_init(int *optind); + +char opt_parse(int argc, char **argv, int *optind, char **optarg, + struct options *opt); + +void opt_help(struct options *opt, FILE *out); + +#endif diff --git a/ue9.c b/ue9.c new file mode 100644 index 0000000..2d326c9 --- /dev/null +++ b/ue9.c @@ -0,0 +1,653 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netutil.h" +#include "compat.h" + +#include "debug.h" +#include "ue9.h" +#include "ue9error.h" +#include "util.h" +#include "netutil.h" + +#define UE9_TIMEOUT 5 /* Timeout for connect/send/recv, in seconds */ + +/* Fill checksums in data buffers, with "normal" checksum format */ +void ue9_checksum_normal(uint8_t *buffer, size_t len) +{ + uint16_t sum = 0; + + if (len < 1) { + fprintf(stderr, "ue9_checksum_normal: len too short\n"); + exit(1); + } + + while (--len >= 1) + sum += (uint16_t) buffer[len]; + sum = (sum / 256) + (sum % 256); + sum = (sum / 256) + (sum % 256); + buffer[0] = (uint8_t) sum; +} + +/* Fill checksums in data buffers, with "extended" checksum format */ +void ue9_checksum_extended(uint8_t *buffer, size_t len) +{ + uint16_t sum = 0; + + if (len < 6) { + fprintf(stderr, "ue9_checksum_extended: len too short\n"); + exit(1); + } + + /* 16-bit extended checksum */ + while (--len >= 6) + sum += (uint16_t) buffer[len]; + buffer[4] = (uint8_t) (sum & 0xff); + buffer[5] = (uint8_t) (sum >> 8); + + /* 8-bit normal checksum over first 6 bytes */ + ue9_checksum_normal(buffer, 6); +} + +/* Verify checksums in data buffers, with "normal" checksum format. */ +int ue9_verify_normal(uint8_t *buffer, size_t len) +{ + uint8_t saved, new; + + if (len < 1) { + fprintf(stderr, "ue9_verify_normal: len too short\n"); + exit(1); + } + + saved = buffer[0]; + ue9_checksum_normal(buffer, len); + new = buffer[0]; + buffer[0] = saved; + + if (new != saved) { + verb("got %02x, expected %02x\n", + saved, new); + return 0; + } + + return 1; +} + +/* Verify checksums in data buffers, with "extended" checksum format. */ +int ue9_verify_extended(uint8_t *buffer, size_t len) +{ + uint8_t saved[3], new[3]; + + if (len < 6) { + fprintf(stderr, "ue9_verify_extended: len too short\n"); + exit(1); + } + + saved[0] = buffer[0]; + saved[1] = buffer[4]; + saved[2] = buffer[5]; + ue9_checksum_extended(buffer, len); + new[0] = buffer[0]; + new[1] = buffer[4]; + new[2] = buffer[5]; + buffer[0] = saved[0]; + buffer[4] = saved[1]; + buffer[5] = saved[2]; + + if (saved[0] != new[0] || + saved[1] != new[1] || + saved[2] != new[2]) { + verb("got %02x %02x %02x, expected %02x %02x %02x\n", + saved[0], saved[1], saved[2], new[0], new[1], new[2]); + return 0; + } + + return 1; +} + +/* Data conversion. If calib is NULL, use uncalibrated conversions. */ +double ue9_binary_to_analog(struct ue9Calibration *calib, + uint8_t gain, uint8_t resolution, uint16_t data) +{ + double slope = 0, offset; + + if (calib == NULL) { + double uncal[9] = { 5.08, 2.54, 1.27, 0.63, 0, 0, 0, 0, 10.25 }; + if (gain >= ARRAY_SIZE(uncal) || uncal[gain] == 0) { + fprintf(stderr, "ue9_binary_to_analog: bad gain\n"); + exit(1); + } + return data * uncal[gain] / 65536.0; + } + + if (resolution < 18) { + if (gain <= 3) { + slope = calib->unipolarSlope[gain]; + offset = calib->unipolarOffset[gain]; + } else if (gain == 8) { + slope = calib->bipolarSlope; + offset = calib->bipolarOffset; + } + } else { + if (gain == 0) { + slope = calib->hiResUnipolarSlope; + offset = calib->hiResUnipolarOffset; + } else if (gain == 8) { + slope = calib->hiResBipolarSlope; + offset = calib->hiResBipolarOffset; + } + } + + if (slope == 0) { + fprintf(stderr, "ue9_binary_to_analog: bad gain\n"); + exit(1); + } + + return data * slope + offset; +} + +/* Execute a command on the UE9. Returns -1 on error. Fills the + checksums on the outgoing packets, and verifies them on the + incoming packets. Data in "out" is transmitted, data in "in" is + received. */ +int ue9_command(int fd, uint8_t *out, uint8_t *in, int inlen) +{ + int extended = 0, outlen; + uint8_t saved_1, saved_3; + ssize_t ret; + + if ((out[1] & 0x78) == 0x78) + extended = 1; + + /* Figure out length of data payload, and fill checksums. */ + if (extended) { + outlen = 6 + (out[2]) * 2; + ue9_checksum_extended(out, outlen); + } else { + outlen = 2 + (out[1] & 7) * 2; + ue9_checksum_normal(out, outlen); + } + + /* Send request */ + ret = send_all_timeout(fd, out, outlen, 0, + & (struct timeval) { .tv_sec = UE9_TIMEOUT }); + if (ret < 0 || ret != outlen) { + verb("short send %d\n", (int)ret); + return -1; + } + + /* Save a few bytes that we'll want to compare against later, + in case the caller passed the same buffer twice. */ + saved_1 = out[1]; + if (extended) + saved_3 = out[3]; + + /* Receive result */ + ret = recv_all_timeout(fd, in, inlen, 0, + & (struct timeval) { .tv_sec = UE9_TIMEOUT }); + if (ret < 0 || ret != inlen) { + verb("short recv %d\n", (int)ret); + return -1; + } + + /* Verify it */ + if ((in[1] & 0xF8) != (saved_1 & 0xF8)) + verb("returned command doesn't match\n"); + else if (extended && (in[3] != saved_3)) + verb("extended command doesn't match\n"); + else if (extended && (inlen != (6 + (in[2]) * 2))) + verb("returned extended data is the wrong len\n"); + else if (!extended && (inlen != (2 + (in[1] & 7) * 2))) + verb("returned data is the wrong len\n"); + else if (extended && !ue9_verify_extended(in, inlen)) + verb("extended checksum is invalid\n"); + else if (!ue9_verify_normal(in, extended ? 6 : inlen)) + verb("normal checksum is invalid\n"); + else + return 0; /* looks good */ + + return -1; +} + + +/* Read a memory block from the device. Returns -1 on error. */ +int ue9_memory_read(int fd, int blocknum, uint8_t *buffer, int len) +{ + uint8_t sendbuf[8], recvbuf[136]; + + if (len != 128) { + fprintf(stderr,"ue9_memory_read: buffer length must be 128\n"); + exit(1); + } + + /* Request memory block */ + sendbuf[1] = 0xf8; + sendbuf[2] = 0x01; + sendbuf[3] = 0x2a; + sendbuf[6] = 0x00; + sendbuf[7] = blocknum; + + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + return -1; + } + + /* Got it */ + memcpy(buffer, recvbuf + 8, len); + + return 0; +} + +/* Convert 64-bit fixed point to double type */ +double ue9_fp64_to_double(uint8_t *data) +{ + int32_t a; + uint32_t b; + + a = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]; + b = (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0]; + + return (double)a + (double)b / (double)4294967296.0L; +} + +/* Retrieve calibration data from the device. Returns -1 on error. */ +int ue9_get_calibration(int fd, struct ue9Calibration *calib) +{ + uint8_t buf[128]; + + /* Block 0 */ + if (ue9_memory_read(fd, 0, buf, 128) < 0) return -1; + calib->unipolarSlope[0] = ue9_fp64_to_double(buf + 0); + calib->unipolarOffset[0] = ue9_fp64_to_double(buf + 8); + calib->unipolarSlope[1] = ue9_fp64_to_double(buf + 16); + calib->unipolarOffset[1] = ue9_fp64_to_double(buf + 24); + calib->unipolarSlope[2] = ue9_fp64_to_double(buf + 32); + calib->unipolarOffset[2] = ue9_fp64_to_double(buf + 40); + calib->unipolarSlope[3] = ue9_fp64_to_double(buf + 48); + calib->unipolarOffset[3] = ue9_fp64_to_double(buf + 56); + + /* Block 1 */ + if (ue9_memory_read(fd, 1, buf, 128) < 0) return -1; + calib->bipolarSlope = ue9_fp64_to_double(buf + 0); + calib->bipolarOffset = ue9_fp64_to_double(buf + 8); + + /* Block 2 */ + if (ue9_memory_read(fd, 2, buf, 128) < 0) return -1; + calib->DACSlope[0] = ue9_fp64_to_double(buf + 0); + calib->DACOffset[0] = ue9_fp64_to_double(buf + 8); + calib->DACSlope[1] = ue9_fp64_to_double(buf + 16); + calib->DACOffset[1] = ue9_fp64_to_double(buf + 24); + calib->tempSlope = ue9_fp64_to_double(buf + 32); + calib->tempSlopeLow = ue9_fp64_to_double(buf + 48); + calib->calTemp = ue9_fp64_to_double(buf + 64); + calib->Vref = ue9_fp64_to_double(buf + 72); + calib->VrefDiv2 = ue9_fp64_to_double(buf + 88); + calib->VsSlope = ue9_fp64_to_double(buf + 96); + + /* Block 3 */ + if (ue9_memory_read(fd, 3, buf, 128) < 0) return -1; + calib->hiResUnipolarSlope = ue9_fp64_to_double(buf + 0); + calib->hiResUnipolarOffset = ue9_fp64_to_double(buf + 8); + + /* Block 4 */ + if (ue9_memory_read(fd, 4, buf, 128) < 0) return -1; + calib->hiResBipolarSlope = ue9_fp64_to_double(buf + 0); + calib->hiResBipolarOffset = ue9_fp64_to_double(buf + 8); + + /* All done */ + return 1; +} + +/* Retrieve comm config, returns -1 on error */ +int ue9_get_comm_config(int fd, struct ue9CommConfig *config) +{ + uint8_t sendbuf[18]; + uint8_t recvbuf[24]; + + memset(sendbuf, 0, sizeof(sendbuf)); + memset(config, 0, sizeof(struct ue9CommConfig)); + + sendbuf[1] = 0xf8; + sendbuf[2] = 0x09; + sendbuf[3] = 0x08; + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + return -1; + } + verb("todo\n"); + return -1; +} + +/* Retrieve control config, returns -1 on error */ +int ue9_get_control_config(int fd, struct ue9ControlConfig *config) +{ + uint8_t sendbuf[18]; + uint8_t recvbuf[24]; + + memset(sendbuf, 0, sizeof(sendbuf)); + memset(config, 0, sizeof(struct ue9ControlConfig)); + + sendbuf[1] = 0xf8; + sendbuf[2] = 0x06; + sendbuf[3] = 0x08; + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + return -1; + } + verb("todo\n"); + return -1; +} + +/* Open TCP/IP connection to the UE9 */ +int ue9_open(const char *host, int port) +{ + int fd; + struct sockaddr_in address; + struct hostent *he; + int window_size = 128 * 1024; + + net_init(); + + /* Create socket */ + fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + verb("socket returned %d\n", fd); + return -1; + } + + /* Set nonblocking */ + if (soblock(fd, 0) < 0) { + verb("can't set nonblocking\n"); + return -1; + } + + /* Set initial window size hint to workaround LabJack firmware bug */ + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&window_size, + sizeof(window_size)); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&window_size, + sizeof(window_size)); + + /* Resolve host */ + address.sin_family = AF_INET; + address.sin_port = htons(port); + he = gethostbyname(host); + if (he == NULL) { + verb("gethostbyname(\"%s\") failed\n", host); + return -1; + } + address.sin_addr = *((struct in_addr *) he->h_addr); + + debug("Resolved %s -> %s\n", host, inet_ntoa(address.sin_addr)); + + /* Connect */ + if (connect_timeout(fd, (struct sockaddr *) &address, sizeof(address), + & (struct timeval) { .tv_sec = UE9_TIMEOUT }) < 0) { + verb("connection to %s:%d failed: %s\n", + inet_ntoa(address.sin_addr), port, compat_strerror(errno)); + return -1; + } + + return fd; +} + +/* Close connection to the UE9 */ +void ue9_close(int fd) +{ + /* does anyone actually call shutdown these days? */ + shutdown(fd, 2 /* SHUT_RDWR */); + close(fd); +} + +/* Compute scanrate based on the provided values. */ +double ue9_compute_rate(uint8_t scanconfig, uint16_t scaninterval) +{ + double clock; + + /* A "scan" is across all channels. Each scan is triggered at + a fixed rate, and not affected by the number of channels. + Channels are scanned as quickly as possible. */ + + switch ((scanconfig >> 3) & 3) { + case 0: clock = 4e6; break; + case 1: clock = 48e6; break; + case 2: clock = 750e3; break; + case 3: clock = 24e6; break; + } + + if (scanconfig & 0x2) + clock /= 256; + + if (scaninterval == 0) + return 0; + + return clock / scaninterval; +} + +/* Choose the best ScanConfig and ScanInterval parameters for the + desired scanrate. Returns -1 if no valid config found */ +int ue9_choose_scan(double desired_rate, double *actual_rate, + uint8_t *scanconfig, uint16_t *scaninterval) +{ + int i; + struct { double clock; uint8_t config; } valid[] = { + { 48e6, 0x08 }, + { 24e6, 0x18 }, + { 4e6, 0x00 }, + { 750e3, 0x10 }, + { 48e6 / 256, 0x0a }, + { 24e6 / 256, 0x1a }, + { 4e6 / 256, 0x02 }, + { 750e3 / 256, 0x12 }, + { 0, 0 } }; + + /* Start with the fastest clock frequency. If the + scaninterval would be too large, knock it down until it + fits. */ + for (i = 0; valid[i].clock != 0; i++) { + double interval = valid[i].clock / desired_rate; + + debug("Considering clock %lf (interval %lf)\n", + valid[i].clock, interval); + + if (interval >= 0.5 && interval < 65535.5) { + + *scaninterval = floor(interval + 0.5); + + *scanconfig = valid[i].config; + *actual_rate = ue9_compute_rate( + *scanconfig, *scaninterval); + + debug("Config 0x%02x, desired %lf, actual %lf\n", + *scanconfig, desired_rate, *actual_rate); + + return 0; + } + } + + return -1; +} + +/* Flush data buffers */ +void ue9_buffer_flush(int fd) +{ + uint8_t sendbuf[2], recvbuf[2]; + + sendbuf[1] = 0x08; /* FlushBuffer */ + + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + } +} + +/* Stop stream. Returns < 0 on failure. */ +int ue9_stream_stop(int fd) +{ + uint8_t sendbuf[2], recvbuf[4]; + + sendbuf[1] = 0xB0; + + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + return -1; + } + + if (recvbuf[2] == STREAM_NOT_RUNNING || recvbuf[2] == 0) + return 0; + + debug("error %s\n", ue9_error(recvbuf[2])); + return -recvbuf[2]; +} + +/* Start stream. Returns < 0 on failure. */ +int ue9_stream_start(int fd) +{ + uint8_t sendbuf[2], recvbuf[4]; + + sendbuf[1] = 0xA8; + + if (ue9_command(fd, sendbuf, recvbuf, sizeof(recvbuf)) < 0) { + verb("command failed\n"); + return -1; + } + + if (recvbuf[2] == 0) + return 0; + + debug("error %s\n", ue9_error(recvbuf[2])); + return -recvbuf[2]; +} + +/* "Simple" stream configuration, assumes the channels are all + configured with the same gain. */ +int ue9_streamconfig_simple(int fd, int *channel_list, int channel_count, + uint8_t scanconfig, uint16_t scaninterval, + uint8_t gain) +{ + int i; + uint8_t buf[256]; + + /* Set up StreamConfig command with channels and scan options */ + buf[1] = 0xF8; /* Extended command */ + buf[2] = channel_count + 3; /* Command data words */ + buf[3] = 0x11; /* StreamConfig */ + buf[6] = channel_count; /* Number of channels */ + buf[7] = 12; /* Bit resolution */ + buf[8] = 0; /* Extra settling time */ + buf[9] = scanconfig; + buf[10] = scaninterval & 0xff; + buf[11] = scaninterval >> 8; + + for (i = 0; i < channel_count; i++) { + buf[12 + 2*i] = channel_list[i]; /* Channel number */ + buf[13 + 2*i] = gain; /* Gain/bipolar setup */ + } + + /* Send StreamConfig */ + if (ue9_command(fd, buf, buf, 8) < 0) { + debug("command failed\n"); + return -1; + } + + if (buf[6] != 0) { + verb("returned error %s\n", ue9_error(buf[6])); + return -1; + } + + return 0; +} + +/* Stream data and pass it to the data callback. If callback returns + negative, stops reading and returns 0. Returns < 0 on error. */ +int ue9_stream_data(int fd, int channels, + ue9_stream_cb_t callback, void *context) +{ + int ret; + uint8_t buf[46]; + uint8_t packet = 0; + int channel = 0; + int i; + uint16_t data[channels]; + + for (;;) { + /* Receive data */ + ret = recv_all_timeout(fd, buf, 46, 0, & (struct timeval) + { .tv_sec = UE9_TIMEOUT }); + + /* Verify packet format */ + if (ret != 46) { + verb("short recv %d\n", (int)ret); + return -1; + } + + if (!ue9_verify_extended(buf, 46) || + !ue9_verify_normal(buf, 6)) { + verb("bad checksum\n"); + return -2; + } + + if (buf[1] != 0xF9 || buf[2] != 0x14 || buf[3] != 0xC0) { + verb("bad command bytes\n"); + return -3; + } + + if (buf[11] != 0) { + verb("stream error: %s\n", ue9_error(buf[11])); + return -4; + } + + /* Check for dropped packets. */ + if (buf[10] != packet) { + verb("expected packet %d, but received packet %d\n", + packet, buf[10]); + return -5; + } + packet++; + + /* Check comm processor backlog (up to 512 kB) */ + if (buf[45] & 0x80) { + verb("buffer overflow in CommBacklog, aborting\n"); + return -6; + } + if ((buf[45] & 0x7f) > 112) + debug("warning: CommBacklog is high (%d bytes)\n", + (buf[45] & 0x7f) * 4096); + + /* Check control processor backlog (up to 256 bytes). */ + if (buf[44] == 255) { + verb("ControlBacklog is maxed out, aborting\n"); + return -7; + } + if (buf[44] > 224) + debug("warning: ControlBacklog is high (%d bytes)\n", + buf[44]); + + /* Read samples from the buffer */ + for (i = 12; i <= 42; i += 2) { + data[channel++] = buf[i] + (buf[i+1] << 8); + if (channel < channels) + continue; + + /* Received a full scan, send to callback */ + channel = 0; + if ((*callback)(channels, data, context) < 0) { + /* We're done */ + return 0; + } + } + } +} diff --git a/ue9.h b/ue9.h new file mode 100644 index 0000000..b049cd3 --- /dev/null +++ b/ue9.h @@ -0,0 +1,141 @@ +/* + * Labjack Tools + * Copyright (c) 2003-2007 Jim Paris + * + * This is free software; you can redistribute it and/or modify it and + * it is provided under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation; see COPYING. + */ + +#ifndef UE9_H +#define UE9_H + +#include +#include + +#include "netutil.h" + +/* Calibration data */ +struct ue9Calibration { + double unipolarSlope[4]; + double unipolarOffset[4]; + double bipolarSlope; + double bipolarOffset; + double DACSlope[2]; + double DACOffset[2]; + double tempSlope; + double tempSlopeLow; + double calTemp; + double Vref; + double VrefDiv2; + double VsSlope; + double hiResUnipolarSlope; + double hiResUnipolarOffset; + double hiResBipolarSlope; + double hiResBipolarOffset; +}; + +/* Comm config */ +struct ue9CommConfig { + uint8_t local_id; + uint8_t power_level; + in_addr_t address; + in_addr_t gateway; + in_addr_t subnet; + in_port_t portA; + in_port_t portB; + uint8_t dhcp_enabled; + uint8_t product_id; + uint8_t mac_address[6]; + double hw_version; + double comm_fw_version; +}; + +/* Control config */ +struct ue9ControlConfig { + uint8_t power_level; + uint8_t reset_source; + double control_fw_version; + double control_bl_version; + uint8_t hires; + uint8_t fio_dir; + uint8_t fio_state; + uint8_t eio_dir; + uint8_t eio_state; + uint8_t cio_dirstate;; + uint8_t mio_dirstate; + uint16_t dac0; + uint16_t dac1; +}; + +#define UE9_UNIPOLAR_GAIN1 0x00 +#define UE9_UNIPOLAR_GAIN2 0x01 +#define UE9_UNIPOLAR_GAIN4 0x02 +#define UE9_UNIPOLAR_GAIN8 0x03 +#define UE9_BIPOLAR_GAIN1 0x08 + +#define UE9_CHANNELS 14 + +/* Fill checksums in data buffers */ +void ue9_checksum_normal(uint8_t *buffer, size_t len); +void ue9_checksum_extended(uint8_t *buffer, size_t len); + +/* Verify checksums in data buffers. Returns 0 on error. */ +int ue9_verify_normal(uint8_t *buffer, size_t len); +int ue9_verify_extended(uint8_t *buffer, size_t len); + +/* Open/close TCP/IP connection to the UE9 */ +int ue9_open(const char *host, int port); +void ue9_close(int fd); + +/* Read a memory block from the device. Returns -1 on error. */ +int ue9_memory_read(int fd, int blocknum, uint8_t *buffer, int len); + +/* Convert 64-bit fixed point to double type */ +double ue9_fp64_to_double(uint8_t *data); + +/* Retrieve calibration data or configuration from the device */ +int ue9_get_calibration(int fd, struct ue9Calibration *calib); +int ue9_get_comm_config(int fd, struct ue9CommConfig *config); +int ue9_get_control_config(int fd, struct ue9ControlConfig *config); + +/* Data conversion. If calib is NULL, use uncalibrated conversions. */ +double ue9_binary_to_analog(struct ue9Calibration *calib, + uint8_t gain, uint8_t resolution, uint16_t data); + +/* Compute scanrate based on the provided values. */ +double ue9_compute_rate(uint8_t scanconfig, uint16_t scaninterval); + +/* Choose the best ScanConfig and ScanInterval parameters for the + desired scanrate. Returns 0 if nothing can be chosen. */ +int ue9_choose_scan(double desired_rate, double *actual_rate, + uint8_t *scanconfig, uint16_t *scaninterval); + +/* Flush data buffers */ +void ue9_buffer_flush(int fd); + +/* Stop stream. Returns < 0 on failure. */ +int ue9_stream_stop(int fd); + +/* Start stream. Returns < 0 on failure. */ +int ue9_stream_start(int fd); + +/* Execute a command on the UE9. Returns -1 on error. Fills the + checksums on the outgoing packets, and verifies them on the + incoming packets. Data in "out" is transmitted, data in "in" is + received. */ +int ue9_command(int fd, uint8_t *out, uint8_t *in, int inlen); + +/* "Simple" stream configuration, assumes the channels are all + configured with the same gain. */ +int ue9_streamconfig_simple(int fd, int *channel_list, int channel_count, + uint8_t scanconfig, uint16_t scaninterval, + uint8_t gain); + +/* Stream data and pass it to the data callback. If callback returns + negative, stops reading and returns 0. Returns < 0 on error. */ +typedef int (*ue9_stream_cb_t)(int channels, uint16_t *data, void *context); +int ue9_stream_data(int fd, int channels, + ue9_stream_cb_t callback, void *context); + +#endif diff --git a/ue9error.c b/ue9error.c new file mode 100644 index 0000000..d8e8f6d --- /dev/null +++ b/ue9error.c @@ -0,0 +1,51 @@ +#include "ue9error.h" + +const char *ue9_error_text[] = { + [0] = "(no error)", + [SCRATCH_WRT_FAIL] = "SCRATCH_WRT_FAIL", + [SCRATCH_ERASE_FAIL] = "SCRATCH_ERASE_FAIL", + [DATA_BUFFER_OVERFLOW] = "DATA_BUFFER_OVERFLOW", + [ADC0_BUFFER_OVERFLOW] = "ADC0_BUFFER_OVERFLOW", + [FUNCTION_INVALID] = "FUNCTION_INVALID", + [SWDT_TIME_INVALID] = "SWDT_TIME_INVALID", + [FLASH_WRITE_FAIL] = "FLASH_WRITE_FAIL", + [FLASH_ERASE_FAIL] = "FLASH_ERASE_FAIL", + [FLASH_JMP_FAIL] = "FLASH_JMP_FAIL", + [FLASH_PSP_TIMEOUT] = "FLASH_PSP_TIMEOUT", + [FLASH_ABORT_RECEIVED] = "FLASH_ABORT_RECEIVED", + [FLASH_PAGE_MISMATCH] = "FLASH_PAGE_MISMATCH", + [FLASH_BLOCK_MISMATCH] = "FLASH_BLOCK_MISMATCH", + [FLASH_PAGE_NOT_IN_CODE_AREA] = "FLASH_PAGE_NOT_IN_CODE_AREA", + [MEM_ILLEGAL_ADDRESS] = "MEM_ILLEGAL_ADDRESS", + [FLASH_LOCKED] = "FLASH_LOCKED", + [INVALID_BLOCK] = "INVALID_BLOCK", + [FLASH_ILLEGAL_PAGE] = "FLASH_ILLEGAL_PAGE", + [STREAM_IS_ACTIVE] = "STREAM_IS_ACTIVE", + [STREAM_TABLE_INVALID] = "STREAM_TABLE_INVALID", + [STREAM_CONFIG_INVALID] = "STREAM_CONFIG_INVALID", + [STREAM_BAD_TRIGGER_SOURCE] = "STREAM_BAD_TRIGGER_SOURCE", + [STREAM_NOT_RUNNING] = "STREAM_NOT_RUNNING", + [STREAM_INVALID_TRIGGER] = "STREAM_INVALID_TRIGGER", + [STREAM_CONTROL_BUFFER_OVERFLOW] = "STREAM_CONTROL_BUFFER_OVERFLOW", + [STREAM_SCAN_OVERLAP] = "STREAM_SCAN_OVERLAP", + [STREAM_SAMPLE_NUM_INVALID] = "STREAM_SAMPLE_NUM_INVALID", + [STREAM_BIPOLAR_GAIN_INVALID] = "STREAM_BIPOLAR_GAIN_INVALID", + [STREAM_SCAN_RATE_INVALID] = "STREAM_SCAN_RATE_INVALID", + [TIMER_INVALID_MODE] = "TIMER_INVALID_MODE", + [TIMER_QUADRATURE_AB_ERROR] = "TIMER_QUADRATURE_AB_ERROR", + [TIMER_QUAD_PULSE_SEQUENCE] = "TIMER_QUAD_PULSE_SEQUENCE", + [TIMER_BAD_CLOCK_SOURCE] = "TIMER_BAD_CLOCK_SOURCE", + [TIMER_STREAM_ACTIVE] = "TIMER_STREAM_ACTIVE", + [TIMER_PWMSTOP_MODULE_ERROR] = "TIMER_PWMSTOP_MODULE_ERROR", + [EXT_OSC_NOT_STABLE] = "EXT_OSC_NOT_STABLE", + [INVALID_POWER_SETTING] = "INVALID_POWER_SETTING", + [PLL_NOT_LOCKED] = "PLL_NOT_LOCKED" +}; + +const char *ue9_error(int errorcode) +{ + if (errorcode > ARRAY_SIZE(ue9_error_text)) + return "(invalid errorcode)"; + else + return ue9_error_text[errorcode]; +} diff --git a/ue9error.h b/ue9error.h new file mode 100644 index 0000000..28198e6 --- /dev/null +++ b/ue9error.h @@ -0,0 +1,49 @@ +#ifndef UE9ERROR_H +#define UE9ERROR_H + +#include "util.h" + +#define SCRATCH_WRT_FAIL 1 +#define SCRATCH_ERASE_FAIL 2 +#define DATA_BUFFER_OVERFLOW 3 +#define ADC0_BUFFER_OVERFLOW 4 +#define FUNCTION_INVALID 5 +#define SWDT_TIME_INVALID 6 +#define FLASH_WRITE_FAIL 16 +#define FLASH_ERASE_FAIL 17 +#define FLASH_JMP_FAIL 18 +#define FLASH_PSP_TIMEOUT 19 +#define FLASH_ABORT_RECEIVED 20 +#define FLASH_PAGE_MISMATCH 21 +#define FLASH_BLOCK_MISMATCH 22 +#define FLASH_PAGE_NOT_IN_CODE_AREA 23 +#define MEM_ILLEGAL_ADDRESS 24 +#define FLASH_LOCKED 25 +#define INVALID_BLOCK 26 +#define FLASH_ILLEGAL_PAGE 27 +#define STREAM_IS_ACTIVE 48 +#define STREAM_TABLE_INVALID 49 +#define STREAM_CONFIG_INVALID 50 +#define STREAM_BAD_TRIGGER_SOURCE 51 +#define STREAM_NOT_RUNNING 52 +#define STREAM_INVALID_TRIGGER 53 +#define STREAM_CONTROL_BUFFER_OVERFLOW 54 +#define STREAM_SCAN_OVERLAP 55 +#define STREAM_SAMPLE_NUM_INVALID 56 +#define STREAM_BIPOLAR_GAIN_INVALID 57 +#define STREAM_SCAN_RATE_INVALID 58 +#define TIMER_INVALID_MODE 64 +#define TIMER_QUADRATURE_AB_ERROR 65 +#define TIMER_QUAD_PULSE_SEQUENCE 66 +#define TIMER_BAD_CLOCK_SOURCE 67 +#define TIMER_STREAM_ACTIVE 68 +#define TIMER_PWMSTOP_MODULE_ERROR 69 +#define EXT_OSC_NOT_STABLE 80 +#define INVALID_POWER_SETTING 81 +#define PLL_NOT_LOCKED 82 + +extern const char *ue9_error_text[]; + +const char *ue9_error(int errorcode); + +#endif diff --git a/util.h b/util.h new file mode 100644 index 0000000..ea84000 --- /dev/null +++ b/util.h @@ -0,0 +1,6 @@ +#ifndef UTIL_H +#define UTIL_H + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#endif diff --git a/version.h b/version.h new file mode 100644 index 0000000..292fd62 --- /dev/null +++ b/version.h @@ -0,0 +1,2 @@ +/* This file was automatically generated. */ +#define VERSION "1.3 (2008-09-19)"