/*
 * W2000A Screenshot Utility
 */

#define REVMAJ		0
#define REVMIN		97
#define MIN_DSO_FW	220
#define MAX_DSO_FW_DT	500

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#ifndef WIN32
#include <fcntl.h>
#include <err.h>
#include <termios.h>
#include <time.h>
#else
#include <windows.h>
#include "ComTools.h"
#endif

typedef struct {
	const char *s;
	int d;
} RANGE;
typedef RANGE TIMEB;

typedef struct {
	unsigned char version;
	char type;
	char mode;
	int n;
	char slot_str[3][16];
	char slot_desc[3][16];
	char slot_dim[3][16];
	char slot_unit[3][16];
	int slot_intbd[3];
	int slot_intad[3];
} MEASDATA;

static void usage(void);
static void set_serial(int);
static int open_serial(char *);
static int rdchar(int);
static int wrchar(int, char);
static int p_numch(unsigned char *);
static int p_numchact(unsigned char *);
static int p_numsamp(unsigned char *);
static int p_chstatus(unsigned char *, int);
static RANGE p_chrange(unsigned char *, int);
static const char *p_chcoup(unsigned char *, int);
static int p_chbwlim(unsigned char *, int);
static int p_chdelay(unsigned char *, int);
static const char *p_adcsetup(unsigned char *);
static const char *p_pregain(unsigned char *);
static TIMEB p_timebase(unsigned char *);
static int p_chvzero(unsigned char *, int);
static int p_chscalef(unsigned char *, int);
static int p_pretriggeridx(unsigned char *);
static void out_meas_delimiter(FILE *, MEASDATA, char);
static void out_trace_delimiter(FILE *, unsigned char *, unsigned char *, char);
static void retrieve(int, int);
static void receive_meas(int, const char *, void (*)(FILE *, MEASDATA, char), char);
static void receive_trace(int, const char *, void (*)(FILE *, unsigned char *, unsigned char *, char), char);
static void receive_screen(int, int);
static void write_screen(int, int);
static time_t gettime(void);
static void showversion(char *);
static int dsoparsefw(char *);
static int dsoinfo(int);

enum {
	SCREEN_COLOUR = 0,
	SCREEN_MONO = 1,
	TRACE = 2,
	MEAS = 3,
	UNDEF = -1
};

static const int planecolours[][3] = {
			/* R     G     B */
/* UI_Plane1 */		{ 0xA0, 0xA0, 0xA0 }, /* light gray */
/* UI_Plane5 */		{ 0x7E, 0x7E, 0x7E }, /* gray */
/* UI_Plane2 */		{ 0x00, 0x00, 0x00 }, /* black */
/* UI_Plane4 */		{ 0xE6, 0xE6, 0x83 }, /* beige-ish yellow */
/* UI_Plane3 */		{ 0xFF, 0xFD, 0xEE }, /* yellow tinted white */
/* Channel_Plane1 */	{ 0xFF, 0xFF, 0x00 }, /* yellow */
/* Channel_Plane2 */	{ 0x00, 0xFF, 0x00 }, /* light green */
/* Channel_Plane3 */	{ 0x32, 0xAB, 0xFF }, /* light blue */
/* Channel_Plane4 */	{ 0xFF, 0x00, 0x00 }, /* red */
/* Channel_Math_Plane */{ 0xFF, 0x96, 0xF6 }, /* pink */
/* Marker_Plane1 */	{ 0xFF, 0x63, 0x47 }, /* tomato */
/* Marker_Plane2 */	{ 0xFF, 0x63, 0x47 }, /* tomato */
/* Grid_Plane */	{ 0x3F, 0x3F, 0x3F }, /* dark gray */
/* Background */	{ 0x00, 0x00, 0x00 }, /* black; don't remove this! */
/* B/W Screenshot */	{ 0xFF, 0xFF, 0xFF }, /* white; don't remove this! */
};

static const RANGE chranges[] = {
	{ "0", 0 },
	{ "1mV/div", 1 },
	{ "2mV/div", 2 },
	{ "5mV/div", 5 },
	{ "10mV/div", 10 },
	{ "20mV/div", 20 },
	{ "50mV/div", 50 },
	{ "100mV/div", 100 },
	{ "200mV/div", 200 },
	{ "500mV/div", 500 },
	{ "1V/div", 1000 },
	{ "2V/div", 2000 },
	{ "5V/div", 5000 },
	{ "10V/div", 10000 },
	{ "20V/div", 20000 },
	{ "50V/div", 50000 },
	{ "100V/div", 100000 } 
};

static const char *chcoup[] = {
	"GND",
	"AC",
	"DC"
};

static const char *adcsetup[] = {
	"factory",
	"high freq",
	"test 1",
	"test 2",
	"test 3",
	"test 4",
	"test 5"
};

static const char *pregain[] = {
	"factory",
	"gain 1.25",
	"24 ohm",
	"33 ohm",
	"add on"
};

#define TIMEB_4K_THRESH		50E06
static const TIMEB timebase[] = {
	{ "0", 0 },
	{ "1GSa/s", 1E09 },	/* 0 */
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },	/* 5 */
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },
	{ "1GSa/s", 1E09 },
	{ "500MSa/s", 250E6 },
	{ "250MSa/s", 250E6 },	/* 10 */
	{ "25MSa/s", 25E06 },
	{ "10MSa/s", 10E06 },
	{ "5MSa/s", 5E06 },
	{ "2.5MSa/s",25E05 },
	{ "1MSa/s", 1E06 },	/* 15 */
	{ "500KSa/s", 500E03 },
	{ "250KSa/s", 250E03 },
	{ "100KSa/s", 100E03 },
	{ "50KSa/s", 50000 },
	{ "25KSa/s", 25000 },	/* 20 */
	{ "10KSa/s", 10000 },
	{ "5KSa/s", 5000 },
	{ "2.5KSa/s", 2500 },
	{ "1KSa/s", 1000 },
	{ "500Sa/s", 500 }
};
static const TIMEB timebase_def = { "USTB", 1 };

static unsigned char buffer[640*480/2];
static char fnamprefix[256];
static int fnum;
static time_t last_t, now_t;
static int iflag, bflag, aflag;
static char tracetype[16];
static char tracepara[16];
static int dso_fw;

const static char *dso_model_str = "Model: ";
const static char *dso_fw_str = "FW: ";
const static char *dso_hw_str = "HW: ";


#ifdef WIN32
static void
err(int e, const char *fmt, ...)
{

	/*
	 * Don't even know if win32 has sth. like vsnprintf(),
	 * so we don't care.
	 */
	(void)fprintf(stderr, "Error: %s, exiting.\n", fmt);
	exit(e);
}

static void
errx(int e, const char *fmt, ...)
{

	err(e, fmt);
}

#define warnx	printf
#endif

static void
usage(void)
{

	(void)fprintf(stderr,
			"Usage:\n"
#ifdef WIN32
			"w2000a-screenshot.exe [-v] [-h] [-c n] [-f prefix] [-n num] [-smd [-b] [-t type]] [-p opt] [-o] [-i] [-a]\n"
#else
			"w2000a-screenshot [-v] [-h] [-c dev] [-f prefix] [-n num] [-smdq [-b] [-t type]] [-p opt] [-o] [-i] [-a]\n"
#endif
			" -v\t\tversion information\n"
			" -h\t\tthis help\n"
			"\n"
			"Most common settings:\n"
#ifdef WIN32
			" -c n\t\tCOM port (0 < n < 10)\n"
#else
			" -c dev\t\tserial device, e.g. /dev/ttyS0\n"
#endif
			" -f prefix\toutput file prefix\n"
			" -n num\t\tdefine start of file numbering\n"
			"\n"
			"Remote triggering of dump:\n"
			" -s\t\tinitiate standard screenshot (colour dump)\n"
			" -m\t\tinitiate monochrome dump\n"
			" -b\t\twrite as BMP (default is PPM)\n"
			" -d\t\tinitiate trace dump\n"
			" -q\t\tinitiate measurement dump\n"
			" -t type\tspecify trace and measurement file type, default: csv, valid: csv, ascii\n"
			"\n"
			"Misc:\n"
			" -o\t\tone-shot, do not wait for more dumps (set on [-smd])\n"
			" -i\t\tinvert colours (most useful with -m for printer output)\n"
			" -a\t\ttwo bells after each retrieval\n"
			" -p\t\t(post)process options; valid: v, m, u (Volt, mV, uV),\n"
			"   \t\t                              q (quiet, no header)\n"
			"\n"
			"Notes:\n"
#ifdef WIN32
			" - DSO communication (r/w) via specified COM port, the default for -c is 1 for COM1.\n"
#else
			" - DSO communication (r/w) via specified character device.  Default for -c is \"/dev/ttyS0\".\n"
#endif
			" - Status messages are directed to stdout.\n"
			" - Error messages are redirected to stderr and on exit errorlevel is set to non-zero.\n"
			" - Prefix defaults to \"\".\n"
			" - Screens are written to \"[prefix]screen-[num].[suf]\" with suffix ppm or bmp.\n"
			" - Trace data is written to \"[prefix]trace-[num].[suf]\" with suffix asc or csv.\n"
			" - Measurements are written to \"[prefix]meas-[num].[suf]\" with suffix asc or csv.\n");
	exit(1);
}

static void
set_serial(int com)
{

#ifdef WIN32
	(void)com; /* unused */
#else
	struct termios termios;

	if (!isatty(com))
		return;

	if (tcgetattr(com, &termios) == -1)
		err(1, "tcgetattr() for serial port failed");
	if (cfsetspeed(&termios,B115200) == -1)
		err(1, "cfsetspeed() failed, unable to set desired speed (115200 Baud)");

	cfmakeraw(&termios);
	termios.c_cc[VTIME]=150;
	termios.c_cc[VMIN]=1;
	if (tcsetattr(com, TCSANOW, &termios) == -1)
		err(1, "tcsetattr() failed, unable to set line attributes");
#endif
}

static int
open_serial(char *device)
{
	int com;

#ifdef WIN32
	if (device == NULL)
		com = 1;
	else
		com = *device - '0';
	if (!ComOpen(com - 1, 115200, P_NONE, S_1BIT, D_8BIT))
		err(1, "Unable to open COM port");
#else
	if (device == NULL)
		device = (char *)"/dev/ttyS0";
	if ((com = open(device, O_RDWR)) == -1)
		err(1, "open(%s) failed", device);
#endif

	return com;
}

static int
rdchar(int com)
{
#ifdef WIN32
	while (ComGetReadCount(com - 1) < 1)
		Sleep(1);
	return ComRead(com - 1);
#else
	unsigned char c;

	if (read(com, &c, 1) > 0)
		return c;
	return -1;
#endif
}

static int
wrchar(int com, char c)
{

#ifdef WIN32
	return ComWrite(com - 1, c);
#else
	return write(com, &c, 1);
#endif
}

static int
p_numch(unsigned char *para)
{

	return para[0];
}

static int
p_numchact(unsigned char *para)
{
	int i, n;

	n = 0;
	for (i = 0; i < p_numch(para); i++)
		if (p_chstatus(para, i))
			n++;

	return n;
}

static int
p_numsamp(unsigned char *para)
{

	if (p_timebase(para).d <= TIMEB_4K_THRESH)
		return 4096;
	return 16384;
}

static int
p_chstatus(unsigned char *para, int n)
{

	return para[1+n];
}

static RANGE
p_chrange(unsigned char *para, int n)
{

	if (!p_chstatus(para, n))
		return chranges[0];
	return chranges[para[5+n]+1];
}

static const char *
p_chcoup(unsigned char *para, int n)
{

	if (!p_chstatus(para, n))
		return "n/a";
	return chcoup[para[9+n]];
}

static int
p_chbwlim(unsigned char *para, int n)
{

	return para[13+n];
}

static int
p_chdelay(unsigned char *para, int n)
{

	return para[17+n];
}

static const char *
p_adcsetup(unsigned char *para)
{

	return adcsetup[para[21]];
}

static const char *
p_pregain(unsigned char *para)
{

	return pregain[para[22]];
}

static TIMEB
p_timebase(unsigned char *para)
{

	if (para[23] > 25)
		return timebase_def;
	return timebase[para[23]+1];
}

static int
p_chvzero(unsigned char *para, int n)
{


	return para[24+n*2]*256 + para[25+n*2] - 1000;
}

static int
p_chscalef(unsigned char *para, int n)
{


	return para[32+n*2]*256 + para[33+n*2];
}

static int
p_pretriggeridx(unsigned char *para)
{

	return para[40]*256 + para[41];
}

static void
out_trace_delimiter(FILE *fp, unsigned char *data, unsigned char *para, char del)
{
	int i, j;
	double rescalef;
	int chlist[5];

	#define CAP(s);		{ (void)fprintf(fp, "%s=%c", s, del); }
	#define NL()		{ (void)fprintf(fp, "\n"); }
	#define FOREACH(x, y)	{ for (i = 0; i < y; i++) { if (i > 0 && i < y ) (void)fprintf(fp, "%c", del); x } }
	#define OUTS(s)		{ (void)fprintf(fp, "%s", s); }
	#define OUTD(d)		{ (void)fprintf(fp, "%d", d); }
	#define OUTF(f)		{ if (rescalef < 1000) (void)fprintf(fp, "%d", (int)round(f)); else (void)fprintf(fp, "%f", f); }

	rescalef = 0;
	if (strchr(tracepara, 'v') != NULL)
		rescalef = 1000.0;
	else if (strchr(tracepara, 'm') != NULL)
		rescalef = 1.0;
	else if (strchr(tracepara, 'u') != NULL)
		rescalef = 0.001;

	j = 0;
	for (i = 0; i < p_numch(para); i++) {
		if (p_chstatus(para, i)) {
			chlist[j] = i;
			j++;
		}
	}
	chlist[j] = -1;

	/*
	 * Header
	 */
	if (strchr(tracepara, 'q') == NULL) {
		(void)fprintf(fp, "[header]\n");

		CAP("number_of_channels");
		OUTD(p_numch(para));
		NL();

		CAP("number_of_active_channels");
		OUTD(p_numchact(para));
		NL();

		CAP("channel_status");
		FOREACH(OUTS(p_chstatus(para, i) == 1 ? "on" : "off"), p_numch(para));
		NL();

		CAP("channel_ranges_str");
		FOREACH(OUTS(p_chrange(para, i).s), p_numchact(para));
		NL();
		CAP("channel_ranges_num");
		FOREACH(OUTD(p_chrange(para, i).d), p_numchact(para));
		NL();

		CAP("channel_couplings");
		FOREACH(OUTS(p_chcoup(para, i)), p_numchact(para));
		NL();

		CAP("channel_bandwith_limits");
		FOREACH(OUTS(p_chbwlim(para, i) == 1 ? "on" : "off"), p_numchact(para));
		NL();

		CAP("channel_delays_ns");
		FOREACH(OUTD(p_chdelay(para, i)), p_numchact(para));
		NL();

		CAP("channel_vzero_levels");
		FOREACH(OUTD(p_chvzero(para, i)), p_numchact(para));
		NL();

		CAP("channel_scale_factors");
		FOREACH(OUTD(p_chscalef(para, i)), p_numchact(para));
		NL();

		CAP("ADC_setup");
		OUTS(p_adcsetup(para));
		NL();

		CAP("pre_gain");
		OUTS(p_pregain(para));
		NL();

		CAP("timebase_str");
		OUTS(p_timebase(para).s);
		NL();
		CAP("timebase_num");
		OUTD(p_timebase(para).d);
		NL();

		CAP("number_of_samples");
		OUTD(p_numsamp(para));
		NL();

		CAP("pre_trigger_index");
		OUTD(p_pretriggeridx(para));
		NL();

		CAP("values");
		i = 0;
		while (chlist[i] != -1) {
			if (i != 0)
				(void)fprintf(fp, "%c", del);
			(void)fprintf(fp, "Ch%d", chlist[i] + 1);
			i++;
		}
		NL();
		NL();
	}

	if (strchr(tracepara, 'Q') == NULL) {
		/*
		 * Data
		 */
		if (strchr(tracepara, 'q') == NULL)
			(void)fprintf(fp, "[data]\n");

		for (j = 0; j < p_numsamp(para); j++) {
			if (rescalef != 0) {
				/*
				 * (((X - 128) * Factor - VirtualZero) / 50px) * -1.0 (invert) * Range
				 */
				FOREACH(OUTF( ( ( (data[j*p_numchact(para)+i] - 128)		/* X - 256/2 */
					          * (p_chscalef(para, chlist[i]) / 1000.0)	/* Factor */
					          - p_chvzero(para, chlist[i]))			/* VirtualZero */
					       / 50.0 )						/* 50px/div */
					     * -1						/* invert */
					     * p_chrange(para, chlist[i]).d			/* Range */
					     / rescalef)					/* rescale to uV, V, ... */
					, p_numchact(para));
			} else {
				FOREACH(OUTD(255 - data[j*p_numchact(para)+i]), p_numchact(para));
			}
			NL();
		}
	}
}

static void
out_meas_delimiter(FILE *fp, MEASDATA m, char del)
{
	int i;

	#define CAP(s);		{ (void)fprintf(fp, "%s=%c", s, del); }
	#define NL()		{ (void)fprintf(fp, "\n"); }
	#define FOREACH(x, y)	{ for (i = 0; i < y; i++) { if (i > 0 && i < y ) (void)fprintf(fp, "%c", del); x } }
	#define OUTS(s)		{ (void)fprintf(fp, "%s", s); }
	#define OUTD(d)		{ (void)fprintf(fp, "%d", d); }

	(void)fprintf(fp, "[measurements]\n");

	CAP("str");
	FOREACH(OUTS(m.slot_str[i]), m.n);
	NL();

	CAP("desc");
	FOREACH(OUTS(m.slot_desc[i]), m.n);
	NL();

	CAP("dimension");
	FOREACH(OUTS(m.slot_dim[i]), m.n);
	NL();

	CAP("unit");
	FOREACH(OUTS(m.slot_unit[i]), m.n);
	NL();

	CAP("value_bd");
	FOREACH(OUTD(m.slot_intbd[i]), m.n);
	NL();

	CAP("value_ad");
	FOREACH(OUTD(m.slot_intad[i]), m.n);
	NL();

	CAP("value_float");
	FOREACH(OUTD(m.slot_intbd[i]) OUTS(".") OUTD(m.slot_intad[i]), m.n);
	NL();
}

/*
 * Receive measurement data and hand out to specific write function
 */
static void
receive_meas(int com, const char *ftype, void out_fun(FILE *, MEASDATA, char), char del)
{
	FILE *fp;
	MEASDATA m;
	int i, j;
	int c;
	char fnam[256];

	(void)printf("  - Receiving measurements...");
	(void)fflush(stdout);

	(void)memset(&m, '\0', sizeof(MEASDATA));

	/*
	 * Version, Type, Mode, and # of slots
	 */
	if ((m.version = rdchar(com)) > 1)
		errx(1, "measurement data format newer on DSO than known here in receive_meas()");
	(void)printf(" (ver %d)", m.version);
	(void)fflush(stdout);

	m.type = rdchar(com);
	(void)printf(" (type '%c',", m.type);
	(void)fflush(stdout);

	m.mode = rdchar(com);
	(void)printf(" mode '%c',", m.mode);
	(void)fflush(stdout);

	m.n = rdchar(com);
	(void)printf(" slots %d)\n", m.n);
	(void)fflush(stdout);

	if (m.type == '0') {
		(void)printf("    (empty slots, no measurements to dump)\n");
		return;
	}

	/*
	 * Data
	 */
	#define NEXT_STR(x)	{\
					j = 0; \
					while ((c = rdchar(com)) > 0x01) { \
						if (j < 16 - 1) { \
							if (c >= '[' && c <= '^') \
								c = c - '[' + '1'; \
							else if (c == '@') \
								c = 'd'; \
							else if (c == '&') \
								c = 'u'; \
							(x)[j] = c; \
						} \
						j++; \
					} \
				}
	#define NEXT_UINT16(x)	{\
					x = rdchar(com) * 256; \
					x += rdchar(com); \
				}

	for (i = 0; i < m.n; i++) {
		NEXT_STR(m.slot_str[i]);
		NEXT_STR(m.slot_desc[i]);
		NEXT_UINT16(m.slot_intbd[i]);
		m.slot_intbd[i] -= 32768;
		NEXT_UINT16(m.slot_intad[i]);
		NEXT_STR(m.slot_dim[i]);
		NEXT_STR(m.slot_unit[i]);

		if ((c = rdchar(com)) != 0x00)
			errx(1, "measurement data format error, expected 0x00, got 0x%x in receive_meas()", c);
	}

	(void)snprintf(fnam, sizeof(fnam), "%smeas-%.4d.%s", fnamprefix, fnum, ftype);
	if ((fp = fopen(fnam, "w+")) == NULL)
		err(1, "fopen(%s, w+) failed", fnam);
	fnum++;

	(void)printf("    * Writing measurement file (%s)...", fnam);
	(void)fflush(stdout);
	out_fun(fp, m, del);

	if (fclose(fp) != 0)
		err(1, "fclose(%s) failed", fnam);

	(void)printf(" done\n");
}

/*
 * Receive trace data and hand out to specific write function (i.e., ASCII/CSV)
 */
static void
receive_trace(int com, const char *ftype, void out_fun(FILE *, unsigned char *, unsigned char *, char), char del)
{
	FILE *fp;
	unsigned char *data, *para;
	char fnam[256];
	int data_len, para_len;
	int i, c;

	(void)printf("  - Receiving trace...\n");

	(void)snprintf(fnam, sizeof(fnam), "%strace-%.4d.%s", fnamprefix, fnum, ftype);
	if ((fp = fopen(fnam, "w+")) == NULL)
		err(1, "fopen(%s, w+) failed", fnam);
	fnum++;

	/*
	 * Step 1:  Parameters
	 */
	(void)printf("    * Receiving parameters...");
	(void)fflush(stdout);

	if ((para_len = rdchar(com)) == -1)
		err(1, "unable to read from DSO in receive_trace() (at 1)");
	if (para_len < 40)
		errx(1, "implausible parameter header size received from DSO in receive_trace()");

	if ((para = (unsigned char *)malloc(sizeof(unsigned char) * para_len)) == NULL)
		err(1, "malloc() failed in receive_trace()");
	(void)memset(para, '\0', para_len);

	for (i = 0; i < para_len; i++) {
		if ((c = rdchar(com)) == -1)
			err(1, "unable to read from DSO in receive_trace() (at 2)");
		para[i] = (unsigned char)c;
	}

	if (c > 1)
		errx(1, "trace parameter format newer on DSO than known here in receive_trace()");
	(void)printf(" (ver %d) done\n", c);

	/*
	 * Step 2:  Trace data
	 */
	(void)printf("    * Receiving data...\n");

	data_len = p_numsamp(para) * p_numchact(para);
	if (data_len < 4096 || data_len > 16384*4)
		errx(1, "Internal Error:  Implausible data_len = %d calculated in receive_trace()", data_len);

	if ((data = (unsigned char *)malloc(sizeof(unsigned char) * data_len)) == NULL)
		err(1, "malloc() failed in receive_trace()");

	last_t = gettime();
	for (i = 0; i < data_len; i++) {
		if ((c = rdchar(com)) == -1)
			err(1, "unable to read from DSO in receive_trace() (at 3)");
		data[i] = (unsigned char)c;
		if (i % 100 == 0) {
			(void)printf("\r      %d bytes...", i);
			(void)fflush(stdout);
		}
	}
	now_t = gettime();
	(void)printf("\r      * Total bytes transferred: %d in %lds\n", i, now_t-last_t);

	(void)printf("    * Writing trace file (%s)...", fnam);
	(void)fflush(stdout);
	out_fun(fp, data, para, del);

	(void)free(para);
	(void)free(data);

	if (fclose(fp) != 0)
		err(1, "fclose(%s) failed", fnam);

	(void)printf(" done\n");
}

/*
 * Receive run length encoded (compressed) screen data
 */
static void
receive_screen(int com, int type)
{
	unsigned int llength;			/* RLE */
	unsigned char lastbyte, esc;
	int outcnt, incnt;
	int r;
	unsigned int bufsize;
	unsigned char c;
	unsigned int i;
	unsigned char *bufptr;

	switch (type) {
	case 0:
		bufsize=640*480/2;
		(void)printf("  - Receiving a colour screenshot");
		break;
	case 1:
		bufsize=640*480/8;
		(void)printf("  - Receiving a monochrome screenshot");
		break;
	default:
		errx(1, "Internal Error:  Unhandled dumptype in receive_screen(): %d", type);
	}

	if ((c = rdchar(com)) > 1)
		errx(1, "screenshot data version newer on DSO than known here in receive_screen()");
	(void)printf(" (ver %d)\n", c);

	incnt = outcnt = 0;
	llength = 0;
	lastbyte = esc = 0;
	bufptr = buffer;
	while (((r = rdchar(com)) >= 0) && (bufptr<buffer+bufsize)) {
		c=(unsigned char)r;
		incnt++;
		if (incnt % 100 == 0) {
			(void)printf("\r    %d bytes...", incnt);
			(void)fflush(stdout);
		}

		if (esc==2) {
			llength|=(c);					// 2nd Byte after ESC is MSB
			for (i=0;i<llength;i++) {
				*bufptr++=lastbyte;
				outcnt++;
			}
			llength=0;
			esc=0; }
		else if (esc==1) {					// 2nd ESC
			if (c==0xFF) {					// escaped 0xFF
				*bufptr++=c;
				outcnt++;
				lastbyte=c;
				llength=0;
				esc=0;					// Escape-Sequence finished
			} 
			else if (c==0xFE) {				// EOF
				(void)printf("\nEOF\n");
				bufptr--;				// remove 0x00, not necessary
				break; }
			else {						// 1st Byte after ESC is MSB
				llength=c<<8;
				esc++; }
			}
		else if (c != 0xFF) {
			*bufptr++=c;
			outcnt++;
			lastbyte=c;
		} else
			esc=1;
	}

	now_t = gettime();
	(void)printf("\r    * Total bytes: %d (transferred: %d, compression ratio: %d%%) in %lds\n", outcnt, incnt, (int)((incnt*1.0)/outcnt*100), now_t-last_t);
	last_t = now_t;
}

/*
 * Wait for and handle Screenshot/Trace
 * and optionally trigger creation of one.
 */
static void
retrieve(int com, int dumptype_req)
{
	int dumptype;
	int r;
	unsigned char c, l;

	if (dumptype_req != UNDEF) {
		/*
		 * http://sourceforge.net/apps/trac/welecw2000a/wiki/RemoteControlAPI
		 */
		(void)printf("* Triggering dump...\n");

		c = 0x02;
		if (wrchar(com, c) != 1)
			err(1, "Unable to write to COM port (at 1)");

		if (dumptype_req == SCREEN_COLOUR)
			c = 0x01;
		else if (dumptype_req == SCREEN_MONO)
			c = 0x02;
		else if (dumptype_req == TRACE)
			c = 0x03;
		else if (dumptype_req == MEAS)
			c = 0x04;
		else
			errx(1, "Internal Error:  Unhandled dumptype in retrieve() (at 1): %d", dumptype_req);

		if (wrchar(com, c) != 1)
			err(1, "Unable to write to COM port (at 2)");
	}

	(void)printf("* Waiting for screenshot, trace, or measurement...\n");

	last_t = gettime();
	l = 0;
	while ((r = rdchar(com)) >= 0) {
		c = (unsigned char)r;
#if DEBUG
		(void)printf(" %d (%c)", c, c >= 'A' && c <= 'z' ? c : '?');
		(void)fflush(stdout);
#endif
		if ((c == 0xFF) && (l == 'S' || l == 'B' || l == 'P' || l == 'C' || l == 'A'))
			break;
		l = c;
	}
	if (r < 0)
		exit(1);

	now_t = gettime();
	(void)printf("* Found Data Start Marker after %lds\n", now_t-last_t);
	last_t = now_t;

	switch (dumptype = (int)rdchar(com)) {
	case 0: /* FALLTHRU */
	case 1: /* Screenshot: 0 -> colour, 1 -> mono */
		if (l == 'B') {			/* BMP */
			receive_screen(com, dumptype);
			write_screen(1, dumptype);
		} else if (l == 'P') {		/* PPM */
			receive_screen(com, dumptype);
			write_screen(0, dumptype);
		} else if (l == 'S') { 		/* initiated via RS232 */
			receive_screen(com, dumptype);
			write_screen(bflag, dumptype);
		} else
			errx(1, "Internal Error:  Unhandled Screen Type in response from DSO in retrieve() (at 1): %c (dec %d)", l, l);
		break;
	case 2: /* FALLTHRU */
	case 3: /* Trace */
		if (l == 'C')
			receive_trace(com, "csv", out_trace_delimiter, '\t');
		else if (l == 'A')
			receive_trace(com, "asc", out_trace_delimiter, ';');
		else if (l == 'S') {		/* initiated via RS232 */
			if (strcasecmp(tracetype, "csv") == 0)
				receive_trace(com, "csv", out_trace_delimiter, '\t');
			else if (strcasecmp(tracetype, "ascii") == 0)
				receive_trace(com, "asc", out_trace_delimiter, ';');
		} else
			errx(1, "Internal Error:  Unhandled Trace Type in response from DSO in retrieve() (at 2): %c (dec %d)", l, l);
		break;
	case 4:	/* Measurements */
		if (l == 'C')
			receive_meas(com, "asc", out_meas_delimiter, '\t');
		else if (l == 'A')
			receive_meas(com, "csv", out_meas_delimiter, ';');
		else if (l == 'S') {		/* initiated via RS232 */
			if (strcasecmp(tracetype, "csv") == 0)
				receive_meas(com, "csv", out_meas_delimiter, '\t');
			else if (strcasecmp(tracetype, "ascii") == 0)
				receive_meas(com, "asc", out_meas_delimiter, ';');
		} else
			errx(1, "Internal Error:  Unhandled Measure Type in response from DSO in retrieve(): %c (dec %d)", l, l);
		break;
	default:
		errx(1, "Internal Error:  Unhandled dumptype in retrieve() (at 4): %d", dumptype);
	}

	if (aflag) {
		(void)printf("\a");
		#ifdef WIN32
		Sleep(500);
		#else
		(void)usleep(500*1000);
		#endif
		(void)printf("\a");
	}
}

/*
 * Write screen data as PPM or BPM, optionally in B/W.
 * Also optionally inverted for printout.
 */
static void
write_screen(int bmp, int bw)
{
	#define INVP(colourcomp)	iflag ? (255 - (colourcomp)) : (colourcomp)

	FILE *fp;
	char fnam[256], suf[4];
	unsigned int colour;
	unsigned int x;
	unsigned char j;
	unsigned char *bufptr;

	(void)snprintf(suf, sizeof(suf), "ppm");
	if (bmp)
		(void)snprintf(suf, sizeof(suf), "bmp");

	(void)snprintf(fnam, sizeof(fnam), "%sscreen-%.4d.%s", fnamprefix, fnum, suf);
	(void)printf("    * Writing screenshot (file: %s)...", fnam);
	if ((fp = fopen(fnam, "w+")) == NULL)
		err(1, "fopen(%s, w+) failed", fnam);
	fnum++;

	bufptr = buffer;
	if (bmp) {
		/*
		 * http://en.wikipedia.org/wiki/BMP_file_format
		 */
		/* 0x00 */ (void)fprintf(fp, "BM");
		/* 0x02 */ (void)fprintf(fp, "%c%c%c%c%c%c%c%c", 0, 0, 0, 0, 0, 0, 0, 0);
		/* 0x0A */ (void)fprintf(fp, "%c%c%c%c", 54, 0, 0, 0);		/* 40 (header size) + 0x0A + 4 */
		/* 0x0E */ (void)fprintf(fp, "%c%c%c%c", 40, 0, 0, 0);		/* header size */
		/* 0x12 */ (void)fprintf(fp, "%c%c%c%c", 128, 2, 0, 0);		/* 640px */
		/* 0x16 */ (void)fprintf(fp, "%c%c%c%c", 32, 254, 255, 255);	/* -480px */
		/* 0x1A */ (void)fprintf(fp, "%c%c", 1, 0);			/* planes */
		/* 0x1C */ (void)fprintf(fp, "%c%c", 24, 0);			/* bits per pixel */
		/* 0x1E */ (void)fprintf(fp, "%c%c%c%c", 0, 0, 0, 0);		/* no compression */
		/* 0x22 */ (void)fprintf(fp, "%c%c%c%c", 0, 16, 14, 0);		/* bitmap data size: 640*480*3=921600 Bytes */
		/* 0x26 */ (void)fprintf(fp, "%c%c%c%c", 19, 11, 0, 0);		/* pixels per meter, x */
		/* 0x2A */ (void)fprintf(fp, "%c%c%c%c", 19, 11, 0, 0);		/*                   y */
		/* 0x2E */ (void)fprintf(fp, "%c%c%c%c", 0, 0, 0, 0);		/* # of colours in palette, use default */
		/* 0x32 */ (void)fprintf(fp, "%c%c%c%c", 0, 0, 0, 0);		/* # of "important" colours, ignore */

		if (!bw) {
			for (x=0;x<640*480/2;x++) {
				colour=bufptr[x] & 0x0F;
				/* colour order BGR */
				(void)fputc(INVP(planecolours[colour][2]), fp);
				(void)fputc(INVP(planecolours[colour][1]), fp);
				(void)fputc(INVP(planecolours[colour][0]), fp);

				colour=bufptr[x] >> 4;
				/* colour order BGR */
				(void)fputc(INVP(planecolours[colour][2]), fp);
				(void)fputc(INVP(planecolours[colour][1]), fp);
				(void)fputc(INVP(planecolours[colour][0]), fp);
			}
		} else {
			for (x=0;x<640*480/8;x++) {
				for (j=0;j<8;j++) {
					if (*bufptr & (1<<j)) {
						(void)fputc(INVP(planecolours[14][2]), fp);
						(void)fputc(INVP(planecolours[14][1]), fp);
						(void)fputc(INVP(planecolours[14][0]), fp);
					} else {
						(void)fputc(INVP(planecolours[13][2]), fp);
						(void)fputc(INVP(planecolours[13][1]), fp);
						(void)fputc(INVP(planecolours[13][0]), fp);
					}
				}
				bufptr++;
			}
		}
	} else	{
		/*
		 * http://netpbm.sourceforge.net/doc/ppm.html
		 */
		(void)fprintf(fp, "P6\n640 480\n255 ");

		if (!bw) {
			for (x=0;x<640*480/2;x++) {
				colour=bufptr[x] & 0x0F;
				/* colour order RGB */
				(void)fputc(INVP(planecolours[colour][0]), fp);
				(void)fputc(INVP(planecolours[colour][1]), fp);
				(void)fputc(INVP(planecolours[colour][2]), fp);

				colour=bufptr[x] >> 4;
				/* colour order RGB */
				(void)fputc(INVP(planecolours[colour][0]), fp);
				(void)fputc(INVP(planecolours[colour][1]), fp);
				(void)fputc(INVP(planecolours[colour][2]), fp);
			}
		} else {
			for (x=0;x<640*480/8;x++) {
				for (j=0;j<8;j++) {
					if (*bufptr & (1<<j)) {
						(void)fputc(INVP(planecolours[14][0]), fp);
						(void)fputc(INVP(planecolours[14][1]), fp);
						(void)fputc(INVP(planecolours[14][2]), fp);
					} else {
						(void)fputc(INVP(planecolours[13][0]), fp);
						(void)fputc(INVP(planecolours[13][1]), fp);
						(void)fputc(INVP(planecolours[13][2]), fp);
					}
				}
				bufptr++;
			}
		}
	}

	if (fclose(fp) != 0)
		err(1, "fclose(%s) failed", fnam);

	(void)printf(" done\n");
}

static time_t
gettime(void)
{
#ifdef WIN32
	/*
	 * Yes, this is crude.  But I won't implement
	 * a decent API just for Windows.
	 */
	SYSTEMTIME st;

	GetSystemTime(&st);
	return st.wDay * 24*3600 + st.wHour * 3600 + st.wMinute * 60 + st.wSecond;
#else
	return time(NULL);
#endif
}

static void
showversion(char *name)
{

	(void)printf("This is W2000A Screenshot (%s) v%d.%d\n", name, REVMAJ, REVMIN);
	exit(0);
}

static int
dsoparsefw(char *p)
{
	int a, b, c, d;

	(void)sscanf(p, "%d.%d.%d.%d", &a, &b, &c, &d);

	return c*100+d;
}

/*
 * Queries DSO information and returns firmware version
 */
static int
dsoinfo(int com)
{
	int i, c, fwver;
	char *info, *p;

	#define ISIZE	512
	if ((info = (char *)malloc(sizeof(char) * ISIZE)) == NULL)
		err(1, "malloc() failed in dsoinfo()");
	(void)memset(info, '\0', ISIZE);

	/*
	 * http://sourceforge.net/apps/trac/welecw2000a/wiki/RemoteControlAPI
	 */
	(void)printf("* Connecting to DSO and retrieving version information...");
	(void)fflush(stdout);
	if (!(wrchar(com, 0x02) == 1 && wrchar(com, 'E') == 1 && wrchar(com, 'v') == 1))
		errx(1, "unable to query information from DSO on COM port (at 1)");
	for (i = 0; i < 7; i++)
		if (wrchar(com, ' ') != 1)
			errx(1, "unable to query information from DSO on COM port (at 2)");
	info[0] = '\0';
	i = 1;
	while (i < ISIZE-1 && (c = rdchar(com)) != '+') {
		if (c >= ' ' && c <= '~')
			info[i] = (char)c;
		else
			info[i] = '\0';
		i++;
	}
	(void)printf(" done\n");

	fwver = 0;
	p = info;
	for (i = 0; i < ISIZE-2; i++, p++)
		if (*p == '\0') {
			if (strncmp((p+1), dso_model_str, strlen(dso_model_str)) == 0)
				(void)printf("  - found model:  %s\n", p+1+strlen(dso_model_str));
			else if (strncmp((p+1), dso_fw_str, strlen(dso_fw_str)) == 0) {
				fwver = dsoparsefw(p+1+strlen(dso_fw_str));
				(void)printf("  - found firmware version:  %d.%d\n", fwver / 100, fwver % 100);
			} else if (strncmp((p+1), dso_hw_str, strlen(dso_hw_str)) == 0)
				(void)printf("  - found hardware version:  %s\n", p+1+strlen(dso_hw_str));
			else if (*(p+1) != '\0')
				(void)printf("  - unknown token:  %s\n", p+1);
		}

	(void)free(info);
	return fwver;
}

int
main(int argc, char **argv)
{
	int ch, oneshot;
	int dumptype;
	char *device;
	char *p;
	int com;

	device = NULL;
	bflag = iflag = aflag = 0;
	oneshot = 0;
	dumptype = UNDEF;
	fnum = 0;
	(void)snprintf(tracetype, sizeof(tracetype), "csv");
	*fnamprefix = '\0';
	while ((ch = getopt(argc, argv, "c:f:bhsmdqovn:it:p:a")) != -1) {
		switch (ch) {
		case 'c':
#ifdef WIN32
			device = optarg;
			if (*device - '0' < 1 || *device - '0' > 9 || strlen(device) > 1)
				errx(1, "invalid COM port given, use -c n, where 0 < n < 10");
#else
			device = optarg;
#endif
			break;
		case 'f':
			(void)snprintf(fnamprefix, sizeof(fnamprefix), "%s", optarg);
			break;
		case 'v':
			showversion(argv[0]);
			break;
		case 'd':
			dumptype = TRACE;
			break;
		case 'q':
			dumptype = MEAS;
			break;
		case 'm':
			dumptype = SCREEN_MONO;
			break;
		case 's':
			dumptype = SCREEN_COLOUR;
			break;
		case 'b':
			bflag = 1;
			break;
		case 'o':
			oneshot = 1;
			break;
		case 'n':
			fnum = atoi(optarg);
			if (fnum < 0 || fnum > 9999)
				errx(1, "invalid start of file numbering given, use -n num, where 0 <= num <= 9999");
			break;
		case 'i':
			iflag = 1;
			break;
		case 't':
			(void)snprintf(tracetype, sizeof(tracetype), "%s", optarg);
			if (strcasecmp(tracetype, "csv") != 0 && strcasecmp(tracetype, "ascii") != 0)
				errx(1, "invalid type given, valid: -t ascii and -t csv");
			break;
		case 'p':
			(void)snprintf(tracepara, sizeof(tracepara), "%s", optarg);
			p = tracepara;
			while (*p != '\0') {
				if (!(*p == 'v' || *p == 'm' || *p == 'u' || *p == 'q'))
					errx(1, "invalid parameter given, valid for -p are: v m u q");
				p++;
			}
			break;
		case 'a':
			aflag = 1;
			break;
		case 'h': /* FALLTHRU */
		default:
			usage();
			/* NOTREACHED */
		}
	}

	if (dumptype != UNDEF)
		oneshot = 1;

	com = open_serial(device);
	set_serial(com);

	dso_fw = dsoinfo(com);
	if (dso_fw < MIN_DSO_FW)
		errx(1, "This version needs at least firmware version %d.%d, but you've got %d.%d.\n"
			"Please update your DSO.", MIN_DSO_FW / 100, MIN_DSO_FW % 100, dso_fw / 100, dso_fw % 100);
	else if (dso_fw - MAX_DSO_FW_DT > MIN_DSO_FW)
		warnx(" --- WARNING:  Your DSO's firmware version is %d.%d.\n"
		      "This is > %d revision units newer than what this programm was written for.\n"
		      "Please consider checking for an update to this programm. ---", dso_fw / 100, dso_fw % 100, MAX_DSO_FW_DT);

	do {
		retrieve(com, dumptype);
	} while (!oneshot);

	return 0;
}
