From 6a8ef052daa0e1854f93cebf70e97e5ed9daf740 Mon Sep 17 00:00:00 2001 From: rob Date: Sat, 4 Jun 2022 17:16:28 +1000 Subject: [PATCH] Various improvement --- Makefile | 13 +- howard.c | 1067 ++++++++++++++++++++++++++++++++------------ howard.h | 137 +++++- initscripts/howard | 90 +++- lsusb.c | 14 + usbreset.c | 40 ++ 6 files changed, 1045 insertions(+), 316 deletions(-) create mode 100644 usbreset.c diff --git a/Makefile b/Makefile index 10bcab8..ed92556 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,15 @@ -CFLAGS=-g -Wall `pkg-config --cflags hidapi` -LDFLAGS=`pkg-config --libs hidapi` +CFLAGS=-c -D_LINUX -g -Wall -I/usr/include/hidapi -I/usr/include/libusb-1.0 +LDFLAGS=-L/usr/lib/x86_64-linux-gnu/ -lhidapi-hidraw -lusb-1.0 all: lsusb howard lsusb: lsusb.c - gcc -olsusb $(CFLAGS) $(LDFLAGS) lsusb.c + gcc $(CFLAGS) lsusb.c -olsusb.o + gcc lsusb.o $(LDFLAGS) -o lsusb howard: howard.c howard.h - gcc -ohoward $(CFLAGS) $(LDFLAGS) howard.c + gcc $(CFLAGS) howard.c -ohoward.o + gcc howard.o $(LDFLAGS) -o howard + +install: howard + cp -af howard /usr/local/bin/howard diff --git a/howard.c b/howard.c index 396c10a..7f1580f 100644 --- a/howard.c +++ b/howard.c @@ -1,6 +1,11 @@ #include #include +#ifdef _LINUX +#include +#include +#endif + #ifdef _TERMUX #include #include @@ -14,27 +19,19 @@ #include #include #include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include #include "howard.h" -#define TRUE (-1) -#define FALSE (0) - -#define BUFLEN 8196 -#define BUFLENSMALL 255 - -// Benestar sound meter -//#define MIC_VID (0x1630) -//#define MIC_PID (0x05dc) -#define MIC_VID (0x8817) -#define MIC_PID (0x2109) - -#define CMD_CAPTURE (0xb3) - - const char *rangestr[] = { "30-130", "30-80", @@ -43,42 +40,170 @@ const char *rangestr[] = { "80-130", }; -char influxhost[BUFLENSMALL]; -char influxdb[BUFLENSMALL]; -char influxuser[BUFLENSMALL]; -char influxpass[BUFLENSMALL]; +enum run_mode mode = M_PROBE; +enum math_mode mathmode = M_MAX; -#define FLAG_DBCMODE (0x10) -#define FLAG_FASTMODE (0x40) +// 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 -#define E_TIMEOUT -1 -#define E_BADREAD -2 +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) -// ANSI stuff -#define BOLD "\x1b[1m" -#define ITALIC "\x1b[3m" -#define STRIKE "\x1b[9m" -#define PLAIN "\x1b[0m" -#define UNDERLINE "\x1b[4m" -#define RED "\x1b[31m" -#define MAGENTA "\x1b[35m" -#define GREEN "\x1b[32m" -#define YELLOW "\x1b[33m" -#define BLUE "\x1b[34m" -#define CYAN "\x1b[36m" -#define GREY "\x1b[2;37m" +// 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); } -void info( char* format, ... ) { - va_list args; - va_start(args, format); - colprintf( "*", CYAN, format, &args); - va_end( args ); +// 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, ... ) { @@ -88,89 +213,118 @@ void err( char* format, ... ) { va_end( args ); } -void warn( char* format, ... ) { - va_list args; - va_start(args, format); - colprintf( "WARNING:", YELLOW, 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; - } + if (strstr(hex, "0x") == hex) base=0; return strtol(hex, NULL, base); } -int readresult(hid_device *dev, uint8_t *retbuf) { - int totbytes = 0; - int wantbytes = 8; - time_t starttime; - int timeoutms = 2000; - starttime = time(NULL); +int influx_cmd(enum influxcmdtype cmdtype, char *cmd, char *retbuf) { + char header[BUFLEN]; + char newcmd[BUFLEN+2]; + int rc = -1; - while (totbytes != wantbytes) { - int thisbytes = 0; - thisbytes = hid_read_timeout(dev, &retbuf[totbytes], wantbytes + 1 - totbytes, timeoutms); - if (thisbytes < 0) { - err("Incomplete read from usb (got %d bytes, want %d bytes): %ls",totbytes,wantbytes, hid_error(dev)); - return E_BADREAD; - } - - totbytes += thisbytes; - if (time(NULL) - starttime > (timeoutms*1000)) { - warn("Timeout reading from usb"); - return E_TIMEOUT; - } + 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; } - return 0; + + 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; } -void showlevel(uint8_t *buf) { - uint16_t decibels; - uint8_t flags, rangeidx; - time_t now; - char *longformat = "%12s: %ld\n"; - char *strformat = "%12s: %s\n"; - char *dbformat = "%12s: %4.2f %s\n"; - decibels = buf[0] << 8 | buf[1]; - flags = buf[2]; - rangeidx = buf[2] & 0xf; - now = time(NULL); - - printf(longformat, "Time", now); - printf(dbformat, "Level", (double)decibels/10.0, flags & FLAG_DBCMODE ? "dBC" : "dBA"); - printf(strformat, "Mode", flags & FLAG_FASTMODE ? "Fast" : "Slow"); - printf(strformat, "Range", rangeidx > 0x4 ? "unknown" : rangestr[rangeidx]); -} - -int tcpconnect(char *hname, int port) { - int sockfd; - struct sockaddr_in servaddr; - struct hostent *hptr; - - hptr = resolve(hname); - if (!hptr) return TRUE; - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - perror("socket()"); - return -1; - } - 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)); - if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) { - printf("errno is %d\n", errno); - perror("tcpconnect()"); - return -1; - } - return sockfd; -} - -int dohttp(char *hname, int port, char *header, char *body, char *retbuf) { +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; @@ -197,7 +351,8 @@ int dohttp(char *hname, int port, char *header, char *body, char *retbuf) { // get response n = read(sockfd, response, sizeof(response)); if (n < 0) { - err("POST failed during response read"); + err("POST failed during response read (n=%d)", n); + perror("read()"); close(sockfd); return -1; } @@ -226,54 +381,15 @@ int dohttp(char *hname, int port, char *header, char *body, char *retbuf) { return retcode_int; } -struct hostent *resolve(char *hname) { - struct hostent *hptr; - hptr = gethostbyname(hname); - if (hptr == NULL) { - err("Failed to resolve %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; -} - int influx_init(char *hname, char *db, char *user, char *pass) { - strncpy(influxhost, hname, BUFLENSMALL); - strncpy(influxdb, db, BUFLENSMALL); - strncpy(influxuser, user, BUFLENSMALL); - strncpy(influxpass, pass, BUFLENSMALL); - return FALSE; -} - -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 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 TRUE; - } - - rc = dohttp(influxhost, 8086, header, newcmd, retbuf); - if (rc != 204) { - err("Influx write to %s/%s failed (HTTP %d)",influxhost,influxdb,rc); + 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; @@ -283,148 +399,533 @@ 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_ping(char *retbuf) { - int rv; - rv = influx_cmd(I_PING, "", retbuf); - if (!rv) { - info("Ping success:\n%s\n", 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; } -int influx_write_decibels(double decibels) { - char cmd[BUFLEN]; - snprintf(cmd, BUFLEN, "volume decibels=%0.1f\n", decibels); - influx_insert(cmd, NULL); - return 0; +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[]) { - //unsigned char buf[65]; - //hid_device *handle; - uint8_t capture_cmd[8] = { CMD_CAPTURE }; - uint8_t buf[8]; - char retbuf[BUFLEN]; - int res; - int i; - enum mode_enum { M_PROBE, M_TEST, M_TESTDB } mode = M_PROBE; - hid_device *dev = NULL; + uint8_t resultbuf[9]; + int finished = 0, opt; + result_t data; + + set_ansi(TRUE); // handle args - for (i=1; i= 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); } - influx_init("gridbug.nethack.net", "haven", "", ""); + + if (mode == M_TEST || mode == M_TESTDB) verbose = TRUE; + + if (validateargs()) exit(1); if (mode == M_TESTDB) { - printf("running ping...\n"); - influx_ping(retbuf); - exit(1); + info("Running database test then aborting...\n"); + hook_exits(); + exit(init_influxdb()); } - - if (hid_init()) { - perror("hid_init()"); - err("Failed to initialise hidapi."); - exit(1); - } - dev = hid_open(MIC_VID, MIC_PID, NULL); - if (mode == M_TEST) { - if (dev) { - info("Found Benestar USB sound meter (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID); - exit(0); + 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; } - if (!dev) { - err("Benestar USB sound meter not found (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID); - exit(1); - } - - // get current reading - res = hid_write(dev, capture_cmd, 9); - - // wait for response - // see: https://github.com/pvachon/gm1356/blob/master/splread.c - res = E_TIMEOUT; - while (res == E_TIMEOUT) { - res = readresult(dev, buf); - } - if (res == 0) { - showlevel(buf); - } - -/* - ret = dev.ctrl_transfer(0xC0,4,0,0,200) - dB = (ret[0]+((ret[1]&3)*256))*0.1+30 - print dB - msg="{'dB':'"+str(dB)+"'}" - try: - requests.post('https://temporacloud.com/connection/clientSend', data={'streams':streams,'tokens':tokens,'message':msg},verify=False) - except: None -*/ - - hid_close(dev); - - hid_exit(); return 0; } - - -/* - - // Initialize the hidapi library - res = hid_init(); - - // Open the device using the VID, PID, - // and optionally the Serial number. - handle = hid_open(0x4d8, 0x3f, NULL); - - // Read the Manufacturer String - res = hid_get_manufacturer_string(handle, wstr, MAX_STR); - wprintf(L"Manufacturer String: %s\n", wstr); - - // Read the Product String - res = hid_get_product_string(handle, wstr, MAX_STR); - wprintf(L"Product String: %s\n", wstr); - - // Read the Serial Number String - res = hid_get_serial_number_string(handle, wstr, MAX_STR); - wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr); - - // Read Indexed String 1 - res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); - wprintf(L"Indexed String 1: %s\n", wstr); - - // Toggle LED (cmd 0x80). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x80; - res = hid_write(handle, buf, 65); - - // Request state (cmd 0x81). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x81; - res = hid_write(handle, buf, 65); - - // Read requested state - res = hid_read(handle, buf, 65); - - // Print out the returned buffer. - for (i = 0; i < 4; i++) - printf("buf[%d]: %d\n", i, buf[i]); - - // Close the device - hid_close(handle); - - // Finalize the hidapi library - res = hid_exit(); -*/ - diff --git a/howard.h b/howard.h index 7411012..af8981b 100644 --- a/howard.h +++ b/howard.h @@ -1,18 +1,137 @@ +// booleans +#define TRUE (-1) +#define FALSE (0) +#define BADFD (-1) + +// buffer lengths +#define BUFLEN 8196 +#define BUFLENSMALL 255 +#define BUFLENTINY 32 + +// Benestar sound meter +#define MIC_VID "64bd" +#define MIC_PID "74e3" + +// mic constants +#define FLAG_DBCMODE (0b00010000) // use dBc (not dBa) +#define FLAG_MAXMODE (0b00100000) // get max db over interval +// fast = 125ms exp time avg +// slow = 1000ms exp time avg +#define FLAG_FASTMODE (0b01000000) +// ranges supported by sonometer +#define FLAG_RANGE_30_130 (0b00000000) +#define FLAG_RANGE_30_80 (0b00000001) +#define FLAG_RANGE_50_100 (0b00000010) +#define FLAG_RANGE_60_110 (0b00000011) +#define FLAG_RANGE_80_130 (0b00000100) + +// ANSI stuff + + +char BOLD[BUFLENTINY]; +char ITALIC[BUFLENTINY]; +char STRIKE[BUFLENTINY]; +char PLAIN[BUFLENTINY]; +char UNDERLINE[BUFLENTINY]; +char RED[BUFLENTINY]; +char MAGENTA[BUFLENTINY]; +char GREEN[BUFLENTINY]; +char YELLOW[BUFLENTINY]; +char BLUE[BUFLENTINY]; +char CYAN[BUFLENTINY]; +char GREY[BUFLENTINY]; + +// Output modes +#define OM_SHORT (1) // Print to stdout in one line +#define OM_LONG (2) // Print to stdout over multiple lines +#define OM_DB (4) // Write to influxdb + + +// Control strings +#define CMD_CAPTURE (0xb3) // take a reading +#define CMD_CONFIGURE (0x56) // apply settings +#define RES_ACK (0xc4) // ack for command + +// what to do +enum run_mode { + M_PROBE, // measure noise level + M_TEST, // just check for sononeter then exit + M_TESTDB // just check influxdb access then exit +}; + +// how to measure sound +enum math_mode { + M_MAX, + M_AVG +}; + +// where to log +#define LOGFILE "/var/log/howard.log" +#define PIDFILE "/var/run/howard.pid" + +// return codes for read_single_result() +enum read_status { + E_NOERROR=0, // all good + E_TIMEOUT=-1, // didn't receive all data in time + E_BADREAD=-2 // received too much or too little data +}; + + +typedef struct { + int dirty; + time_t unixtime; + uint8_t flags; + double decibels_last; + double decibels_max; + double decibels_avg; + double decibels_tot; + int count; + int rangeidx; + char range[BUFLENSMALL]; + char dbunits[BUFLENSMALL]; + char checkmode[BUFLENSMALL]; + char timenice[BUFLENSMALL]; +} result_t; enum influxcmdtype { I_READ, I_WRITE, I_PING }; + +// prototypes +int add_output_mode(char *m); +char *append(char *orig, char *new, int maxlen); +void cleanup(void); +void clear_results(result_t *data); void colprintf( char *prefix, const char *col, char* format, va_list *args ); -void info( char* format, ... ); +int configure_mic(void); +int daemonize(void); +struct hostent *dnslookup(char *hname); void err( char* format, ... ); -void warn( char* format, ... ); +double extract_decibels(uint8_t *buf); +void generate_commands(void); +char *getprocfspath(int vid, int pid, char *retvar); +void handle_signal(int signum); int hextoint(char *hex); -int readresult(hid_device *dev, uint8_t *retbuf); -void showlevel(uint8_t *buf); -int tcpconnect(char *hname, int port); -int dohttp(char *hname, int port, char *header, char *body, char *retbuf); -struct hostent *resolve(char *hname); -int influx_init(char *hname, char *db, char *user, char *pass); +int hook_exits(void); int influx_cmd(enum influxcmdtype cmdtype, char *cmd, char *retbuf); +int influx_httppost(char *hname, int port, char *header, char *body, char *retbuf); +int influx_init(char *hname, char *db, char *user, char *pass); int influx_insert(char *cmd, char *retbuf); -int influx_query(char *cmd, char *retbuf); int influx_ping(char *retbuf); +int influx_query(char *cmd, char *retbuf); int influx_write_decibels(double decibels); +void info( char* format, ... ); +int init_influxdb(void); +int init_mic(void); +int init_output(void); +int mic_docmd(hid_device *dev, uint8_t *cmd, int cmdlen, uint8_t *buf); +int output_results(result_t *data); +int parse_results(uint8_t *buf, result_t *data); +void pr(char *col, char* format, ... ); +enum read_status read_single_result(hid_device *dev, uint8_t *retbuf); +void reopen_logfile(int signum); +int resetusb(char *path); +void set_ansi(int wantansi); +int tcpconnect(char *hname, int port); +void usage(void); +int validateargs(void); +void vinfo( char* format, ... ); +void warn( char* format, ... ); diff --git a/initscripts/howard b/initscripts/howard index 86c6ab7..26f712e 100755 --- a/initscripts/howard +++ b/initscripts/howard @@ -15,21 +15,22 @@ CONFIG=/etc/howard.conf PIDFILE="/var/run/howard.pid" -rv=0 -# Carry out specific functions when asked to by the system -case "$1" in - start) +function do_start() { + local rv opts if [[ ! -e $CONFIG ]]; then echo "Config file '$CONFIG' is missing" exit 1 fi echo -n "Starting howard... " - OPTS=$(cat $CONFIG | tr '\n' ' ') - $BINARY $OPTS + opts=$(cat $CONFIG | tr '\n' ' ') + $BINARY $opts rv=$? [[ $rv -eq 0 ]] && echo "ok" || echo "failed" - ;; - stop) + return $rv +} + +function do_stop() { + local rv echo -n "Stopping howard... " if [[ -e $PIDFILE ]]; then kill $(cat "$PIDFILE") @@ -39,36 +40,85 @@ case "$1" in rv=$? fi [[ $rv -eq 0 ]] && echo "ok" || echo "failed" - ;; - status) + return $rv +} + + +function getpid() { + local realpid pid rv=1 + local retvar msg="" + + retvar="$1" + realpid="-1" if [[ -e $PIDFILE ]]; then pid=$(cat "$PIDFILE") if ps -p $pid >/dev/null 2>&1; then - echo "howard is running (PID $pid)" + realpid=$pid + msg="howard is running (PID $pid)" rv=0 else - realpid=$(pgrep howard) + pid=$(pgrep howard) if [[ $? -eq 0 ]]; then - echo "howard is running (PID $realpid) but pidfile has pid $pid" + realpid=$pid rv=0 + msg="howard is running (PID $realpid) but pidfile has pid $pid" else - echo "howard pidfile exists but pid $pid isn't running." - rv=1 + msg="howard pidfile exists but pid $pid isn't running." fi fi else - realpid=$(pgrep -f $BINARY) + pid=$(pgrep -f $BINARY) if [[ $? -eq 0 ]]; then - echo "howard is running (PID $realpid) but pidfile is missing." + realpid=$pid + msg="howard is running (PID $realpid) but pidfile is missing." rv=0 else - echo "howard is not running." - rv=1 + msg="howard is not running." fi fi + [[ -n $retvar ]] && eval "$retvar='$msg'" + echo "$realpid" +} + +function do_reload() { + local pid rv=0 + echo -n "Reloading howard... " + pid=$(getpid) + if [[ $? -eq 0 ]]; then + kill -HUP $pid + echo "ok" + else + echo "failed - howard is not running" + rv=1 + fi + return $rv +} + +rv=0 +# Carry out specific functions when asked to by the system +case "$1" in + start) + do_start + rv=$? + ;; + stop) + do_stop + rv=$? + ;; + restart) + do_stop && do_start + rv=$? + ;; + reload) + do_reload + ;; + status) + getpid retmsg >/dev/null + rv=$? + echo "$retmsg" ;; *) - echo "Usage: /etc/init.d/howard {start|stop|status}" + echo "Usage: /etc/init.d/howard {start|stop|status|reload|restart}" exit 1 ;; esac diff --git a/lsusb.c b/lsusb.c index 41a17de..43cbeec 100644 --- a/lsusb.c +++ b/lsusb.c @@ -2,6 +2,10 @@ #include #include #include +#ifdef _LINUX +#include +#include +#endif #include @@ -38,6 +42,7 @@ int main(int argc, char *argv[]) } alldevs = hid_enumerate(wantvendorid, wantproductid); for (dev=alldevs; dev; dev = dev->next) { + hid_device *dev2 = NULL; printf("%s\n", dev->path); printf(" Manufacturer: %ls\n", dev->manufacturer_string); printf(" Vendor ID: 0x%04hx\n", dev->vendor_id); @@ -47,6 +52,15 @@ int main(int argc, char *argv[]) printf(" Interface: %d\n", dev->interface_number); printf(" Usage (page): 0x%hx (0x%hx)\n", dev->usage, dev->usage_page); printf("\n"); + dev2 = hid_open(dev->vendor_id, dev->product_id, NULL); + if (dev2) { + printf("device open ok\n"); + hid_close(dev2); + hid_exit(); + } else { + printf("device open FAILED\n"); + } + printf("\n"); } hid_exit(); return 0; diff --git a/usbreset.c b/usbreset.c new file mode 100644 index 0000000..76c30c0 --- /dev/null +++ b/usbreset.c @@ -0,0 +1,40 @@ +/* usbreset -- send a USB port reset to a USB device */ + +#include +#include +#include +#include +#include + +#include + + +int main(int argc, char **argv) +{ + const char *filename; + int fd; + int rc; + + if (argc != 2) { + fprintf(stderr, "Usage: usbreset device-filename\n"); + return 1; + } + filename = argv[1]; + + fd = open(filename, O_WRONLY); + if (fd < 0) { + perror("Error opening output file"); + return 1; + } + + printf("Resetting USB device %s\n", filename); + rc = ioctl(fd, USBDEVFS_RESET, 0); + if (rc < 0) { + perror("Error in ioctl"); + return 1; + } + printf("Reset successful\n"); + + close(fd); + return 0; +}