/* * 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 "netutil.h" #include "compat.h" #include "debug.h" #include "nerdjack.h" #include "util.h" #include "netutil.h" #include "ethstream.h" #define NERDJACK_TIMEOUT 5 /* Timeout for connect/send/recv, in seconds */ #define NERD_HEADER_SIZE 8 #define MAX_SOCKETS 32 typedef struct __attribute__ ((__packed__)) { unsigned char headerone; unsigned char headertwo; unsigned short packetNumber; unsigned short adcused; unsigned short packetsready; signed short data[NERDJACK_NUM_SAMPLES]; } dataPacket; struct discovered_socket { int sock; uint32_t local_ip; uint32_t subnet_mask; }; struct discover_t { struct discovered_socket socks[MAX_SOCKETS]; unsigned int sock_count; }; /* 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, unsigned long *period) { //The ffffe is because of a silicon bug. The last bit is unusable in all //devices so far. It is worked around on the chip, but giving it exactly //0xfffff would cause the workaround code to roll over. *period = floor ((double) NERDJACK_CLOCK_RATE / desired_rate); if (*period > 0x0ffffe) { info ("Cannot sample that slowly\n"); *actual_rate = (double) NERDJACK_CLOCK_RATE / (double) 0x0ffffe; *period = 0x0ffffe; return -1; } //Period holds the period register for the NerdJack, so it needs to be right *actual_rate = (double) NERDJACK_CLOCK_RATE / (double) *period; if (*actual_rate != desired_rate) { return -1; } return 0; } /** * Create a discovered socket and add it to the socket list structure. * All sockets in the structure should be created, bound, and ready for broadcasting */ static int discovered_sock_create(struct discover_t *ds, uint32_t local_ip, uint32_t subnet_mask) { if (ds->sock_count >= MAX_SOCKETS) { return 0; } /* Create socket. */ int sock = (int)socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { return 0; } /* Allow broadcast. */ int sock_opt = 1; setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); /* Set nonblocking */ if (soblock (sock, 0) < 0) { verb ("can't set nonblocking\n"); return 0; } /* Bind socket. */ struct sockaddr_in sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(local_ip); sock_addr.sin_port = htons(0); if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { close(sock); return 0; } /* Write sock entry. */ struct discovered_socket *dss = &ds->socks[ds->sock_count++]; dss->sock = sock; dss->local_ip = local_ip; dss->subnet_mask = subnet_mask; return 1; } /** * Enumerate all interfaces we can find and open sockets on each */ #if defined(USE_IPHLPAPI) static void enumerate_interfaces(struct discover_t *ds) { PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO); DWORD Ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); if (Ret != NO_ERROR) { free(pAdapterInfo); if (Ret != ERROR_BUFFER_OVERFLOW) { return; } pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); Ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); if (Ret != NO_ERROR) { free(pAdapterInfo); return; } } PIP_ADAPTER_INFO pAdapter = pAdapterInfo; while (pAdapter) { IP_ADDR_STRING *pIPAddr = &pAdapter->IpAddressList; while (pIPAddr) { uint32_t local_ip = ntohl(inet_addr(pIPAddr->IpAddress.String)); uint32_t mask = ntohl(inet_addr(pIPAddr->IpMask.String)); if (local_ip == 0) { pIPAddr = pIPAddr->Next; continue; } discovered_sock_create(ds, local_ip, mask); pIPAddr = pIPAddr->Next; } pAdapter = pAdapter->Next; } free(pAdapterInfo); } #else static void enumerate_interfaces(struct discover_t *ds) { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { return; } struct ifconf ifc; uint8_t buf[8192]; ifc.ifc_len = sizeof(buf); ifc.ifc_buf = (char *)buf; memset(buf, 0, sizeof(buf)); if (ioctl(fd, SIOCGIFCONF, &ifc) != 0) { close(fd); return; } uint8_t *ptr = (uint8_t *)ifc.ifc_req; uint8_t *end = (uint8_t *)&ifc.ifc_buf[ifc.ifc_len]; while (ptr <= end) { struct ifreq *ifr = (struct ifreq *)ptr; ptr += _SIZEOF_ADDR_IFREQ(*ifr); if (ioctl(fd, SIOCGIFADDR, ifr) != 0) { continue; } struct sockaddr_in *addr_in = (struct sockaddr_in *)&(ifr->ifr_addr); uint32_t local_ip = ntohl(addr_in->sin_addr.s_addr); if (local_ip == 0) { continue; } if (ioctl(fd, SIOCGIFNETMASK, ifr) != 0) { continue; } struct sockaddr_in *mask_in = (struct sockaddr_in *)&(ifr->ifr_addr); uint32_t mask = ntohl(mask_in->sin_addr.s_addr); discovered_sock_create(ds, local_ip, mask); } } #endif /** * Close all sockets previously enumerated and free the struct */ static void destroy_socks(struct discover_t *ds) { unsigned int i; for (i = 0; i < ds->sock_count; i++) { struct discovered_socket *dss = &ds->socks[i]; close(dss->sock); } free(ds); } /* Perform autodetection. Returns 0 on success, -1 on error * Sets ipAddress to the detected address */ int nerdjack_detect (char *ipAddress) { int32_t receivesock; struct sockaddr_in sa, receiveaddr, sFromAddr; int buffer_length; char buffer[200]; char incomingData[10]; unsigned int lFromLen; sprintf (buffer, "TEST"); buffer_length = strlen (buffer) + 1; net_init (); receivesock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); /* Set nonblocking */ if (soblock (receivesock, 0) < 0) { verb ("can't set nonblocking\n"); return -1; } if (-1 == receivesock) /* if socket failed to initialize, exit */ { verb ("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 receiveaddr.sin_addr.s_addr = INADDR_ANY; bind (receivesock, (struct sockaddr *) &receiveaddr, sizeof (struct sockaddr_in)); struct discover_t *ds = (struct discover_t *)calloc(1, sizeof(struct discover_t)); if (!ds) { return -1; } /* Create a routable broadcast socket. */ if (!discovered_sock_create(ds, 0, 0)) { free(ds); return -1; } /* Detect & create local sockets. */ enumerate_interfaces(ds); /* * Send subnet broadcast using each local ip socket. * This will work with multiple separate 169.254.x.x interfaces. */ unsigned int i; for (i = 0; i < ds->sock_count; i++) { struct discovered_socket *dss = &ds->socks[i]; uint32_t target_ip = dss->local_ip | ~dss->subnet_mask; sa.sin_addr.s_addr = htonl(target_ip); sendto (dss->sock, buffer, buffer_length, 0, (struct sockaddr *) &sa, sizeof (struct sockaddr_in)); } destroy_socks(ds); lFromLen = sizeof (sFromAddr); if (0 > recvfrom_timeout (receivesock, incomingData, sizeof (incomingData), 0, (struct sockaddr *) &sFromAddr, &lFromLen, &(struct timeval) { .tv_sec = NERDJACK_TIMEOUT})) { close(receivesock); 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 (receivesock); return 0; } /* Send the given command to address. The command should be something * of the specified length. This expects the NerdJack to reply with OK * or NO */ int nerd_send_command (const char *address, void *command, int length) { int ret, fd_command; char buf[3]; fd_command = nerd_open (address, NERDJACK_COMMAND_PORT); if (fd_command < 0) { info ("Connect failed: %s:%d\n", address, NERDJACK_COMMAND_PORT); return -2; } /* Send request */ ret = send_all_timeout (fd_command, command, length, 0, &(struct timeval) { .tv_sec = NERDJACK_TIMEOUT}); if (ret < 0 || ret != length) { verb ("short send %d\n", (int) ret); return -1; } ret = recv_all_timeout (fd_command, buf, 3, 0, &(struct timeval) { .tv_sec = NERDJACK_TIMEOUT}); nerd_close_conn (fd_command); if (ret < 0 || ret != 3) { verb ("Error receiving OK for command\n"); return -1; } if (0 != strcmp ("OK", buf)) { verb ("Did not receive OK. Received %s\n", buf); return -4; } return 0; } int nerd_data_stream (int data_fd, int numChannels, int *channel_list, int precision, int convert, int lines, int showmem, unsigned short *currentcount, unsigned int period, int wasreset) { //Variables that should persist across retries static dataPacket buf; static int linesleft = 0; static int linesdumped = 0; //Variables essential to packet processing signed short datapoint = 0; int i; int numChannelsSampled = channel_list[0] + 1; //The number sampled will be the highest channel requested plus 1 //(i.e. channel 0 requested means 1 sampled) for (i = 0; i < numChannels; i++) { if (channel_list[i] + 1 > numChannelsSampled) numChannelsSampled = channel_list[i] + 1; } double voltline[numChannels]; unsigned short dataline[numChannels]; unsigned short packetsready = 0; unsigned short adcused = 0; unsigned short tempshort = 0; int charsread = 0; int numgroupsProcessed = 0; double volts; //The timeout should be the expected time plus 60 seconds //This permits slower speeds to work properly unsigned int expectedtimeout = (period * NERDJACK_NUM_SAMPLES / NERDJACK_CLOCK_RATE) + 60; //Check to see if we're trying to resume //Don't blow away linesleft in that case if (lines != 0 && linesleft == 0) { linesleft = lines; } //If there was a reset, we still need to dump a line because of faulty PDCA start if (wasreset) { linesdumped = 0; } //If this is the first time called, warn the user if we're too fast if (linesdumped == 0) { if (period < (numChannelsSampled * 200 + 600)) { info ("You are sampling close to the limit of NerdJack\n"); info ("Sample fewer channels or sample slower\n"); } } //Now destination structure array is set as well as numDuplicates. int totalGroups = NERDJACK_NUM_SAMPLES / numChannelsSampled; //Loop forever to grab data while ((charsread = recv_all_timeout (data_fd, &buf, NERDJACK_PACKET_SIZE, 0, &(struct timeval) { .tv_sec = expectedtimeout}))) { if (charsread != NERDJACK_PACKET_SIZE) { //There was a problem getting data. Probably a closed //connection. info ("Packet timed out or was too short\n"); return -2; } //First check the header info if (buf.headerone != 0xF0 || buf.headertwo != 0xAA) { info ("No Header info\n"); return -1; } //Check counter info to make sure not out of order tempshort = ntohs (buf.packetNumber); if (tempshort != *currentcount) { info ("Count wrong. Expected %hd but got %hd\n", *currentcount, tempshort); return -1; } //Increment number of packets received *currentcount = *currentcount + 1; adcused = ntohs (buf.adcused); packetsready = ntohs (buf.packetsready); numgroupsProcessed = 0; if (showmem) { printf ("%hd %hd\n", adcused, packetsready); continue; } //While there is still more data in the packet, process it while (numgroupsProcessed < totalGroups) { //Poison the data structure switch (convert) { case CONVERT_VOLTS: memset (voltline, 0, numChannels * sizeof (double)); break; default: case CONVERT_HEX: case CONVERT_DEC: memset (dataline, 0, numChannels * sizeof (unsigned char)); } //Read in each group for (i = 0; i < numChannels; i++) { //Get the datapoint associated with the desired channel datapoint = ntohs (buf. data[channel_list[i] + numgroupsProcessed * numChannelsSampled]); //Place it into the line switch (convert) { case CONVERT_VOLTS: if (channel_list[i] <= 5) { volts = (double) (datapoint / 32767.0) * ((precision & 0x01) ? 5.0 : 10.0); } else { volts = (double) (datapoint / 32767.0) * ((precision & 0x02) ? 5.0 : 10.0); } voltline[i] = volts; break; default: case CONVERT_HEX: case CONVERT_DEC: dataline[i] = (unsigned short) (datapoint - INT16_MIN); break; } } //We want to dump the first line because it's usually spurious if (linesdumped != 0) { //Now print the group switch (convert) { case CONVERT_VOLTS: for (i = 0; i < numChannels; i++) { if (printf ("%lf ", voltline[i]) < 0) goto bad; } break; case CONVERT_HEX: for (i = 0; i < numChannels; i++) { if (printf ("%04hX", dataline[i]) < 0) goto bad; } break; default: case CONVERT_DEC: for (i = 0; i < numChannels; i++) { if (printf ("%hu ", dataline[i]) < 0) goto bad; } break; } if (printf ("\n") < 0) goto bad; //If we're counting lines, decrement them if (lines != 0) { linesleft--; if (linesleft == 0) { return 0; } } } else { linesdumped = linesdumped + 1; } //We've processed this group, so advance the counter numgroupsProcessed++; } } return 0; bad: info ("Output error (disk full?)\n"); return -3; } /* Open a connection to the NerdJack */ 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) { verb ("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 = 3}) < 0) { verb ("connection to %s:%d failed: %s\n", inet_ntoa (stSockAddr.sin_addr), port, compat_strerror (errno)); return -1; } return i32SocketFD; } //Generate an appropriate sample initiation command int nerd_generate_command (getPacket * command, int *channel_list, int channel_count, int precision, unsigned long period) { short channelbit = 0; int i; int highestchannel = 0; for (i = 0; i < channel_count; i++) { if (channel_list[i] > highestchannel) { highestchannel = channel_list[i]; } //channelbit = channelbit | (0x1 << channel_list[i]); } for (i = 0; i <= highestchannel; i++) { channelbit = channelbit | (0x01 << i); } command->word[0] = 'G'; command->word[1] = 'E'; command->word[2] = 'T'; command->word[3] = 'D'; command->channelbit = htons (channelbit); command->precision = precision; command->period = htonl (period); command->prescaler = 0; return 0; } int nerd_close_conn (int data_fd) { shutdown (data_fd, 2); close (data_fd); return 0; }