#include #include #ifdef _LINUX #include #include #endif #ifdef _TERMUX #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "howard.h" const char *rangestr[] = { "30-130", "30-80", "50-100", "60-110", "80-130", }; enum run_mode mode = M_PROBE; enum math_mode mathmode = M_MAX; // globals hid_device *dev = NULL; // mic device int useinflux = FALSE; // write results to influxdb? int continuous = FALSE; // continue after 1st iteration? int dofork = FALSE; // fork into a daemon int isdaemon = FALSE; // are we a daemon? int verbose = FALSE; // extra logging int waitsecs = 3; // time to wait between reads int itersecs = 60; // time to gather max decibels over int outmode = 0; // bitmask using OM_* macros uint8_t config_cmd[17]; // config string based on outmode uint8_t capture_cmd[9] = { CMD_CAPTURE }; int doit = FALSE; // really write to influxdb? char devpath[BUFLEN]; // procfs path to mic (for usb resets) // influxdb parameters char *influxhost = NULL; char *influxdb = NULL; char *influxuser = NULL; char *influxpass = NULL; // functions int add_output_mode(char *m) { if (!strcmp(m, "db")) { outmode |= OM_DB; } else if (!strcmp(m, "short")) { outmode |= OM_SHORT; } else if (!strcmp(m, "long")) { outmode |= OM_LONG; } else { err("Invalid output mode '%s'", m); return TRUE; } return FALSE; } char *append(char *orig, char *new, int maxlen) { if (!orig) return NULL; if (!new) return orig; if (strlen(orig)) { strncat(orig, ", ", maxlen); strncat(orig, new, maxlen); } else { strncpy(orig, new, maxlen); } return orig; } void cleanup(void) { info("Cleaning up...\n"); if (dev) hid_close(dev); if (influxhost) free(influxhost); if (influxdb) free(influxdb); if (influxuser) free(influxuser); if (influxpass) free(influxpass); if (unlink(PIDFILE)) { warn("Failed to remove PID file '%s'", PIDFILE); } hid_exit(); } void clear_results(result_t *data) { data->dirty = 1; data->unixtime = 0; data->flags = 0; data->count = 0; data->decibels_last = 0; data->decibels_max = 0; data->decibels_avg = 0; data->decibels_tot = 0; data->rangeidx = 0; bzero(data->range, BUFLENSMALL); bzero(data->dbunits, BUFLENSMALL); bzero(data->checkmode, BUFLENSMALL); bzero(data->timenice, BUFLENSMALL); } void colprintf( char *prefix, const char *col, char *format, va_list *args ) { printf("%s%s%s %s%s",col,BOLD,prefix,PLAIN,col); vprintf(format, *args); printf("%s\n",PLAIN); fflush(stdout); } // TODO: this doesn't work yet int configure_mic(void) { int res,rv = 1; uint8_t buf[BUFLENSMALL]; vinfo("noop config command"); return 0; vinfo("Sending config command"); hid_write(dev, config_cmd, 17); res = read_single_result(dev, buf); if (res == 0) { if (buf[0] == RES_ACK) { vinfo("Got config ACK"); rv = 0; } else { vinfo("Got non-ack config response"); } } else { vinfo("%s after config", (res == E_TIMEOUT) ? "Timeout" : "Bad read" ); } return rv; } int daemonize(void) { pid_t pid, sid = 0; pid = fork(); if (pid < 0) { err("Fork failed\n"); exit(1); } if (pid > 0) { // parent process FILE *fp; fp = fopen(PIDFILE, "w"); if (fp) { fprintf(fp, "%d", pid); fclose(fp); } else { warn("Failed to open PID file '%s'", PIDFILE); } exit(0); } isdaemon = TRUE; umask(0); //set new session sid = setsid(); if(sid < 0) { // failure err("Set session ID failed\n"); exit(1); } chdir("/"); set_ansi(FALSE); freopen(LOGFILE, "a+", stdout); freopen(LOGFILE, "a+", stderr); return(0); } struct hostent *dnslookup(char *hname) { struct hostent *hptr; hptr = gethostbyname(hname); if (hptr == NULL) { err("Failed to dnslookup %s (%s)",hname,hstrerror(h_errno)); return NULL; } if (hptr->h_addrtype != AF_INET) { err("No IN records for %s.",hname); return NULL; } if (hptr->h_addr_list == NULL) { err("No A records for %s.",hname); return NULL; } return hptr; } void err( char* format, ... ) { va_list args; va_start(args, format); colprintf( "ERROR:", RED, format, &args); va_end( args ); } double extract_decibels(uint8_t *buf) { if (!buf) return -1; if (strlen((char *)buf) < 2) return -1; return (buf[0] << 8 | buf[1])/10; } void generate_commands(void) { struct timeval seed; int i; // command to ask for a capture // bytes 2-4 must be unique for mic to accept command gettimeofday(&seed, NULL); srand(seed.tv_usec); for (i = 1; i <= 3; i++) { capture_cmd[i] = rand() % 256; } for (i = 4; i <= 7; i++) { capture_cmd[i] = 0; } // command to configure mic bzero(config_cmd, 17); config_cmd[0] = CMD_CONFIGURE; // } config //config_cmd[1] ^= FLAG_FASTMODE; // not fast mode config_cmd[1] |= FLAG_MAXMODE; // max mode config_cmd[1] = FLAG_RANGE_30_130; } char *getprocfspath(int vid, int pid, char *retvar) { struct libusb_device **devlist; struct libusb_context *ctx; int i,ndevices,found = 0; if (libusb_init(&ctx)) { err("libusb_init() failed"); return NULL; } ndevices = libusb_get_device_list(ctx, &devlist); for (i = 0; i < ndevices; i++) { struct libusb_device* thisdev = devlist[i]; struct libusb_device_descriptor desc; libusb_get_device_descriptor(thisdev, &desc); if (desc.idVendor == vid && desc.idProduct == pid) { int busnum = -1,devnum = -1; busnum = libusb_get_bus_number(thisdev); devnum = libusb_get_device_address(thisdev); snprintf(retvar, BUFLEN, "/dev/bus/usb/%03d/%03d",busnum,devnum); found = 1; break; } } libusb_free_device_list(devlist, 1); libusb_exit(ctx); if (found) { return retvar; } return NULL; } void handle_signal(int signum) { exit(1); // (ie. call cleanup()) } int hook_exits(void) { if (atexit(cleanup)) { err("Failed to hook exit function.\n"); perror("atexit()"); exit(1); } signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); signal(SIGHUP, reopen_logfile); return(0); } int hextoint(char *hex) { int base=16; if (strstr(hex, "0x") == hex) base=0; return strtol(hex, NULL, base); } int influx_cmd(enum influxcmdtype cmdtype, char *cmd, char *retbuf) { char header[BUFLEN]; char newcmd[BUFLEN+2]; int rc = -1; if (cmdtype == I_WRITE) { snprintf(header, BUFLEN, "POST /write?db=%s&u=%s&p=%s&precision=s HTTP/1.1\r\nHost: influx:8086\r\nContent-Length: %ld\r\n\r\n", influxdb, influxuser, influxpass, strlen(cmd)); snprintf(newcmd,BUFLEN,"%s",cmd); } else if (cmdtype == I_READ) { snprintf(header, BUFLEN, "GET /query?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: influx:8086\r\nContent-Length: %ld\r\n\r\n", influxdb, influxuser, influxpass, strlen(cmd)+2); snprintf(newcmd,BUFLEN,"q=%s",cmd); } else if (cmdtype == I_PING) { snprintf(header, BUFLEN, "GET /ping HTTP/1.1\r\nHost: influx:8086\r\n\r\n"); strncpy(newcmd,"",BUFLEN); } else { err("Invalid influx command type (%d)", cmdtype); return FALSE; } rc = influx_httppost(influxhost, 8086, header, newcmd, retbuf); if (rc != 204) { err("Influx POST to http://%s:8086 db %s failed (HTTP %d)",influxhost,influxdb,rc); return TRUE; } return FALSE; } int influx_httppost(char *hname, int port, char *header, char *body, char *retbuf) { char response[BUFLEN]; char *p,*dp,retcode_str[BUFLENSMALL]; ssize_t n; int sockfd,retcode_int = -1; sockfd = tcpconnect(hname,port); if (sockfd < 0) { err("TCP connect to %s:%d failed", hname, port); return TRUE; } if (write(sockfd, header, strlen(header)) < 0) { err("POST failed during header send"); close(sockfd); return -1; } if (write(sockfd, body, strlen(body)) < 0) { err("POST failed during body send"); close(sockfd); return -1; } // get response n = read(sockfd, response, sizeof(response)); if (n < 0) { err("POST failed during response read (n=%d)", n); perror("read()"); close(sockfd); return -1; } response[n] = 0; // NUL-terminate close(sockfd); if (retbuf) strncpy(retbuf, response, BUFLEN); // HTTP/1.x 204 retcode_int = -1; p = strstr(response, "HTTP/1"); if (p) { p += 9; // should now be at start of return code dp = retcode_str; for ( ; (p + 3 < (response + strlen(response))) && *p && isdigit(*p); p++,dp++) { *dp = *p; } *dp = 0; retcode_int = atoi(retcode_str); } else { retcode_int = -1; } return retcode_int; } int influx_init(char *hname, char *db, char *user, char *pass) { char missing[BUFLEN]; strncpy(missing, "", BUFLEN); if (!hname) append(missing, "hostname (-H)", BUFLEN); if (!db) append(missing, "dbname (-d)", BUFLEN); if (!user) append(missing, "username (-u)", BUFLEN); if (!pass) append(missing, "password (-p)", BUFLEN); if (strlen(missing)) { err("missing influxdb details: %s", missing); return TRUE; } return FALSE; } int influx_insert(char *cmd, char *retbuf) { return influx_cmd(I_WRITE, cmd, retbuf); } int influx_ping(char *retbuf) { int rv; if (!(rv = influx_cmd(I_PING, "", retbuf))) vinfo("Ping success"); return rv; } int influx_query(char *cmd, char *retbuf ) { return influx_cmd(I_READ, cmd, retbuf); } int influx_write_decibels(double decibels) { char cmd[BUFLEN]; time_t now = time(NULL); snprintf(cmd, BUFLEN, "volume decibels=%0.1f %ld\n", decibels, now); if (doit) { influx_insert(cmd, NULL); } else { pr(GREEN, "Proposed influx cmd (use -y to do writes): %s", cmd); } return FALSE; } void info( char* format, ... ) { va_list args; va_start(args, format); colprintf( "*", CYAN, format, &args); va_end( args ); } int init_influxdb(void) { char retbuf[BUFLEN]; if (influx_init(influxhost, influxdb, influxuser, influxpass)) return TRUE; vinfo("Running influxdb http PING..."); if (influx_ping(retbuf)) { err("Couldn't access influxdb at host=%s:8086 db=%s",influxhost,influxdb); return TRUE; } return FALSE; } int init_mic(void) { if (hid_init()) { err("Failed to initialise hidapi. Details:"); perror("hid_init()"); return TRUE; exit(1); } dev = hid_open(hextoint(MIC_VID), hextoint(MIC_PID), NULL); if (dev) { vinfo("Found Benestar USB sound meter (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID); getprocfspath(hextoint(MIC_VID), hextoint(MIC_PID), devpath); vinfo("Full device path is: %s", devpath); } else { err("Benestar USB sound meter not found (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID); return TRUE; } return FALSE; } int init_output(void) { char out[BUFLEN]; bzero(out, BUFLEN); if (outmode & OM_LONG) append(out, "long", BUFLEN); if (outmode & OM_SHORT) append(out, "short", BUFLEN); if (outmode & OM_DB) { char dbstr[BUFLENSMALL]; snprintf(dbstr, BUFLENSMALL, "influxdb(host:%s db:%s user:%s)", influxhost, influxdb, influxuser ); append(out, dbstr, BUFLEN); } vinfo("Output mode(s): %s", out); return FALSE; } int mic_docmd(hid_device *dev, uint8_t *cmd, int cmdlen, uint8_t *buf) { int rv,ntimeouts = 0, donereading = 0; vinfo("Sending command"); hid_write(dev, cmd, cmdlen); vinfo("Waiting for response"); while (!donereading) { rv = read_single_result(dev, buf); if (rv) { // failure if (++ntimeouts >= 3) { // too many timeouts, give up return TRUE; } else { if (ntimeouts == 2) { // after first retry, try resetting the usb device resetusb(devpath); configure_mic(); } // try again hid_write(dev, cmd, cmdlen); } } else { // success donereading = 1; } } return FALSE; } int output_results(result_t *data) { char *strformat = "%12s: %s\n"; char *dbformat = "%12s: %4.2f %s\n"; char *timeformat = "%12s: %ld (%s)\n"; if (!data) return TRUE; if (data->dirty == 0) return TRUE; if (outmode & OM_LONG) { pr(GREEN,timeformat, "Time", data->unixtime, data->timenice); pr(GREEN, dbformat, "Level", data->decibels_last, data->dbunits); pr(GREEN, dbformat, "Level(Avg)", data->decibels_avg, data->dbunits); pr(GREEN, dbformat, "Level(Max)", data->decibels_max, data->dbunits); pr(GREEN, strformat, "Mode", data->checkmode); pr(GREEN, strformat, "Range", data->range); } if (outmode & OM_SHORT) { pr(GREEN, "%s time=[%ld] level=[%4.2f %s avg:%4.2f max:%4.2f] mode=[%s] range=[%s]\n", data->timenice, data->unixtime, data->decibels_last, data->decibels_avg, data->decibels_max, data->dbunits, data->checkmode, data->range); } if (outmode & OM_DB) { influx_write_decibels(mathmode == M_AVG ? data->decibels_avg : data->decibels_max); } return FALSE; } int parse_results(uint8_t *buf, result_t *data) { struct tm *nowlocal; double decibels; if (!data || !buf) return TRUE; decibels = extract_decibels(buf); if (decibels < 0 || decibels > 130) { warn("Ignoring out-of-range decibel reading: %0.2f", decibels); return TRUE; } data->dirty = 1; (data->count)++; data->decibels_last = decibels; data->decibels_tot += decibels; if (decibels > data->decibels_max) { data->decibels_max = decibels; } data->decibels_avg = data->decibels_tot / data->count; data->flags = buf[2]; data->rangeidx = buf[2] & 0xf; time(&(data->unixtime)); nowlocal = localtime(&(data->unixtime)); strftime(data->timenice, BUFLENSMALL, "%d/%b/%Y %H:%M:%S", nowlocal); strncpy(data->dbunits, data->flags & FLAG_DBCMODE ? "dBC" : "dBA", BUFLENSMALL); strncpy(data->checkmode, data->flags & FLAG_FASTMODE ? "Fast" : "Slow", BUFLENSMALL); if (data->flags & FLAG_MAXMODE) strncat(data->checkmode, "-MAX", 5); strncpy(data->range, data->rangeidx > 0x4 ? "unknown" : rangestr[data->rangeidx], BUFLENSMALL); return FALSE; } void pr(char *col, char* format, ... ) { va_list args; va_start(args, format); colprintf( "==>", col, format, &args); va_end( args ); } enum read_status read_single_result(hid_device *dev, uint8_t *retbuf) { int totbytes = 0; int wantbytes = 8; time_t starttime; double timeoutsecs = 0.5; double timeoutms = timeoutsecs*1000; starttime = time(NULL); while (totbytes != wantbytes) { int thisbytes = 0; thisbytes = hid_read_timeout(dev, &retbuf[totbytes], wantbytes + 1 - totbytes, timeoutms); if (thisbytes < 0) { warn("Incomplete read from usb (got %d bytes, want %d bytes): %ls",totbytes,wantbytes, hid_error(dev)); return E_BADREAD; } totbytes += thisbytes; if (thisbytes > 0) { vinfo("Got %d/%d bytes",totbytes,wantbytes); } if (totbytes > wantbytes) { warn("Bad read from usb (got %d bytes, want %d bytes): %ls",totbytes,wantbytes, hid_error(dev)); return E_BADREAD; } if (time(NULL) - starttime > timeoutsecs) { warn("Timeout reading from usb"); return E_TIMEOUT; } } return E_NOERROR; } void reopen_logfile(int signum) { if (isdaemon) { fclose(stdout); fclose(stderr); freopen(LOGFILE, "w+", stdout); freopen(LOGFILE, "w+", stderr); info("Log file re-opened."); } } int resetusb(char *filename) { int fd,rv = FALSE; strncpy(filename, devpath, BUFLEN); warn("Attempting to reset usb port"); fd = open(filename, O_WRONLY); if (fd < 0) { err("Failed to open %s, details:", filename); perror("open()"); rv = TRUE; } else { int rc; rc = ioctl(fd, USBDEVFS_RESET, 0); if (rc < 0) { err("Failed to send reset, details:", filename); perror("ioctl()"); rv = TRUE; } else { info("Reset successful"); rv = FALSE; } close(fd); } return rv; } void set_ansi(int wantansi) { if (wantansi) { strncpy(BOLD,"\x1b[1m", BUFLENTINY); strncpy(ITALIC,"\x1b[3m", BUFLENTINY); strncpy(STRIKE,"\x1b[9m", BUFLENTINY); strncpy(PLAIN,"\x1b[0m", BUFLENTINY); strncpy(UNDERLINE,"\x1b[4m", BUFLENTINY); strncpy(RED,"\x1b[31m", BUFLENTINY); strncpy(MAGENTA,"\x1b[35m", BUFLENTINY); strncpy(GREEN,"\x1b[32m", BUFLENTINY); strncpy(YELLOW,"\x1b[33m", BUFLENTINY); strncpy(BLUE,"\x1b[34m", BUFLENTINY); strncpy(CYAN,"\x1b[36m", BUFLENTINY); strncpy(GREY,"\x1b[2;37m", BUFLENTINY); } else { bzero(BOLD,BUFLENTINY); bzero(ITALIC,BUFLENTINY); bzero(STRIKE,BUFLENTINY); bzero(PLAIN,BUFLENTINY); bzero(UNDERLINE,BUFLENTINY); bzero(RED,BUFLENTINY); bzero(MAGENTA,BUFLENTINY); bzero(GREEN,BUFLENTINY); bzero(YELLOW,BUFLENTINY); bzero(BLUE,BUFLENTINY); bzero(CYAN,BUFLENTINY); bzero(GREY,BUFLENTINY); } } int tcpconnect(char *hname, int port) { int sockfd; struct sockaddr_in servaddr; struct hostent *hptr; fd_set fdset; struct timeval tv; int tcpflags; hptr = dnslookup(hname); if (!hptr) return BADFD; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { err("Couldn't create socket. Details:"); perror("socket()"); return BADFD; } servaddr.sin_family = hptr->h_addrtype; servaddr.sin_port = htons(8086); memcpy(&servaddr.sin_addr, hptr->h_addr_list[0], hptr->h_length); bzero(servaddr.sin_zero, sizeof(servaddr.sin_zero)); // go to non-blocking mode so that we can // use select() to handle syn timeouts fcntl(sockfd, F_SETFL, O_NONBLOCK); if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) { if (errno == EINPROGRESS) { FD_ZERO(&fdset); FD_SET(sockfd, &fdset); tv.tv_sec = 2; tv.tv_usec = 0; if (select(sockfd+1, NULL, &fdset, NULL, &tv) == 1) { int so_error; socklen_t len = sizeof so_error; getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len); if (so_error != 0) { perror("getsockopt(d)"); close(sockfd); return BADFD; } } } else { perror("tcpconnect()"); return BADFD; } } // go back to blocking mode tcpflags = fcntl(sockfd, F_GETFL); fcntl(sockfd, F_SETFL, tcpflags & ~O_NONBLOCK); return sockfd; } void usage(void) { printf("usage: howard [OPTIONS] outputmode1 [ outputmode2 ... ]\n"); printf("\n"); printf("Reads max/average noise level from a Benestar GM1356 sonometer\n"); printf("over a given interval, and optionally stores result in an\n"); printf("influxdb database.\n"); printf("\n"); printf("Valid output modes are:\n"); printf(" short (single line to stdout)\n"); printf(" long (multiple lines to stdout)\n"); printf(" db (HTTP POST to influxdb, must also provide INFLUXDB OPTIONS - see below)\n"); printf("\n"); printf("OPTIONS:\n"); printf(" -a Measure average volume over interval instead of maximum.\n"); printf(" -b Run as a background daemon.\n"); printf(" -c Run endlessly (also see -i and -w)\n"); printf(" -h Show this usage text then exit.\n"); printf(" -i secs Define time over which to get max decibels\n"); printf(" -t Test mode - just probe for sononeter then exit\n"); printf(" -T Influxdb test mode - just test influxdb connectivity then exit\n"); printf(" -w secs Define period to wait between noise level checks\n"); printf(" insert will just be written to stdout.\n"); printf("\n"); printf("INFLUXDB OPTIONS:\n"); printf(" -d dbname Define influxdb database name\n"); printf(" -H hostname Define influxdb hostname\n"); printf(" -p password Define influxdb password\n"); printf(" -u username Define influxdb password\n"); printf(" -y Actually write to influxdb. Without this,\n"); printf("\n"); } int validateargs(void) { if (itersecs <= waitsecs) { err("Iteration time must be greater than wait time.\n"); return TRUE; } if (!outmode && mode == M_PROBE) { err("No output modes provided."); return TRUE; } if ((outmode & OM_DB) || (mode == M_TESTDB)) { useinflux = TRUE; } else { useinflux = FALSE; } return FALSE; } // output only in verbose mode void vinfo( char* format, ... ) { va_list args; if (!verbose) return; va_start(args, format); colprintf( "*", CYAN, format, &args); va_end( args ); } void warn( char* format, ... ) { va_list args; va_start(args, format); colprintf( "WARNING:", YELLOW, format, &args); va_end( args ); } int main(int argc, char *argv[]) { uint8_t resultbuf[9]; int finished = 0, opt; result_t data; set_ansi(TRUE); // handle args while((opt = getopt(argc, argv, ":abcd:o:p:hH:i:tTu:vwy")) != -1) { switch(opt) { case 'a': mathmode = M_AVG; break; case 'b': dofork = TRUE; break; case 'c': continuous = TRUE; break; case 'd': // influxdb database name influxdb = strdup(optarg); break; case 'h': usage(); exit(1); case 'H': // influxdb host name influxhost = strdup(optarg); break; case 'i': // iteration time before writing max itersecs = atoi(optarg); break; case 'p': // influxdb password influxpass = strdup(optarg); break; case 't': mode = M_TEST; break; case 'T': mode = M_TESTDB; break; case 'u': // influxdb username influxuser = strdup(optarg); break; case 'v': verbose = TRUE; break; case 'w': // time to wait between reads waitsecs = atoi(optarg); if (waitsecs < 2) { err("Wait interval must be >= 2\n"); exit(1); } break; case 'y': doit = TRUE; break; case ':': err("%s requires a value\n", optopt); break; case '?': err("Invalid argument '%s'\n", optopt); break; } } for(;optind < argc; optind++){ if (add_output_mode(argv[optind])) exit(1); } if (mode == M_TEST || mode == M_TESTDB) verbose = TRUE; if (validateargs()) exit(1); if (mode == M_TESTDB) { info("Running database test then aborting...\n"); hook_exits(); exit(init_influxdb()); } if (mode == M_TEST) { info("Running mic test then aborting...\n"); hook_exits(); exit(init_mic()); } if (dofork) { if (daemonize()) exit(1); } hook_exits(); generate_commands(); if (useinflux && init_influxdb()) exit(1); if (init_mic()) exit(1); if (configure_mic()) exit(1); if (init_output()) exit(1); while (!finished) { int donereading = 0; time_t iterstart; clear_results(&data); // use max decibel reading over 'itersecs' seconds iterstart = time(NULL); while (!donereading) { if (!mic_docmd(dev, capture_cmd, 9, resultbuf)) { // success parse_results(resultbuf, &data); } if (data.dirty) { pr(MAGENTA, "Reading %2d: %0.2f %s (avg: %0.2f max: %0.2f), %d seconds left ", data.count, data.decibels_last, data.dbunits, data.decibels_avg, data.decibels_max, itersecs - (time(NULL) - iterstart)); } vinfo("Delaying for %d seconds before next read", waitsecs); sleep(waitsecs); // have 'itersecs' passed? if ((time(NULL) - iterstart) >= itersecs) { donereading = 1; } } // output to screen and/or influxdb output_results(&data); if (!continuous) finished = 1; } return 0; }