howard/howard.c

760 lines
20 KiB
C

#include <stdio.h>
#include <stdlib.h>
#ifdef _LINUX
#include <stdarg.h>
#include <ctype.h>
#endif
#ifdef _TERMUX
#include <strings.h>
#include <ctype.h>
#else
#include <string.h>
#endif
#include <wchar.h>
#include <time.h>
#include <hidapi.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <linux/usbdevice_fs.h>
#include <libusb.h>
#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 "64bd"
#define MIC_PID "74e3"
#define CMD_CAPTURE (0xb3)
hid_device *dev = NULL;
const char *rangestr[] = {
"30-130",
"30-80",
"50-100",
"60-110",
"80-130",
};
char *influxhost = NULL;
char *influxdb = NULL;
char *influxuser = NULL;
char *influxpass = NULL;
int doit = FALSE;
int verbose = FALSE;
int outmode = 0;
#define OM_SHORT (1)
#define OM_LONG (2)
#define OM_DB (4)
#define FLAG_DBCMODE (0x10)
#define FLAG_FASTMODE (0x40)
#define E_TIMEOUT -1
#define E_BADREAD -2
// 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"
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);
}
char *getdevpath(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;
}
int resetusb(char *filename) {
int fd,rv = 0;
strncpy(filename, "/dev/bus/usb/003/006", 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 = 1;
} else {
int rc;
rc = ioctl(fd, USBDEVFS_RESET, 0);
if (rc < 0) {
err("Failed to send reset, details:", filename);
perror("ioctl()");
rv = 1;
} else {
info("Reset successful");
rv = 0;
}
close(fd);
}
return rv;
}
void info( char* format, ... ) {
va_list args;
va_start(args, format);
colprintf( "*", CYAN, format, &args);
va_end( args );
}
void vinfo( char* format, ... ) {
va_list args;
if (!verbose) return;
va_start(args, format);
colprintf( "*", CYAN, format, &args);
va_end( args );
}
void err( char* format, ... ) {
va_list args;
va_start(args, format);
colprintf( "ERROR:", RED, 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 hextoint(char *hex) {
int base=16;
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;
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 (thisbytes > 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 0;
}
void dooutput(uint8_t *buf) {
double 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";
char range[BUFLENSMALL];
char dbunits[BUFLENSMALL];
char checkmode[BUFLENSMALL];
decibels = getdecibels(buf);
flags = buf[2];
rangeidx = buf[2] & 0xf;
now = time(NULL);
strncpy(dbunits, flags & FLAG_DBCMODE ? "dBC" : "dBA", BUFLENSMALL);
strncpy(checkmode, flags & FLAG_FASTMODE ? "Fast" : "Slow", BUFLENSMALL);
strncpy(range, rangeidx > 0x4 ? "unknown" : rangestr[rangeidx], BUFLENSMALL);
if (outmode & OM_LONG) {
printf(longformat, "Time", now);
printf(dbformat, "Level", decibels, dbunits);
printf(strformat, "Mode", checkmode);
printf(strformat, "Range", range);
}
if (outmode & OM_SHORT) {
printf("time=[%ld] level=[%4.2f %s] mode=[%s] range=[%s]\n",
now, decibels, dbunits, checkmode, range);
}
if (outmode & OM_DB) {
influx_write_decibels(decibels);
}
}
int tcpconnect(char *hname, int port) {
int sockfd;
struct sockaddr_in servaddr;
struct hostent *hptr;
fd_set fdset;
struct timeval tv;
int flags;
hptr = resolve(hname);
if (!hptr) return -1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
err("socket");
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));
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 -1;
}
}
} else {
perror("tcpconnect()");
return -1;
}
}
flags = fcntl(sockfd, F_GETFL);
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
return sockfd;
}
int dohttp(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;
}
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;
}
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;
}
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_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 = dohttp(influxhost, 8086, header, newcmd, retbuf);
if (rc != 204) {
err("Influx write to %s:8086 db %s failed (HTTP %d)",influxhost,influxdb,rc);
return TRUE;
}
return FALSE;
}
int influx_insert(char *cmd, char *retbuf) {
return influx_cmd(I_WRITE, cmd, retbuf);
}
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) {
vinfo("Ping success");
}
return rv;
}
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 {
info("Would have done: %s", cmd);
}
return TRUE;
}
double getdecibels(uint8_t *buf) {
if (!buf) return -1;
if (strlen((char *)buf) < 2) return -1;
return (buf[0] << 8 | buf[1])/10;
}
void handle_signal(int signum) {
exit(1);
}
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);
hid_exit();
}
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],path[BUFLEN];
int i, finished = 0;
int interval = 0;
int useinflux = FALSE;
enum mode_enum { M_PROBE, M_TEST, M_TESTDB } mode = M_PROBE;
struct timeval seed;
gettimeofday(&seed, NULL);
// generate magic id in bytes 2-4
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;
}
printf("cap cmd is:\n");
for (i = 0; i < 8; i++) {
printf(" 0x%01x\n",capture_cmd[i]);
}
printf("\n");
// handle args
for (i=1; i<argc;i++) {
if (!strcmp(argv[i], "-T")) {
mode = M_TESTDB;
} else if (!strcmp(argv[i], "-t")) {
mode = M_TEST;
} else if (!strcmp(argv[i], "-v")) {
verbose = TRUE;
} else if (!strcmp(argv[i], "-o")) {
i++;
if (i < argc && argv[i]) {
if (!strcmp(argv[i], "db")) {
outmode |= OM_DB;
} else if (!strcmp(argv[i], "short")) {
outmode |= OM_SHORT;
} else if (!strcmp(argv[i], "long")) {
outmode |= OM_LONG;
} else {
err("Invalid output mode '%s'", argv[i]);
exit(1);
}
} else {
err("outputmode not provided\n");
exit(1);
}
} else if (!strcmp(argv[i], "-i")) {
i++;
if (i < argc && argv[i]) {
interval = atoi(argv[i]);
if (interval < 2) {
err("Wait interval must be >= 2\n");
exit(1);
}
} else {
err("Wait interval not provided\n");
exit(1);
}
} else if (!strcmp(argv[i], "-y")) {
doit = TRUE;
} else if (!strcmp(argv[i], "-H")) {
i++;
if (i < argc && argv[i]) {
influxhost = strdup(argv[i]);
} else {
err("influxdb host not provided\n");
exit(1);
}
} else if (!strcmp(argv[i], "-d")) {
i++;
if (i < argc && argv[i]) {
influxdb = strdup(argv[i]);
} else {
err("influxdb dbname not provided\n");
exit(1);
}
} else if (!strcmp(argv[i], "-u")) {
i++;
if (i < argc && argv[i]) {
influxuser = strdup(argv[i]);
} else {
err("influxdb username not provided\n");
exit(1);
}
} else if (!strcmp(argv[i], "-p")) {
i++;
if (i < argc && argv[i]) {
influxpass = strdup(argv[i]);
} else {
err("influxdb password not provided\n");
exit(1);
}
} else {
err("Invalid argument '%s'\n", argv[i]);
exit(1);
}
}
if (atexit(cleanup)) {
perror("atexit()");
exit(1);
}
signal(SIGINT, handle_signal);
if (!outmode) {
outmode = OM_SHORT;
}
if (outmode & OM_DB) {
useinflux = TRUE;
} else if (mode == M_TESTDB) {
useinflux = TRUE;
} else {
useinflux = FALSE;
}
if (useinflux) {
if (influx_init(influxhost, influxdb, influxuser, influxpass)) {
exit(1);
}
if (mode == M_TESTDB) {
verbose = TRUE;
}
vinfo("Running influxdb http PING...");
if (influx_ping(retbuf)) {
err("Couldn't acess influxdb at host=%s:8086 db=%s",influxhost,influxdb);
exit(1);
}
if (mode == M_TESTDB) {
exit(0);
}
}
if (hid_init()) {
perror("hid_init()");
err("Failed to initialise hidapi.");
exit(1);
}
dev = hid_open(hextoint(MIC_VID), hextoint(MIC_PID), NULL);
if (mode == M_TEST) {
verbose = TRUE;
}
if (dev) {
vinfo("Found Benestar USB sound meter (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID);
getdevpath(hextoint(MIC_VID), hextoint(MIC_PID), path);
vinfo("Full device path is: %s", path);
}
if (!dev) {
err("Benestar USB sound meter not found (vendor 0x%04hx, product 0x%04hx).", MIC_VID, MIC_PID);
exit(1);
}
if (mode == M_TEST) {
exit(0);
}
while (!finished) {
int rv = 0,donereading = 0,ntimeouts = 0;;
vinfo("Asking for status");
// get current reading
hid_write(dev, capture_cmd, 9);
// wait for response
vinfo("Waiting for response");
while (!donereading) {
// see: https://github.com/pvachon/gm1356/blob/master/splread.c
rv = readresult(dev, buf);
if (rv) {
// failure
ntimeouts++;
if (ntimeouts >= 3) {
// give up for this time
donereading = 1;
} else {
if (ntimeouts == 2) {
// after first retry, try resetting the usb device
resetusb(path);
}
// try again
hid_write(dev, capture_cmd, 9);
}
} else {
// success
dooutput(buf);
donereading = 1;
}
}
if (interval) {
sleep(interval);
} else {
finished = 1;
}
}
/*
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
*/
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();
*/