howard/howard.c

932 lines
26 KiB
C
Raw Normal View History

2021-11-27 16:58:52 +11:00
#include <stdio.h>
#include <stdlib.h>
2021-11-28 20:00:32 +11:00
2022-06-04 17:16:28 +10:00
#ifdef _LINUX
#include <stdarg.h>
#include <ctype.h>
#endif
2021-11-28 20:00:32 +11:00
#ifdef _TERMUX
#include <strings.h>
#include <ctype.h>
#else
2021-11-27 16:58:52 +11:00
#include <string.h>
2021-11-28 20:00:32 +11:00
#endif
2021-11-27 16:58:52 +11:00
#include <wchar.h>
#include <time.h>
#include <hidapi.h>
2021-11-28 18:42:03 +11:00
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
2022-06-04 17:16:28 +10:00
#include <sys/ioctl.h>
2021-11-28 18:42:03 +11:00
#include <sys/errno.h>
#include <sys/types.h>
2022-06-04 17:16:28 +10:00
#include <sys/stat.h>
2021-11-28 18:42:03 +11:00
#include <sys/socket.h>
2022-06-04 17:16:28 +10:00
#include <sys/time.h>
2021-11-28 18:42:03 +11:00
#include <unistd.h>
2022-06-04 17:16:28 +10:00
#include <fcntl.h>
#include <signal.h>
#include <linux/usbdevice_fs.h>
#include <libusb.h>
2021-11-27 16:58:52 +11:00
#include "howard.h"
const char *rangestr[] = {
"30-130",
"30-80",
"50-100",
"60-110",
"80-130",
};
2022-06-04 17:16:28 +10:00
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);
}
2021-11-27 16:58:52 +11:00
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);
2022-06-04 17:16:28 +10:00
fflush(stdout);
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
// 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;
2021-11-27 16:58:52 +11:00
}
void err( char* format, ... ) {
va_list args;
va_start(args, format);
colprintf( "ERROR:", RED, format, &args);
va_end( args );
}
2022-06-04 17:16:28 +10:00
double extract_decibels(uint8_t *buf) {
if (!buf) return -1;
if (strlen((char *)buf) < 2) return -1;
return (buf[0] << 8 | buf[1])/10;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
char *getprocfspath(int vid, int pid, char *retvar) {
struct libusb_device **devlist;
struct libusb_context *ctx;
int i,ndevices,found = 0;
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if (libusb_init(&ctx)) {
err("libusb_init() failed");
return NULL;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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;
2021-11-27 16:58:52 +11:00
}
}
2022-06-04 17:16:28 +10:00
libusb_free_device_list(devlist, 1);
libusb_exit(ctx);
if (found) {
return retvar;
}
return NULL;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
void handle_signal(int signum) {
exit(1); // (ie. call cleanup())
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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);
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
int hextoint(char *hex) {
int base=16;
if (strstr(hex, "0x") == hex) base=0;
return strtol(hex, NULL, base);
}
2021-11-28 18:42:03 +11:00
2022-06-04 17:16:28 +10:00
int influx_cmd(enum influxcmdtype cmdtype, char *cmd, char *retbuf) {
char header[BUFLEN];
char newcmd[BUFLEN+2];
int rc = -1;
2021-11-28 18:42:03 +11:00
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
return FALSE;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
int influx_httppost(char *hname, int port, char *header, char *body, char *retbuf) {
2021-11-28 18:42:03 +11:00
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) {
2022-06-04 17:16:28 +10:00
err("POST failed during response read (n=%d)", n);
perror("read()");
2021-11-28 18:42:03 +11:00
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;
}
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
return FALSE;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
int influx_insert(char *cmd, char *retbuf) {
return influx_cmd(I_WRITE, cmd, retbuf);
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
int influx_ping(char *retbuf) {
int rv;
if (!(rv = influx_cmd(I_PING, "", retbuf))) vinfo("Ping success");
return rv;
}
2021-11-28 18:42:03 +11:00
2022-06-04 17:16:28 +10:00
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);
2021-11-28 18:42:03 +11:00
} else {
2022-06-04 17:16:28 +10:00
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);
2021-11-28 18:42:03 +11:00
return TRUE;
}
2022-06-04 17:16:28 +10:00
return FALSE;
}
2021-11-28 18:42:03 +11:00
2022-06-04 17:16:28 +10:00
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);
2021-11-28 18:42:03 +11:00
return TRUE;
}
return FALSE;
}
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
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);
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
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;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
void pr(char *col, char* format, ... ) {
va_list args;
va_start(args, format);
colprintf( "==>", col, format, &args);
va_end( args );
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
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;
}
2021-11-28 18:42:03 +11:00
}
2022-06-04 17:16:28 +10:00
return E_NOERROR;
}
2022-06-04 17:16:28 +10:00
void reopen_logfile(int signum) {
if (isdaemon) {
fclose(stdout);
fclose(stderr);
freopen(LOGFILE, "w+", stdout);
freopen(LOGFILE, "w+", stderr);
info("Log file re-opened.");
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
}
2022-06-04 17:16:28 +10:00
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;
}
2022-06-04 17:16:28 +10:00
close(fd);
}
2022-06-04 17:16:28 +10:00
return rv;
}
2022-06-04 17:16:28 +10:00
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);
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
}
int tcpconnect(char *hname, int port) {
int sockfd;
struct sockaddr_in servaddr;
struct hostent *hptr;
fd_set fdset;
struct timeval tv;
int tcpflags;
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
hptr = dnslookup(hname);
if (!hptr) return BADFD;
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
err("Couldn't create socket. Details:");
perror("socket()");
return BADFD;
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
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;
}
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
// go back to blocking mode
tcpflags = fcntl(sockfd, F_GETFL);
fcntl(sockfd, F_SETFL, tcpflags & ~O_NONBLOCK);
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
return sockfd;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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");
2021-11-27 16:58:52 +11:00
}
2022-06-04 17:16:28 +10:00
int validateargs(void) {
if (itersecs <= waitsecs) {
err("Iteration time must be greater than wait time.\n");
return TRUE;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if (!outmode && mode == M_PROBE) {
err("No output modes provided.");
return TRUE;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if ((outmode & OM_DB) || (mode == M_TESTDB)) {
useinflux = TRUE;
} else {
useinflux = FALSE;
}
return FALSE;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
// 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 );
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
void warn( char* format, ... ) {
va_list args;
va_start(args, format);
colprintf( "WARNING:", YELLOW, format, &args);
va_end( args );
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
int main(int argc, char *argv[]) {
uint8_t resultbuf[9];
int finished = 0, opt;
result_t data;
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
set_ansi(TRUE);
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
// 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);
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if (mode == M_TEST || mode == M_TESTDB) verbose = TRUE;
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if (validateargs()) exit(1);
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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());
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
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;
}
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
// output to screen and/or influxdb
output_results(&data);
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
if (!continuous) finished = 1;
}
2021-11-27 16:58:52 +11:00
2022-06-04 17:16:28 +10:00
return 0;
}