/* Metageek WiSPY LibUSB interface 
 *
 * LibUSB userspace control for the Metageek WiSPY spectrum analyzer
 * http://www.metageek.net/
 *
 * Version 0.1 - demo/PoC
 * Mike Kershaw/Dragorn <dragorn@kismetwireless.net>
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Extra thanks to Ryan Woodings @ Metageek for interface documentation
 */

#include <stdio.h>
#include <usb.h>
#include <curses.h>

/* First-gen firmware */
#define METAGEEK_WISPY1_VID	0x04b4
#define METAGEEK_WISPY1_PID	0x0bad

/* Second-gen firmware */
#define METAGEEK_WISPY2_VID	0x1781
#define METAGEEK_WISPY2_PID	0x083e

#define TIMEOUT 3000

/* Hid get report from spec */
#define HID_GET_REPORT 0x01
#define HID_RT_FEATURE 0x03

/* maximum number of samples */
#define WISPY_SAMPLES 85
#define WISPY_MINSIG -65

/* Basic device initialize, grabs either version 1 or version 2 */
static struct usb_device *init_wispy_device() {
	struct usb_bus *bus;
	struct usb_device *dev;

	/* Libusb init */
	usb_init();
	usb_find_busses();
	usb_find_devices();

	/* Scan all the devices on all the busses */
	for (bus = usb_busses; bus; bus = bus->next) {
		for (dev = bus->devices; dev; dev = dev->next) {
			if (((dev->descriptor.idVendor == METAGEEK_WISPY1_VID) &&
				 (dev->descriptor.idProduct == METAGEEK_WISPY1_PID)) ||
				((dev->descriptor.idVendor == METAGEEK_WISPY2_VID) &&
				 (dev->descriptor.idProduct == METAGEEK_WISPY2_PID)))
				return dev;
		}
	}

	return NULL;
}

int main(int argc, char *argv[]) {
	struct usb_device *dev;
	struct usb_dev_handle *wispy;
	int arg;
	char buf[8];
	int x, cursesdraw;
	struct timeval tm;

	/* previous samples */
	int lastsignal[WISPY_SAMPLES];
	int peaksignal[WISPY_SAMPLES];

	/* curses window */
	WINDOW *window;

	memset(buf, 0, sizeof(char) * 8);
	memset(lastsignal, 0, sizeof(int) * WISPY_SAMPLES);
	memset(peaksignal, 0, sizeof(int) * WISPY_SAMPLES);

	dev = init_wispy_device();
	if (!dev) {
		printf("Unable to find device.\n");
		return 1;
	}

	/* Open the wispy device */
	wispy = usb_open(dev);
	if (!wispy) {
		printf("Unable to open device\n");
		return 1;
	}

	/* Grab it like a keyboard interface, interface 0 */
	if (usb_claim_interface(wispy, 0) < 0) {
		printf("Unable to claim interface 0.\n");
		return 1;
	}

	initscr();
	cbreak();
	noecho();

	if (COLS < 80) {
		printf("Terminal must be at least 80 characters wide\n");
		return 1;
	}

	window = subwin(stdscr, LINES, COLS, 0, 0);
	box(window, 0, 0);

	wrefresh(window);
	refresh();

	cursesdraw = 0;
	while (1) {
		/* Grab a report */
		if (usb_control_msg(wispy, USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
							HID_GET_REPORT, (HID_RT_FEATURE << 8),
							0, buf, 8, TIMEOUT) == 0) {
			printf("Failure in control_msg\n");
			return 1;
		}

		/* put it into the sample records */
		for (x = 1; x < 8; x++) {
			float dbm = ((float) buf[x] * 0.95f) - 61;	
			int pos = buf[0] + x - 1;

			lastsignal[pos] = (int) dbm;
			if ((int) dbm > peaksignal[pos] || peaksignal[pos] == 0) {
				peaksignal[pos] = (int) dbm;
			}
		}

		tm.tv_sec = 0;
		tm.tv_usec = 80;

		select(0, NULL, NULL, NULL, &tm);

		/* draw the interface */
		cursesdraw++;
		if (cursesdraw > 5) {
			int zed;
			
			for (zed = 0; zed < (LINES - 2); zed++) {
				char line[WISPY_SAMPLES];

				memset(line, ' ', WISPY_SAMPLES);

				/* modify the line */
				for (x = 0; x < WISPY_SAMPLES; x++) {
					float graphperc;

					graphperc = (LINES - 2) * (float) ((float) abs(peaksignal[x]) / 
													   (float) abs(WISPY_MINSIG));
					if (graphperc <= zed && graphperc != 0)
						line[x] = ':';

					graphperc = (LINES - 2) * (float) ((float) abs(lastsignal[x]) / 
													   (float) abs(WISPY_MINSIG));
					if (graphperc <= zed && graphperc != 0)
						line[x] = '*';
				}

				mvwaddstr(window, zed + 1, 1, line);
			}
			
			cursesdraw = 0;
			wrefresh(window);
			refresh();
		}
	}

	usb_close(wispy);

	return 0;
}	

