From 9508a7832acabb84d91d40bfb1e0b483f9402695 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 19 Jun 2012 12:28:24 -0700 Subject: [PATCH] split into multiple source files --- .gitignore | 1 + config.c | 100 +++ config.h | 15 + globals.c | 5 + globals.h | 10 + makefile | 5 + util.c | 1554 ++++++++++++++++++++++++++++++++++++++++ util.h | 4 + wattsup.c | 1783 +--------------------------------------------- wattsup_common.h | 122 ++++ 10 files changed, 1818 insertions(+), 1781 deletions(-) create mode 100644 config.c create mode 100644 config.h create mode 100644 globals.c create mode 100644 globals.h create mode 100644 util.c create mode 100644 util.h create mode 100644 wattsup_common.h diff --git a/.gitignore b/.gitignore index 578fb66..0ff5f27 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +TAGS *~ *.o wattsup diff --git a/config.c b/config.c new file mode 100644 index 0000000..c2c525e --- /dev/null +++ b/config.c @@ -0,0 +1,100 @@ +#include "wattsup_common.h" + +const char * wu_version = "0.03"; + +const char * prog_name = "wattsup"; + +const char * sysfs_path_start = "/sys/class/tty"; + +struct wu_field wu_fields[wu_num_fields] = { + [wu_field_watts] = { + .name = "watts", + .descr = "Watt Consumption", + }, + + [wu_field_min_watts] = { + .name = "min-watts", + .descr = "Minimum Watts Consumed", + }, + + [wu_field_max_watts] = { + .name = "max-watts", + .descr = "Maxium Watts Consumed", + }, + + [wu_field_volts] = { + .name = "volts", + .descr = "Volts Consumption", + }, + + [wu_field_min_volts] = { + .name = "max-volts", + .descr = "Minimum Volts Consumed", + }, + + [wu_field_max_volts] = { + .name = "min-volts", + .descr = "Maximum Volts Consumed", + }, + + [wu_field_amps] = { + .name = "amps", + .descr = "Amp Consumption", + }, + + [wu_field_min_amps] = { + .name = "min-amps", + .descr = "Minimum Amps Consumed", + }, + + [wu_field_max_amps] = { + .name = "max-amps", + .descr = "Maximum Amps Consumed", + }, + + [wu_field_watt_hours] = { + .name = "kwh", + .descr = "Average KWH", + }, + + [wu_field_mo_kwh] = { + .name = "mo-kwh", + .descr = "Average monthly KWH", + }, + + [wu_field_cost] = { + .name = "cost", + .descr = "Cost per watt", + }, + + [wu_field_mo_cost] = { + .name = "mo-cost", + .descr = "Monthly Cost", + }, + + [wu_field_power_factor] = { + .name = "power-factor", + .descr = "Ratio of Watts vs. Volt Amps", + }, + + [wu_field_duty_cycle] = { + .name = "duty-cycle", + .descr = "Percent of the Time On vs. Time Off", + }, + + [wu_field_power_cycle] = { + .name = "power-cycle", + .descr = "Indication of power cycle", + }, + + [wu_field_frequency] = { + .name = "frequency", + .descr = "AC frequency (HZ)", + }, + + [wu_field_va] = { + .name = "VA", + .descr = "VA", + }, +}; + diff --git a/config.h b/config.h new file mode 100644 index 0000000..221fc43 --- /dev/null +++ b/config.h @@ -0,0 +1,15 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "wattsup_common.h" +#include "config.h" + +extern const char * wu_version, + *prog_name, + *sysfs_path_start; + +#define wu_num_options ARRAY_SIZE(wu_options) + +extern struct wu_field wu_fields[]; + +#endif diff --git a/globals.c b/globals.c new file mode 100644 index 0000000..31909bf --- /dev/null +++ b/globals.c @@ -0,0 +1,5 @@ +char * wu_device = "ttyUSB0"; +int wu_fd = 0; +int wu_info_all = 0; +int wu_no_data = 0; +int wu_set_only = 0; diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..5a2c868 --- /dev/null +++ b/globals.h @@ -0,0 +1,10 @@ +#ifndef GLOBALS_H +#define GLOBALS_H +extern char *wu_device; +extern int wu_fd; +extern int wu_info_all; +extern int wu_no_data; +extern int wu_set_only; + + +#endif diff --git a/makefile b/makefile index afaecd0..3383746 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,9 @@ all: wattsup +wattsup: wattsup.c util.c config.c globals.c + install: all install -m 0555 wattsup /usr/local/bin + +clean: + rm -f *~ wattsup diff --git a/util.c b/util.c new file mode 100644 index 0000000..4256b05 --- /dev/null +++ b/util.c @@ -0,0 +1,1554 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "globals.h" +#include "config.h" +#include "wattsup_common.h" + +int wu_count = 0; +int wu_debug = 0; +char *wu_delim = ", "; +int wu_final = 0; +int wu_interval = 1; +int wu_label = 0; +int wu_newline = 0; +int wu_suppress = 0; + +int wu_localtime = 0; +int wu_gmtime = 0; + + +static char * wu_option_value(unsigned int index); + +static void msg_start(const char * fmt, ...) +{ + va_list(ap); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +static void msg_end(void) +{ + printf("\n"); + fflush(stdout); +} + +static void msg(const char * fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void dbg(const char * fmt, ...) +{ + va_list ap; + + if (wu_debug) { + va_start(ap, fmt); + msg_start("%s: [debug] ", prog_name); + vprintf(fmt, ap); + msg_end(); + va_end(ap); + } +} + +static void err(const char * fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s: [error] ", prog_name); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void perr(const char * fmt, ...) +{ + char buf[1024]; + int n; + va_list ap; + + va_start(ap, fmt); + n = sprintf(buf, "%s: [error] ", prog_name); + vsnprintf(buf + n, sizeof(buf) - n, fmt, ap); + perror(buf); + va_end(ap); +} + +static int ret_err(int err) +{ + errno = err; + return -1; +} + + +static void print_packet(struct wu_packet * p, char * str) +{ + int i; + + if (!wu_suppress) + msg_start("Watts Up? %s\n", str); + for (i = 0; i < p->count; i++) { + if (i) + msg("%s", wu_newline ? "\n" : wu_delim); + if (wu_label) + msg("[%s] ", p->label[i]); + msg(p->field[i]); + } + msg_end(); +} + + +static void print_time(void) +{ + time_t t; + struct tm * tm; + + if (wu_localtime || wu_gmtime) { + time(&t); + + if (wu_localtime) + tm = localtime(&t); + else + tm = gmtime(&t); + + msg("[%02d:%02d:%02d] ", + tm->tm_hour, tm->tm_min, tm->tm_sec); + } +} + +static void print_packet_filter(struct wu_packet * p, + int (*filter_ok)(struct wu_packet * p, int i, char * str)) +{ + char buf[256]; + int printed; + int i; + + print_time(); + for (i = 0, printed = 0; i < p->count; i++) { + if (!filter_ok(p, i, buf)) + continue; + + if (printed++) + msg("%s", wu_newline ? "\n" : wu_delim); + if (wu_label) + msg("[%s] ", p->label[i]); + msg(buf); + } + msg_end(); +} + + +/* + * Device should be something like "ttyS0" + */ + +int open_device(char * device_name, int * dev_fd) +{ + struct stat s; + int ret; + int cur_fd; + + cur_fd = open(".", O_RDONLY); + if (cur_fd < 0) { + perr("Could not open current directory."); + return cur_fd; + } + + ret = chdir(sysfs_path_start); + if (ret) { + perr(sysfs_path_start); + return ret; + } + + /* + * First, check if /sys/class/tty// exists. + */ + + dbg("Checking sysfs path: %s/%s", sysfs_path_start, device_name); + + ret = stat(device_name, &s); + if (ret < 0) { + perr(device_name); + goto Done; + } + + if (!S_ISDIR(s.st_mode)) { + errno = -ENOTDIR; + err("%s is not a TTY device.", device_name); + goto Done; + } + + dbg("%s is a registered TTY device", device_name); + + fchdir(cur_fd); + + + /* + * Check if device node exists and is writable + */ + chdir("/dev"); + + ret = stat(device_name, &s); + if (ret < 0) { + perr("/dev/%s (device node)", device_name); + goto Done; + } + + if (!S_ISCHR(s.st_mode)) { + errno = -ENOTTY; + ret = -1; + err("%s is not a TTY character device.", device_name); + goto Done; + } + + dbg("%s has a device node", device_name); + + ret = access(device_name, R_OK | W_OK); + if (ret) { + perr("%s: Not writable?", device_name); + goto Done; + } + + ret = open(device_name, O_RDWR | O_NONBLOCK); + if (ret < 0) { + perr("Could not open %s"); + goto Done; + } + *dev_fd = ret; + ret = 0; +Done: + fchdir(cur_fd); + close(cur_fd); + return ret; +} + + +int setup_serial_device(int dev_fd) +{ + struct termios t; + int ret; + + ret = tcgetattr(dev_fd, &t); + if (ret) + return ret; + + cfmakeraw(&t); + cfsetispeed(&t, B115200); + cfsetospeed(&t, B115200); + tcflush(dev_fd, TCIFLUSH); + + t.c_iflag |= IGNPAR; + t.c_cflag &= ~CSTOPB; + ret = tcsetattr(dev_fd, TCSANOW, &t); + + if (ret) { + perr("setting terminal attributes"); + return ret; + } + + return 0; +} + + +static int wu_write(int fd, struct wu_packet * p) +{ + int ret; + int n; + int i; + char * s = p->buf; + + memset(p->buf, 0, sizeof(p->buf)); + n = sprintf(p->buf, "#%c,%c,%d", p->cmd, p->sub_cmd, p->count); + p->len = n; + s = p->buf + n; + + for (i = 0; i < p->count; i++) { + if ((p->len + strlen(p->field[i]) + 4) >= sizeof(p->buf)) { + err("Overflowed command string"); + return ret_err(EOVERFLOW); + } + n = sprintf(s, ",%s", p->field[i]); + s += n; + p->len += n; + } + p->buf[p->len++] = ';'; + + dbg("Writing '%s' (strlen = %d) (len = %d) to device", + p->buf, strlen(p->buf), p->len); + ret = write(fd, p->buf, p->len); + if (ret != p->len) + perr("Writing to device"); + + return ret >= 0 ? 0 : ret; +} + + +static void dump_packet(struct wu_packet * p) +{ + int i; + + dbg("Packet - Command '%c' %d parameters", p->cmd, p->count); + + for (i = 0; i < p->count; i++) + dbg("[%2d] [%20s] = \"%s\"", i, p->label[i], p->field[i]); +} + + +static int parse_packet(struct wu_packet * p) +{ + char * s, *next; + int i; + + p->buf[p->len] = '\0'; + + dbg("Parsing Packet, Raw buffer is (%d bytes) [%s]", + p->len, p->buf); + + s = p->buf; + + /* + * First character should be '#' + */ + if (s) { + s = strchr(s, '#'); + if (s) + s++; + else { + dbg("Invalid packet"); + return ret_err(EFAULT); + } + } else { + dbg("Invalid packet"); + return ret_err(EFAULT); + } + + /* + * Command character is first + */ + next = strchr(s, ','); + if (next) { + p->cmd = *s; + s = ++next; + } else { + dbg("Invalid Command field [%s]", s); + return ret_err(EFAULT); + } + + /* + * Next character is the subcommand, and should be '-' + * Though, it doesn't matter, because we just + * discard it anyway. + */ + next = strchr(s, ','); + if (next) { + p->sub_cmd = *s; + s = ++next; + } else { + dbg("Invalid 2nd field"); + return ret_err(EFAULT); + } + + /* + * Next is the number of parameters, + * which should always be > 0. + */ + next = strchr(s, ','); + if (next) { + *next++ = '\0'; + p->count = atoi(s); + s = next; + } else { + dbg("Couldn't determine number of parameters"); + return ret_err(EFAULT); + } + + dbg("Have %d parameter%s (cmd = '%c')", + p->count, p->count > 1 ? "s" : "", p->cmd); + + /* + * Now, we loop over the rest of the string, + * storing a pointer to each in p->field[]. + * + * The last character was originally a ';', but may have been + * overwritten with a '\0', so we make sure to catch + * that when converting the last parameter. + */ + for (i = 0; i < p->count; i++) { + next = strpbrk(s, ",;"); + if (next) { + *next++ = '\0'; + } else { + if (i < (p->count - 1)) { + dbg("Malformed parameter string [%s]", s); + return ret_err(EFAULT); + } + } + + /* + * Skip leading white space in fields + */ + while (isspace(*s)) + s++; + p->field[i] = s; + s = next; + } + dump_packet(p); + return 0; +} + + +static int wu_read(int fd, struct wu_packet * p) +{ + fd_set read_fd; + struct timeval tv; + int ret; + + FD_ZERO(&read_fd); + FD_SET(fd, &read_fd); + + tv.tv_sec = 2; + tv.tv_usec = 0; + + ret = select(fd + 1, &read_fd, NULL, NULL, &tv); + if (ret < 0) { + perr("select on terminal device"); + return ret; + } else if (ret > 0) { + + ret = read(fd, p->buf, wu_strlen); + if (ret < 0) { + perr("Reading from device"); + return ret; + } + p->len = ret; + } else { + dbg("Device timed out while reading"); + return ret_err(ETIME); + } + return parse_packet(p); +} + + +static int wu_show_header(int fd) +{ + struct wu_packet p = { + .cmd = 'H', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "watts header", + [1] = "volts header", + [2] = "amps header", + [3] = "kWh header", + [4] = "cost header", + [5] = "mo. kWh header", + [6] = "mo. cost header", + [7] = "max watts header", + [8] = "max volts header", + [9] = "max amps header", + [10] = "min watts header", + [11] = "min volts header", + [12] = "min amps header", + [13] = "power factor header", + [14] = "duty cycle header", + [15] = "power cycle header", + [16] = "frequency header", + [17] = "VA header" + } + }; + int ret; + + ret = wu_write(fd, &p); + if (ret) { + perr("Requesting header strings"); + return ret; + } + sleep(1); + + ret = wu_read(fd, &p); + if (ret) { + perr("Reading header strings"); + return ret; + } + + print_packet(&p, "Header Record"); + + return 0; +} + + +static int wu_show_cal(int fd) +{ + struct wu_packet p = { + .cmd = 'F', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "flags", + [1] = "sample count", + [2] = "volts gain", + [3] = "volts bias", + [4] = "amps gain", + [5] = "amps bias", + [6] = "amps offset", + [7] = "low amps gain", + [8] = "low amps bias", + [9] = "low amps offset", + [10] = "watts gain", + [11] = "watts offset", + [12] = "low watts gain", + [13] = "low watts offset", + }, + }; + int ret; + + ret = wu_write(fd, &p); + if (ret) { + perr("Requesting calibration parameters"); + return ret; + } + sleep(1); + + ret = wu_read(fd, &p); + if (ret) { + perr("Reading header strings"); + return ret; + } + print_packet(&p, "Calibration Settings"); + + return 0; +} + +int wu_start_log(void) +{ + struct wu_packet p = { + .cmd = 'L', + .sub_cmd = 'W', + .count = 3, + .field = { + [0] = "E", + [1] = "1", + [2] = "1", + }, + }; + int ret; + + /* + * Start up logging + */ + ret = wu_write(wu_fd, &p); + if (!ret) + sleep(1); + else { + perr("Starting External Logging"); + return ret; + } + return ret; +} + +int wu_stop_log(void) +{ + struct wu_packet p = { + .cmd = 'L', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "time stamp", + [1] = "interval", + }, + }; + int ret; + + /* + * Stop logging and read time stamp. + */ + ret = wu_write(wu_fd, &p); + if (ret) { + perr("Stopping External Logging"); + return ret; + } + sleep(1); + + ret = wu_read(wu_fd, &p); + if (ret) { + perr("Reading final time stamp"); + return ret; + } + if (wu_final) + print_packet(&p, "Final Time Stamp and Interval"); + return ret; +} + +static int filter_data(struct wu_packet * p, int i, char * buf) +{ + if (i < wu_num_fields) { + if (wu_fields[i].enable) { + double scale; + int digits; + switch(i){ + case wu_field_min_amps: + case wu_field_max_amps: + case wu_field_amps: + scale = 1000.0; + digits = 3; + break; + case wu_field_power_factor: + case wu_field_duty_cycle: + scale = 1.0; + digits = 1; + break; + default: + scale = 10.0; + digits = 1; + } + double val = strtod(p->field[i], NULL); + snprintf(buf, 256, "%.*lf", digits, val / scale); + return 1; + } + } + return 0; +} + +int wu_clear(int fd) +{ + struct wu_packet p = { + .cmd = 'R', + .sub_cmd = 'W', + .count = 0, + }; + int ret; + + /* + * Clear the memory + */ + ret = wu_write(fd, &p); + if (ret) + perr("Clearing memory"); + else + sleep(2); + + /* + * Dummy read + */ + wu_read(fd, &p); + return ret; + +} + +int wu_read_data(int fd) +{ + struct wu_packet p = { + .label = { + [0] = "watts", + [1] = "volts", + [2] = "amps", + [3] = "watt hours", + [4] = "cost", + [5] = "mo. kWh", + [6] = "mo. cost", + [7] = "max watts", + [8] = "max volts", + [9] = "max amps", + [10] = "min watts", + [11] = "min volts", + [12] = "min amps", + [13] = "power factor", + [14] = "duty cycle", + [15] = "power cycle", + [16] = "frequency", + [17] = "VA" + }, + }; + int num_read = 0; + int retry = 0; + int ret; + int i; + + static const int wu_max_retry = 2; + + i = 0; + while (1) { + + ret = wu_read(fd, &p); + if (ret) { + if (++retry < wu_max_retry) { + dbg("Bad record back, retrying\n"); + sleep(wu_interval); + continue; + } else if (retry == wu_max_retry) { + dbg("Still couldn't get a good record, resetting\n"); + wu_stop_log(); + wu_clear(fd); + wu_start_log(); + num_read = 0; + sleep(wu_interval); + continue; + } + perr("Blech. Giving up on read"); + break; + } else if (retry) + retry = 0; + + dbg("[%d] ", num_read); + num_read++; + print_packet_filter(&p, filter_data); + + if (wu_count && (++i == wu_count)) + break; + + sleep(wu_interval); + } + return 0; + +} + + +static int wu_show_interval(int fd) +{ + struct wu_packet p = { + .cmd = 'S', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "reserved", + [1] = "interval", + }, + }; + int ret; + + ret = wu_write(fd, &p); + if (ret) { + perr("Requesting interval"); + return ret; + } + sleep(1); + + ret = wu_read(fd, &p); + if (ret) { + perr("Reading interval"); + return ret; + } + print_packet(&p, "Interval Settings"); + + return 0; +} + +static int wu_write_interval(int fd, unsigned int seconds, + unsigned int interval) +{ + char str_seconds[wu_param_len]; + char str_interval[wu_param_len]; + struct wu_packet p = { + .cmd = 'S', + .sub_cmd = 'W', + .count = 2, + .field = { + [0] = str_seconds, + [1] = str_interval, + }, + }; + int ret; + + snprintf(str_seconds, wu_param_len, "%ud", seconds); + snprintf(str_interval, wu_param_len, "%ud", interval); + + ret = wu_write(fd, &p); + if (ret) { + perr("Setting Sampling Interval"); + return ret; + } + sleep(1); + return 0; +} + +static int wu_store_interval(int fd) +{ + char * s = wu_option_value(wu_option_interval); + char * end; + + wu_interval = strtol(s, &end, 0); + if (*end) { + err("Invalid interval: %s", s); + return ret_err(EINVAL); + } + return wu_write_interval(fd, 1, wu_interval); +} + +static int wu_show_mode(int fd) +{ + struct wu_packet p = { + .cmd = 'M', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "display mode", + }, + }; + int ret; + + ret = wu_write(fd, &p); + if (ret) { + perr("Requesting device display mode"); + return ret; + } + + ret = wu_read(fd, &p); + if (ret) { + perr("Reaing device display mode"); + return ret; + } + dump_packet(&p); + return ret; +} + +static int wu_write_mode(int fd, int mode) +{ + char str_mode[wu_param_len]; + struct wu_packet p = { + .cmd = 'M', + .sub_cmd = 'W', + .count = 1, + .field = { + [0] = str_mode, + }, + }; + int ret; + + snprintf(str_mode, wu_param_len, "%ud", mode); + ret = wu_write(fd, &p); + if (ret) + perr("Setting device display mode"); + else + sleep(1); + return ret; +} + +static int wu_store_mode(int fd) +{ + char * s = wu_option_value(wu_option_mode); + char * end; + unsigned int mode; + + mode = strtol(s, &end, 0); + if (*end) { + err("Invalid mode: %s", s); + return ret_err(EINVAL); + } + return wu_write_mode(fd, mode); +} + +static int wu_show_user(int fd) +{ + struct wu_packet p = { + .cmd = 'U', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = "cost per kWh", + [1] = "2nd tier cost", + [2] = "2nd tier threshold", + [3] = "duty cycle threshold", + }, + }; + int ret; + + ret = wu_write(fd, &p); + if (ret) { + perr("Requesting user parameters"); + return ret; + } + sleep(1); + + ret = wu_read(fd, &p); + if (ret) { + perr("Reading user parameters"); + return ret; + } + print_packet(&p, "User Settings"); + return 0; +} + +static int wu_write_user(int fd, unsigned int kwh_cost, + unsigned int second_tier_cost, + unsigned int second_tier_threshold, + unsigned int duty_cycle_threshold) +{ + char str_kwh_cost[wu_param_len]; + char str_2nd_tier_cost[wu_param_len]; + char str_2nd_tier_threshold[wu_param_len]; + char str_duty_cycle_threshold[wu_param_len]; + + struct wu_packet p = { + .cmd = 'U', + .sub_cmd = 'R', + .count = 0, + .label = { + [0] = str_kwh_cost, + [1] = str_2nd_tier_cost, + [2] = str_2nd_tier_threshold, + [3] = str_duty_cycle_threshold, + }, + }; + int ret; + + snprintf(str_kwh_cost, wu_param_len, "%ud", kwh_cost); + snprintf(str_2nd_tier_cost, wu_param_len, "%ud", + second_tier_cost); + snprintf(str_2nd_tier_threshold, wu_param_len, "%ud", + second_tier_threshold); + snprintf(str_duty_cycle_threshold, wu_param_len, "%ud", + duty_cycle_threshold); + + ret = wu_write(fd, &p); + if (ret) + perr("Writing user parameters"); + else + sleep(1); + return ret; +} + +static int wu_store_user(int fd) +{ + unsigned int kwh_cost; + unsigned int second_tier_cost; + unsigned int second_tier_threshold; + unsigned int duty_cycle_threshold; + char * buf = wu_option_value(wu_option_user); + char * s = buf; + char * next; + + if (!buf) { + err("No user parameters?"); + return ret_err(EINVAL); + } + + kwh_cost = strtoul(s, &next, 0); + if (next == s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + s = next; + while (s && !isdigit(*s)) + s++; + if (!s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + + second_tier_cost = strtoul(s, &next, 0); + if (next == s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + s = next; + while (s && !isdigit(*s)) + s++; + if (!s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + + second_tier_threshold = strtoul(s, &next, 0); + if (next == s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + s = next; + while (s && !isdigit(*s)) + s++; + if (!s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + + duty_cycle_threshold = strtoul(s, &next, 0); + if (next == s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + s = next; + while (s && !isdigit(*s)) + s++; + if (!s) { + err("Incomplete user parameters"); + return ret_err(EINVAL); + } + + return wu_write_user(fd, kwh_cost, second_tier_cost, + second_tier_threshold, duty_cycle_threshold); +} + + +static void enable_field(char * name) +{ + int i; + + for (i = 0; i < wu_num_fields; i++) { + if (!strcasecmp(wu_fields[i].name, name)) { + wu_fields[i].enable = 1; + break; + } + } +} + +static void enable_all_fields(void) +{ + int i; + + for (i = 0; i < wu_num_fields; i++) + wu_fields[i].enable = 1; +} + + + +static int wu_show_help(int); +static int wu_show_version(int); + + +static int wu_store_count(int unused) +{ + char * s = wu_option_value(wu_option_count); + char * end; + + if (s) { + wu_count = strtol(s, &end, 0); + if (*end) { + err("Bad count field"); + return ret_err(EINVAL); + } + } + return 0; +} + +static int wu_store_debug(int unused) +{ + wu_debug = 1; + return 0; +} + +static int wu_store_delim(int unused) +{ + char * s = wu_option_value(wu_option_delim); + + if (s) + wu_delim = s; + return 0; +} + +static int wu_store_final(int unused) +{ + wu_final = 1; + return 0; +} + +static int wu_store_label(int unused) +{ + wu_label = 1; + return 0; +} + +static int wu_store_newline(int unused) +{ + wu_newline = 1; + return 0; +} + +static int wu_store_suppress(int unused) +{ + wu_suppress = 1; + return 0; +} + +static int wu_store_localtime(int unused) +{ + wu_localtime = 1; + return 0; +} + +static int wu_store_gmtime(int unused) +{ + wu_gmtime = 1; + return 0; +} + +static int wu_store_info_all(int unused) +{ + wu_info_all = 1; + return 0; +} + +static int wu_store_no_data(int unused) +{ + wu_no_data = 1; + return 0; +} + +static int wu_store_set_only(int unused) +{ + wu_set_only = 1; + return 0; +} + +static int wu_show_version(int unused) +{ + printf("%s Version %s\n", prog_name, wu_version); + return 0; +} + +/** + * wu_options - command line options and their associated flags + * + */ +struct wu_option wu_options[] = { + + /* + * Help! + */ + [wu_option_help] = { + .longopt = "help", + .shortopt = 'h', + .param = 0, + .descr = "Display help text and exit", + .show = wu_show_help, + }, + + [wu_option_version] = { + .longopt = "version", + .shortopt = 'V', + .param = 0, + .descr = "Display version information and exit", + .show = wu_show_version, + }, + + /* + * Modifies the output for all other options + */ + [wu_option_debug] = { + .longopt = "debug", + .shortopt = 'd', + .param = 0, + .descr = "Print out debugging messages", + .store = wu_store_debug, + }, + + /* + * For data reading.. + */ + [wu_option_count] = { + .longopt = "count", + .shortopt = 'c', + .param = 1, + .descr = "Specify number of data samples", + .option = "", + .store = wu_store_count, + }, + + [wu_option_final] = { + .longopt = "final", + .shortopt = 'z', + .param = 0, + .descr = "Print final interval information", + .store = wu_store_final, + }, + + /* + * Modifies output for each option (most relevant for data) + */ + [wu_option_delim] = { + .longopt = "delim", + .shortopt = 'f', + .param = 1, + .descr = "Set field delimiter (default \", \")", + .option = "", + .store = wu_store_delim, + }, + + [wu_option_newline] = { + .longopt = "newline", + .shortopt = 'n', + .param = 0, + .descr = "Use '\\n' as delimter instead", + .store = wu_store_newline, + }, + + [wu_option_localtime] = { + .longopt = "localtime", + .shortopt = 't', + .param = 0, + .descr = "Print localtime with each data reading", + .store = wu_store_localtime, + }, + + [wu_option_gmtime] = { + .longopt = "gmtime", + .shortopt = 'g', + .param = 0, + .descr = "Print GMT time with each data reading", + .store = wu_store_gmtime, + }, + + [wu_option_label] = { + .longopt = "label", + .shortopt = 'l', + .param = 0, + .descr = "Show labels of each field", + .store = wu_store_label, + }, + + /* + * Relevant for each of the fields below + */ + [wu_option_suppress] = { + .longopt = "suppress", + .shortopt = 's', + .param = 0, + .descr = "Suppress printing of the field description", + .store = wu_store_suppress, + }, + + /* + * These options print values from the device and exit. + */ + [wu_option_cal] = { + .longopt = "calibrate", + .shortopt = 'b', + .param = 0, + .descr = "Print calibration parameters", + .show = wu_show_cal, + }, + + [wu_option_header] = { + .longopt = "header", + .shortopt = 'r', + .param = 0, + .descr = "Print data field names (as read from device)", + .show = wu_show_header, + }, + + /* + * These options have an optional parameter. + * W/o that parameter, they print values from the device. + * W/ that parameter, they set that option and read data. + * + * Except when the 'set-only' parameter is used, then the + * parameters are set, then re-read and printed. + */ + [wu_option_interval] = { + .longopt = "interval", + .shortopt = 'i', + .param = 2, + .descr = "Get/Set sampling interval", + .option = "", + .show = wu_show_interval, + .store = wu_store_interval, + }, + + [wu_option_mode] = { + .longopt = "mode", + .shortopt = 'm', + .param = 2, + .descr = "Get/Set display mode", + .option = "", + .show = wu_show_mode, + .store = wu_store_mode, + }, + + [wu_option_user] = { + .longopt = "user", + .shortopt = 'u', + .param = 2, + .descr = "Get/Set user parameters", + .option = "", + .format = ",<2nd tier cost>," + "<2nd tier threshold>," + "", + .show = wu_show_user, + .store = wu_store_user, + }, + + [wu_option_info_all] = { + .longopt = "show-all", + .shortopt = 'a', + .param = 0, + .descr = "Show all device parameters", + .store = wu_store_info_all, + }, + + [wu_option_no_data] = { + .longopt = "no-data", + .shortopt = 'N', + .param = 0, + .descr = "Don't read any data (just read device info)", + .store = wu_store_no_data, + }, + + [wu_option_set_only] = { + .longopt = "set-only", + .shortopt = 'S', + .param = 0, + .descr = "Set parameters only (don't read them back)", + .store = wu_store_set_only, + }, +}; + +#define wu_num_options ARRAY_SIZE(wu_options) + +static int wu_show_help(int unused) +{ + int i; + int n; + + wu_show_version(unused); + printf(" A program for interfacing with the Watts Up? Power Meter\n"); + printf("\n"); + + printf("Usage: %s [ ... ] [ ... ]\n", + prog_name); + printf("\n"); + + printf(" is the serial port the device is connected at.\n"); + printf("\n"); + + printf(" are any of the following:\n"); + for (i = 0; i < wu_num_options; i++) { + n = printf(" -%c", wu_options[i].shortopt); + + if (wu_options[i].param == 0) + n = printf(" "); + else if (wu_options[i].param == 1) + n = printf(" %s", wu_options[i].option); + else if (wu_options[i].param == 2) + n = printf(" [%s]", wu_options[i].option); + + n += printf("%*c| ", n - 12, ' '); + n += printf("--%s", wu_options[i].longopt); + + if (wu_options[i].param == 0) + n += printf(" "); + else if (wu_options[i].param == 1) + n += printf("=%s", wu_options[i].option); + else if (wu_options[i].param == 2) + n += printf("[=%s]", wu_options[i].option); + + printf("%*c%s\n", + 40 - n, ' ', wu_options[i].descr); + } + printf("\n"); + printf(" specifies which of these to print out (default: ALL)\n"); + for (i = 0; i < wu_num_fields; i++) { + printf("%12s -- %s\n", wu_fields[i].name, wu_fields[i].descr); + } + printf("\n"); + + return 0; +} + + +static char * wu_option_value(unsigned int index) +{ + return (index < wu_num_options) ? wu_options[index].value : NULL; +} + + +static int wu_check_option_show(int index) +{ + /* + * Return 1 if we need to print something out for + * a particular option. + */ + if (index < wu_num_options) { + if (wu_options[index].flag) { + return 1; + } + } + return 0; +} + +static int wu_check_option_store(int index) +{ + /* + * Return a 1 if this option is set. + */ + + if (index < wu_num_options) { + if (wu_options[index].flag) { + return 1; + } + } + return 0; +} + + +int wu_show(int index, int dev_fd) +{ + if (wu_options[index].show) + return wu_options[index].show(dev_fd); + return 0; +} + +/* + * Check if the option is set, and call its method if so. + * Return whether or not we did anything.. + */ + +int wu_check_show(int index, int dev_fd) +{ + if (wu_check_option_show(index)) { + wu_show(index, dev_fd); + return 1; + } + return 0; +} + + +/* + * Check if the option is set and if so, call it + * Return the value from the ->store() method. + */ + +int wu_check_store(int index, int dev_fd) +{ + if (wu_check_option_store(index)) { + if (wu_options[index].store) + return wu_options[index].store(dev_fd); + } + return 0; +} + + +static void make_longopt(struct option * l) +{ + int i; + + for (i = 0; i < wu_num_options; i++) { + l[i].name = wu_options[i].longopt; + l[i].has_arg = wu_options[i].param; + l[i].flag = &wu_options[i].flag; + l[i].val = 0; + } +} + +static void make_shortopt(char * str) +{ + int i; + char * s = str; + + for (i = 0; i < wu_num_options; i++) { + *s++ = wu_options[i].shortopt; + if (wu_options[i].param) + *s++ = wu_options[i].param == 1 ? ':' : ';'; + } +} + +static void enable_short_option(int c, char * arg) +{ + int i; + + /* + * Friggin' getopt_long() will return the + * character if we get a short option (e.g. '-h'), + * instead of returning 0 like it does when it + * gets a long option (e.g. "--help"). Ugh. + */ + for (i = 0; i < wu_num_options; i++) { + if (wu_options[i].shortopt == c) { + wu_options[i].flag = 1; + if (arg) + wu_options[i].value = strdup(arg); + break; + } + } +} + +int parse_args(int argc, char ** argv) +{ + struct option longopts[wu_num_options + 1] = { }; + char shortopts[wu_num_options * 2] = ""; + + make_longopt(longopts); + make_shortopt(shortopts); + + + while (1) { + int c; + int index; + + c = getopt_long(argc, argv, shortopts, + longopts, &index); + if (c == -1) + break; + + switch (c) { + case 0: + wu_options[index].flag = 1; + if (optarg) + wu_options[index].value = strdup(optarg); + + printf("long option: val = %c, optarg = %s\n", + wu_options[index].shortopt, optarg); + break; + case '?': + err("Bad parameter"); + return ret_err(EINVAL); + break; + default: + enable_short_option(c, optarg); + break; + } + } + + /* + * Check for help request now and bail after + * printing it, if it's set. + */ + if (wu_check_show(wu_option_help, 0)) + exit(0); + + if (wu_check_show(wu_option_version, 0)) + exit(0); + + /* + * Fields to print out + */ + if (optind < argc) { + int i; + + wu_device = argv[optind++]; + + if (optind < argc) { + for (i = optind; i < argc; i++) + enable_field(argv[i]); + } else + enable_all_fields(); + + } else { + wu_show(wu_option_help, 0); + return ret_err(EINVAL); + } + return 0; +} + diff --git a/util.h b/util.h new file mode 100644 index 0000000..70a5ce8 --- /dev/null +++ b/util.h @@ -0,0 +1,4 @@ +#ifndef UTIL_H +#define UTIL_H + +#endif diff --git a/wattsup.c b/wattsup.c index 159b674..7bb901b 100644 --- a/wattsup.c +++ b/wattsup.c @@ -14,1788 +14,9 @@ */ #define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static const char * wu_version = "0.02"; - - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) - -static const char * prog_name = "wattsup"; - -static const char * sysfs_path_start = "/sys/class/tty"; - -static char * wu_device = "ttyUSB0"; -static int wu_fd = 0; -static int wu_count = 0; -static int wu_debug = 0; -static char *wu_delim = ", "; -static int wu_final = 0; -static int wu_interval = 1; -static int wu_label = 0; -static int wu_newline = 0; -static int wu_suppress = 0; - -static int wu_localtime = 0; -static int wu_gmtime = 0; - -static int wu_info_all = 0; -static int wu_no_data = 0; -static int wu_set_only = 0; - -#define wu_strlen 256 -#define wu_num_fields 18 -#define wu_param_len 16 - -struct wu_packet { - unsigned int cmd; - unsigned int sub_cmd; - unsigned int count; - char buf[wu_strlen]; - int len; - char * field[wu_num_fields]; - char * label[wu_num_fields]; -}; - - -struct wu_data { - unsigned int watts; - unsigned int volts; - unsigned int amps; - unsigned int watt_hours; - - unsigned int cost; - unsigned int mo_kWh; - unsigned int mo_cost; - unsigned int max_watts; - - unsigned int max_volts; - unsigned int max_amps; - unsigned int min_watts; - unsigned int min_volts; - - unsigned int min_amps; - unsigned int power_factor; - unsigned int duty_cycle; - unsigned int power_cycle; - - unsigned int frequency; - unsigned int VA; -}; - -struct wu_options { - char * longopt; - int shortopt; - int param; - int flag; - char * value; - - char * descr; - char * option; - char * format; - - int (*show)(int dev_fd); - int (*store)(int dev_fd); -}; - -enum { - wu_option_help = 0, - wu_option_version, - - wu_option_debug, - - wu_option_count, - wu_option_final, - - wu_option_delim, - wu_option_newline, - wu_option_localtime, - wu_option_gmtime, - wu_option_label, - - wu_option_suppress, - - wu_option_cal, - wu_option_header, - - wu_option_interval, - wu_option_mode, - wu_option_user, - - wu_option_info_all, - wu_option_no_data, - wu_option_set_only, -}; - - -static char * wu_option_value(unsigned int index); - - -enum { - wu_field_watts = 0, - wu_field_volts, - wu_field_amps, - - wu_field_watt_hours, - wu_field_cost, - wu_field_mo_kwh, - wu_field_mo_cost, - - wu_field_max_watts, - wu_field_max_volts, - wu_field_max_amps, - - wu_field_min_watts, - wu_field_min_volts, - wu_field_min_amps, - - wu_field_power_factor, - wu_field_duty_cycle, - wu_field_power_cycle, - - wu_field_frequency, - wu_field_va -}; - -struct wu_field { - unsigned int enable; - char * name; - char * descr; -}; - -static struct wu_field wu_fields[wu_num_fields] = { - [wu_field_watts] = { - .name = "watts", - .descr = "Watt Consumption", - }, - - [wu_field_min_watts] = { - .name = "min-watts", - .descr = "Minimum Watts Consumed", - }, - - [wu_field_max_watts] = { - .name = "max-watts", - .descr = "Maxium Watts Consumed", - }, - - [wu_field_volts] = { - .name = "volts", - .descr = "Volts Consumption", - }, - - [wu_field_min_volts] = { - .name = "max-volts", - .descr = "Minimum Volts Consumed", - }, - - [wu_field_max_volts] = { - .name = "min-volts", - .descr = "Maximum Volts Consumed", - }, - - [wu_field_amps] = { - .name = "amps", - .descr = "Amp Consumption", - }, - - [wu_field_min_amps] = { - .name = "min-amps", - .descr = "Minimum Amps Consumed", - }, - - [wu_field_max_amps] = { - .name = "max-amps", - .descr = "Maximum Amps Consumed", - }, - - [wu_field_watt_hours] = { - .name = "kwh", - .descr = "Average KWH", - }, - - [wu_field_mo_kwh] = { - .name = "mo-kwh", - .descr = "Average monthly KWH", - }, - - [wu_field_cost] = { - .name = "cost", - .descr = "Cost per watt", - }, - - [wu_field_mo_cost] = { - .name = "mo-cost", - .descr = "Monthly Cost", - }, - - [wu_field_power_factor] = { - .name = "power-factor", - .descr = "Ratio of Watts vs. Volt Amps", - }, - - [wu_field_duty_cycle] = { - .name = "duty-cycle", - .descr = "Percent of the Time On vs. Time Off", - }, - - [wu_field_power_cycle] = { - .name = "power-cycle", - .descr = "Indication of power cycle", - }, - - [wu_field_frequency] = { - .name = "frequency", - .descr = "AC frequency (HZ)", - }, - - [wu_field_va] = { - .name = "VA", - .descr = "VA", - }, -}; - - - -static void msg_start(const char * fmt, ...) -{ - va_list(ap); - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); -} - -static void msg_end(void) -{ - printf("\n"); - fflush(stdout); -} - -static void msg(const char * fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); -} - -static void dbg(const char * fmt, ...) -{ - va_list ap; - - if (wu_debug) { - va_start(ap, fmt); - msg_start("%s: [debug] ", prog_name); - vprintf(fmt, ap); - msg_end(); - va_end(ap); - } -} - -static void err(const char * fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - fprintf(stderr, "%s: [error] ", prog_name); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); -} - -static void perr(const char * fmt, ...) -{ - char buf[1024]; - int n; - va_list ap; - - va_start(ap, fmt); - n = sprintf(buf, "%s: [error] ", prog_name); - vsnprintf(buf + n, sizeof(buf) - n, fmt, ap); - perror(buf); - va_end(ap); -} - -static int ret_err(int err) -{ - errno = err; - return -1; -} - - -static void print_packet(struct wu_packet * p, char * str) -{ - int i; - - if (!wu_suppress) - msg_start("Watts Up? %s\n", str); - for (i = 0; i < p->count; i++) { - if (i) - msg("%s", wu_newline ? "\n" : wu_delim); - if (wu_label) - msg("[%s] ", p->label[i]); - msg(p->field[i]); - } - msg_end(); -} - - -static void print_time(void) -{ - time_t t; - struct tm * tm; - - if (wu_localtime || wu_gmtime) { - time(&t); - - if (wu_localtime) - tm = localtime(&t); - else - tm = gmtime(&t); - - msg("[%02d:%02d:%02d] ", - tm->tm_hour, tm->tm_min, tm->tm_sec); - } -} - -static void print_packet_filter(struct wu_packet * p, - int (*filter_ok)(struct wu_packet * p, int i, char * str)) -{ - char buf[256]; - int printed; - int i; - - print_time(); - for (i = 0, printed = 0; i < p->count; i++) { - if (!filter_ok(p, i, buf)) - continue; - - if (printed++) - msg("%s", wu_newline ? "\n" : wu_delim); - if (wu_label) - msg("[%s] ", p->label[i]); - msg(buf); - } - msg_end(); -} - - -/* - * Device should be something like "ttyS0" - */ - -static int open_device(char * device_name, int * dev_fd) -{ - struct stat s; - int ret; - int cur_fd; - - cur_fd = open(".", O_RDONLY); - if (cur_fd < 0) { - perr("Could not open current directory."); - return cur_fd; - } - - ret = chdir(sysfs_path_start); - if (ret) { - perr(sysfs_path_start); - return ret; - } - - /* - * First, check if /sys/class/tty// exists. - */ - - dbg("Checking sysfs path: %s/%s", sysfs_path_start, device_name); - - ret = stat(device_name, &s); - if (ret < 0) { - perr(device_name); - goto Done; - } - - if (!S_ISDIR(s.st_mode)) { - errno = -ENOTDIR; - err("%s is not a TTY device.", device_name); - goto Done; - } - - dbg("%s is a registered TTY device", device_name); - - fchdir(cur_fd); - - - /* - * Check if device node exists and is writable - */ - chdir("/dev"); - - ret = stat(device_name, &s); - if (ret < 0) { - perr("/dev/%s (device node)", device_name); - goto Done; - } - - if (!S_ISCHR(s.st_mode)) { - errno = -ENOTTY; - ret = -1; - err("%s is not a TTY character device.", device_name); - goto Done; - } - - dbg("%s has a device node", device_name); - - ret = access(device_name, R_OK | W_OK); - if (ret) { - perr("%s: Not writable?", device_name); - goto Done; - } - - ret = open(device_name, O_RDWR | O_NONBLOCK); - if (ret < 0) { - perr("Could not open %s"); - goto Done; - } - *dev_fd = ret; - ret = 0; -Done: - fchdir(cur_fd); - close(cur_fd); - return ret; -} - - -static int setup_serial_device(int dev_fd) -{ - struct termios t; - int ret; - - ret = tcgetattr(dev_fd, &t); - if (ret) - return ret; - - cfmakeraw(&t); - cfsetispeed(&t, B115200); - cfsetospeed(&t, B115200); - tcflush(dev_fd, TCIFLUSH); - - t.c_iflag |= IGNPAR; - t.c_cflag &= ~CSTOPB; - ret = tcsetattr(dev_fd, TCSANOW, &t); - - if (ret) { - perr("setting terminal attributes"); - return ret; - } - - return 0; -} - - -static int wu_write(int fd, struct wu_packet * p) -{ - int ret; - int n; - int i; - char * s = p->buf; - - memset(p->buf, 0, sizeof(p->buf)); - n = sprintf(p->buf, "#%c,%c,%d", p->cmd, p->sub_cmd, p->count); - p->len = n; - s = p->buf + n; - - for (i = 0; i < p->count; i++) { - if ((p->len + strlen(p->field[i]) + 4) >= sizeof(p->buf)) { - err("Overflowed command string"); - return ret_err(EOVERFLOW); - } - n = sprintf(s, ",%s", p->field[i]); - s += n; - p->len += n; - } - p->buf[p->len++] = ';'; - - dbg("Writing '%s' (strlen = %d) (len = %d) to device", - p->buf, strlen(p->buf), p->len); - ret = write(fd, p->buf, p->len); - if (ret != p->len) - perr("Writing to device"); - - return ret >= 0 ? 0 : ret; -} - - -static void dump_packet(struct wu_packet * p) -{ - int i; - - dbg("Packet - Command '%c' %d parameters", p->cmd, p->count); - - for (i = 0; i < p->count; i++) - dbg("[%2d] [%20s] = \"%s\"", i, p->label[i], p->field[i]); -} - - -static int parse_packet(struct wu_packet * p) -{ - char * s, *next; - int i; - - p->buf[p->len] = '\0'; - - dbg("Parsing Packet, Raw buffer is (%d bytes) [%s]", - p->len, p->buf); - - s = p->buf; - - /* - * First character should be '#' - */ - if (s) { - s = strchr(s, '#'); - if (s) - s++; - else { - dbg("Invalid packet"); - return ret_err(EFAULT); - } - } else { - dbg("Invalid packet"); - return ret_err(EFAULT); - } - - /* - * Command character is first - */ - next = strchr(s, ','); - if (next) { - p->cmd = *s; - s = ++next; - } else { - dbg("Invalid Command field [%s]", s); - return ret_err(EFAULT); - } - - /* - * Next character is the subcommand, and should be '-' - * Though, it doesn't matter, because we just - * discard it anyway. - */ - next = strchr(s, ','); - if (next) { - p->sub_cmd = *s; - s = ++next; - } else { - dbg("Invalid 2nd field"); - return ret_err(EFAULT); - } - - /* - * Next is the number of parameters, - * which should always be > 0. - */ - next = strchr(s, ','); - if (next) { - *next++ = '\0'; - p->count = atoi(s); - s = next; - } else { - dbg("Couldn't determine number of parameters"); - return ret_err(EFAULT); - } - - dbg("Have %d parameter%s (cmd = '%c')", - p->count, p->count > 1 ? "s" : "", p->cmd); - - /* - * Now, we loop over the rest of the string, - * storing a pointer to each in p->field[]. - * - * The last character was originally a ';', but may have been - * overwritten with a '\0', so we make sure to catch - * that when converting the last parameter. - */ - for (i = 0; i < p->count; i++) { - next = strpbrk(s, ",;"); - if (next) { - *next++ = '\0'; - } else { - if (i < (p->count - 1)) { - dbg("Malformed parameter string [%s]", s); - return ret_err(EFAULT); - } - } - - /* - * Skip leading white space in fields - */ - while (isspace(*s)) - s++; - p->field[i] = s; - s = next; - } - dump_packet(p); - return 0; -} - - -static int wu_read(int fd, struct wu_packet * p) -{ - fd_set read_fd; - struct timeval tv; - int ret; - - FD_ZERO(&read_fd); - FD_SET(fd, &read_fd); - - tv.tv_sec = 2; - tv.tv_usec = 0; - - ret = select(fd + 1, &read_fd, NULL, NULL, &tv); - if (ret < 0) { - perr("select on terminal device"); - return ret; - } else if (ret > 0) { - - ret = read(fd, p->buf, wu_strlen); - if (ret < 0) { - perr("Reading from device"); - return ret; - } - p->len = ret; - } else { - dbg("Device timed out while reading"); - return ret_err(ETIME); - } - return parse_packet(p); -} - - -static int wu_show_header(int fd) -{ - struct wu_packet p = { - .cmd = 'H', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "watts header", - [1] = "volts header", - [2] = "amps header", - [3] = "kWh header", - [4] = "cost header", - [5] = "mo. kWh header", - [6] = "mo. cost header", - [7] = "max watts header", - [8] = "max volts header", - [9] = "max amps header", - [10] = "min watts header", - [11] = "min volts header", - [12] = "min amps header", - [13] = "power factor header", - [14] = "duty cycle header", - [15] = "power cycle header", - [16] = "frequency header", - [17] = "VA header" - } - }; - int ret; - - ret = wu_write(fd, &p); - if (ret) { - perr("Requesting header strings"); - return ret; - } - sleep(1); - - ret = wu_read(fd, &p); - if (ret) { - perr("Reading header strings"); - return ret; - } - - print_packet(&p, "Header Record"); - - return 0; -} - - -static int wu_show_cal(int fd) -{ - struct wu_packet p = { - .cmd = 'F', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "flags", - [1] = "sample count", - [2] = "volts gain", - [3] = "volts bias", - [4] = "amps gain", - [5] = "amps bias", - [6] = "amps offset", - [7] = "low amps gain", - [8] = "low amps bias", - [9] = "low amps offset", - [10] = "watts gain", - [11] = "watts offset", - [12] = "low watts gain", - [13] = "low watts offset", - }, - }; - int ret; - - ret = wu_write(fd, &p); - if (ret) { - perr("Requesting calibration parameters"); - return ret; - } - sleep(1); - - ret = wu_read(fd, &p); - if (ret) { - perr("Reading header strings"); - return ret; - } - print_packet(&p, "Calibration Settings"); - - return 0; -} - -static int wu_start_log(void) -{ - struct wu_packet p = { - .cmd = 'L', - .sub_cmd = 'W', - .count = 3, - .field = { - [0] = "E", - [1] = "1", - [2] = "1", - }, - }; - int ret; - - /* - * Start up logging - */ - ret = wu_write(wu_fd, &p); - if (!ret) - sleep(1); - else { - perr("Starting External Logging"); - return ret; - } - return ret; -} - -static int wu_stop_log(void) -{ - struct wu_packet p = { - .cmd = 'L', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "time stamp", - [1] = "interval", - }, - }; - int ret; - - /* - * Stop logging and read time stamp. - */ - ret = wu_write(wu_fd, &p); - if (ret) { - perr("Stopping External Logging"); - return ret; - } - sleep(1); - - ret = wu_read(wu_fd, &p); - if (ret) { - perr("Reading final time stamp"); - return ret; - } - if (wu_final) - print_packet(&p, "Final Time Stamp and Interval"); - return ret; -} - -static int filter_data(struct wu_packet * p, int i, char * buf) -{ - if (i < wu_num_fields) { - if (wu_fields[i].enable) { - double scale; - int digits; - switch(i){ - case wu_field_min_amps: - case wu_field_max_amps: - case wu_field_amps: - scale = 1000.0; - digits = 3; - break; - case wu_field_power_factor: - case wu_field_duty_cycle: - scale = 1.0; - digits = 1; - break; - default: - scale = 10.0; - digits = 1; - } - double val = strtod(p->field[i], NULL); - snprintf(buf, 256, "%.*lf", digits, val / scale); - return 1; - } - } - return 0; -} - -static int wu_clear(int fd) -{ - struct wu_packet p = { - .cmd = 'R', - .sub_cmd = 'W', - .count = 0, - }; - int ret; - - /* - * Clear the memory - */ - ret = wu_write(fd, &p); - if (ret) - perr("Clearing memory"); - else - sleep(2); - - /* - * Dummy read - */ - wu_read(fd, &p); - return ret; - -} - -static int wu_read_data(int fd) -{ - struct wu_packet p = { - .label = { - [0] = "watts", - [1] = "volts", - [2] = "amps", - [3] = "watt hours", - [4] = "cost", - [5] = "mo. kWh", - [6] = "mo. cost", - [7] = "max watts", - [8] = "max volts", - [9] = "max amps", - [10] = "min watts", - [11] = "min volts", - [12] = "min amps", - [13] = "power factor", - [14] = "duty cycle", - [15] = "power cycle", - [16] = "frequency", - [17] = "VA" - }, - }; - int num_read = 0; - int retry = 0; - int ret; - int i; - - static const int wu_max_retry = 2; - - i = 0; - while (1) { - - ret = wu_read(fd, &p); - if (ret) { - if (++retry < wu_max_retry) { - dbg("Bad record back, retrying\n"); - sleep(wu_interval); - continue; - } else if (retry == wu_max_retry) { - dbg("Still couldn't get a good record, resetting\n"); - wu_stop_log(); - wu_clear(fd); - wu_start_log(); - num_read = 0; - sleep(wu_interval); - continue; - } - perr("Blech. Giving up on read"); - break; - } else if (retry) - retry = 0; - - dbg("[%d] ", num_read); - num_read++; - print_packet_filter(&p, filter_data); - - if (wu_count && (++i == wu_count)) - break; - - sleep(wu_interval); - } - return 0; - -} - - -static int wu_show_interval(int fd) -{ - struct wu_packet p = { - .cmd = 'S', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "reserved", - [1] = "interval", - }, - }; - int ret; - - ret = wu_write(fd, &p); - if (ret) { - perr("Requesting interval"); - return ret; - } - sleep(1); - - ret = wu_read(fd, &p); - if (ret) { - perr("Reading interval"); - return ret; - } - print_packet(&p, "Interval Settings"); - - return 0; -} - -static int wu_write_interval(int fd, unsigned int seconds, - unsigned int interval) -{ - char str_seconds[wu_param_len]; - char str_interval[wu_param_len]; - struct wu_packet p = { - .cmd = 'S', - .sub_cmd = 'W', - .count = 2, - .field = { - [0] = str_seconds, - [1] = str_interval, - }, - }; - int ret; - - snprintf(str_seconds, wu_param_len, "%ud", seconds); - snprintf(str_interval, wu_param_len, "%ud", interval); - - ret = wu_write(fd, &p); - if (ret) { - perr("Setting Sampling Interval"); - return ret; - } - sleep(1); - return 0; -} - -static int wu_store_interval(int fd) -{ - char * s = wu_option_value(wu_option_interval); - char * end; - - wu_interval = strtol(s, &end, 0); - if (*end) { - err("Invalid interval: %s", s); - return ret_err(EINVAL); - } - return wu_write_interval(fd, 1, wu_interval); -} - -static int wu_show_mode(int fd) -{ - struct wu_packet p = { - .cmd = 'M', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "display mode", - }, - }; - int ret; - - ret = wu_write(fd, &p); - if (ret) { - perr("Requesting device display mode"); - return ret; - } - - ret = wu_read(fd, &p); - if (ret) { - perr("Reaing device display mode"); - return ret; - } - dump_packet(&p); - return ret; -} - -static int wu_write_mode(int fd, int mode) -{ - char str_mode[wu_param_len]; - struct wu_packet p = { - .cmd = 'M', - .sub_cmd = 'W', - .count = 1, - .field = { - [0] = str_mode, - }, - }; - int ret; - - snprintf(str_mode, wu_param_len, "%ud", mode); - ret = wu_write(fd, &p); - if (ret) - perr("Setting device display mode"); - else - sleep(1); - return ret; -} - -static int wu_store_mode(int fd) -{ - char * s = wu_option_value(wu_option_mode); - char * end; - unsigned int mode; - - mode = strtol(s, &end, 0); - if (*end) { - err("Invalid mode: %s", s); - return ret_err(EINVAL); - } - return wu_write_mode(fd, mode); -} - - - -static int wu_show_user(int fd) -{ - struct wu_packet p = { - .cmd = 'U', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = "cost per kWh", - [1] = "2nd tier cost", - [2] = "2nd tier threshold", - [3] = "duty cycle threshold", - }, - }; - int ret; - - ret = wu_write(fd, &p); - if (ret) { - perr("Requesting user parameters"); - return ret; - } - sleep(1); - - ret = wu_read(fd, &p); - if (ret) { - perr("Reading user parameters"); - return ret; - } - print_packet(&p, "User Settings"); - return 0; -} - - -static int wu_write_user(int fd, unsigned int kwh_cost, - unsigned int second_tier_cost, - unsigned int second_tier_threshold, - unsigned int duty_cycle_threshold) -{ - char str_kwh_cost[wu_param_len]; - char str_2nd_tier_cost[wu_param_len]; - char str_2nd_tier_threshold[wu_param_len]; - char str_duty_cycle_threshold[wu_param_len]; - - struct wu_packet p = { - .cmd = 'U', - .sub_cmd = 'R', - .count = 0, - .label = { - [0] = str_kwh_cost, - [1] = str_2nd_tier_cost, - [2] = str_2nd_tier_threshold, - [3] = str_duty_cycle_threshold, - }, - }; - int ret; - - snprintf(str_kwh_cost, wu_param_len, "%ud", kwh_cost); - snprintf(str_2nd_tier_cost, wu_param_len, "%ud", - second_tier_cost); - snprintf(str_2nd_tier_threshold, wu_param_len, "%ud", - second_tier_threshold); - snprintf(str_duty_cycle_threshold, wu_param_len, "%ud", - duty_cycle_threshold); - - ret = wu_write(fd, &p); - if (ret) - perr("Writing user parameters"); - else - sleep(1); - return ret; -} - -static int wu_store_user(int fd) -{ - unsigned int kwh_cost; - unsigned int second_tier_cost; - unsigned int second_tier_threshold; - unsigned int duty_cycle_threshold; - char * buf = wu_option_value(wu_option_user); - char * s = buf; - char * next; - - if (!buf) { - err("No user parameters?"); - return ret_err(EINVAL); - } - - kwh_cost = strtoul(s, &next, 0); - if (next == s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - s = next; - while (s && !isdigit(*s)) - s++; - if (!s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - - second_tier_cost = strtoul(s, &next, 0); - if (next == s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - s = next; - while (s && !isdigit(*s)) - s++; - if (!s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - - second_tier_threshold = strtoul(s, &next, 0); - if (next == s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - s = next; - while (s && !isdigit(*s)) - s++; - if (!s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - - duty_cycle_threshold = strtoul(s, &next, 0); - if (next == s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - s = next; - while (s && !isdigit(*s)) - s++; - if (!s) { - err("Incomplete user parameters"); - return ret_err(EINVAL); - } - - return wu_write_user(fd, kwh_cost, second_tier_cost, - second_tier_threshold, duty_cycle_threshold); -} - - -static void enable_field(char * name) -{ - int i; - - for (i = 0; i < wu_num_fields; i++) { - if (!strcasecmp(wu_fields[i].name, name)) { - wu_fields[i].enable = 1; - break; - } - } -} - -static void enable_all_fields(void) -{ - int i; - - for (i = 0; i < wu_num_fields; i++) - wu_fields[i].enable = 1; -} - - - -static int wu_show_help(int); -static int wu_show_version(int); - - - -static int wu_store_count(int unused) -{ - char * s = wu_option_value(wu_option_count); - char * end; - - if (s) { - wu_count = strtol(s, &end, 0); - if (*end) { - err("Bad count field"); - return ret_err(EINVAL); - } - } - return 0; -} - -static int wu_store_debug(int unused) -{ - wu_debug = 1; - return 0; -} - -static int wu_store_delim(int unused) -{ - char * s = wu_option_value(wu_option_delim); - - if (s) - wu_delim = s; - return 0; -} - -static int wu_store_final(int unused) -{ - wu_final = 1; - return 0; -} - -static int wu_store_label(int unused) -{ - wu_label = 1; - return 0; -} - -static int wu_store_newline(int unused) -{ - wu_newline = 1; - return 0; -} - -static int wu_store_suppress(int unused) -{ - wu_suppress = 1; - return 0; -} - -static int wu_store_localtime(int unused) -{ - wu_localtime = 1; - return 0; -} - -static int wu_store_gmtime(int unused) -{ - wu_gmtime = 1; - return 0; -} - -static int wu_store_info_all(int unused) -{ - wu_info_all = 1; - return 0; -} - -static int wu_store_no_data(int unused) -{ - wu_no_data = 1; - return 0; -} - -static int wu_store_set_only(int unused) -{ - wu_set_only = 1; - return 0; -} - - -/** - * wu_options - command line options and their associated flags - * - */ -static struct wu_options wu_options[] = { - - /* - * Help! - */ - [wu_option_help] = { - .longopt = "help", - .shortopt = 'h', - .param = 0, - .descr = "Display help text and exit", - .show = wu_show_help, - }, - - [wu_option_version] = { - .longopt = "version", - .shortopt = 'V', - .param = 0, - .descr = "Display version information and exit", - .show = wu_show_version, - }, - - /* - * Modifies the output for all other options - */ - [wu_option_debug] = { - .longopt = "debug", - .shortopt = 'd', - .param = 0, - .descr = "Print out debugging messages", - .store = wu_store_debug, - }, - - /* - * For data reading.. - */ - [wu_option_count] = { - .longopt = "count", - .shortopt = 'c', - .param = 1, - .descr = "Specify number of data samples", - .option = "", - .store = wu_store_count, - }, - - [wu_option_final] = { - .longopt = "final", - .shortopt = 'z', - .param = 0, - .descr = "Print final interval information", - .store = wu_store_final, - }, - - /* - * Modifies output for each option (most relevant for data) - */ - [wu_option_delim] = { - .longopt = "delim", - .shortopt = 'f', - .param = 1, - .descr = "Set field delimiter (default \", \")", - .option = "", - .store = wu_store_delim, - }, - - [wu_option_newline] = { - .longopt = "newline", - .shortopt = 'n', - .param = 0, - .descr = "Use '\\n' as delimter instead", - .store = wu_store_newline, - }, - - [wu_option_localtime] = { - .longopt = "localtime", - .shortopt = 't', - .param = 0, - .descr = "Print localtime with each data reading", - .store = wu_store_localtime, - }, - - [wu_option_gmtime] = { - .longopt = "gmtime", - .shortopt = 'g', - .param = 0, - .descr = "Print GMT time with each data reading", - .store = wu_store_gmtime, - }, - - [wu_option_label] = { - .longopt = "label", - .shortopt = 'l', - .param = 0, - .descr = "Show labels of each field", - .store = wu_store_label, - }, - - /* - * Relevant for each of the fields below - */ - [wu_option_suppress] = { - .longopt = "suppress", - .shortopt = 's', - .param = 0, - .descr = "Suppress printing of the field description", - .store = wu_store_suppress, - }, - - /* - * These options print values from the device and exit. - */ - [wu_option_cal] = { - .longopt = "calibrate", - .shortopt = 'b', - .param = 0, - .descr = "Print calibration parameters", - .show = wu_show_cal, - }, - - [wu_option_header] = { - .longopt = "header", - .shortopt = 'r', - .param = 0, - .descr = "Print data field names (as read from device)", - .show = wu_show_header, - }, - - /* - * These options have an optional parameter. - * W/o that parameter, they print values from the device. - * W/ that parameter, they set that option and read data. - * - * Except when the 'set-only' parameter is used, then the - * parameters are set, then re-read and printed. - */ - [wu_option_interval] = { - .longopt = "interval", - .shortopt = 'i', - .param = 2, - .descr = "Get/Set sampling interval", - .option = "", - .show = wu_show_interval, - .store = wu_store_interval, - }, - - [wu_option_mode] = { - .longopt = "mode", - .shortopt = 'm', - .param = 2, - .descr = "Get/Set display mode", - .option = "", - .show = wu_show_mode, - .store = wu_store_mode, - }, - - [wu_option_user] = { - .longopt = "user", - .shortopt = 'u', - .param = 2, - .descr = "Get/Set user parameters", - .option = "", - .format = ",<2nd tier cost>," - "<2nd tier threshold>," - "", - .show = wu_show_user, - .store = wu_store_user, - }, - - [wu_option_info_all] = { - .longopt = "show-all", - .shortopt = 'a', - .param = 0, - .descr = "Show all device parameters", - .store = wu_store_info_all, - }, - - [wu_option_no_data] = { - .longopt = "no-data", - .shortopt = 'N', - .param = 0, - .descr = "Don't read any data (just read device info)", - .store = wu_store_no_data, - }, - - [wu_option_set_only] = { - .longopt = "set-only", - .shortopt = 'S', - .param = 0, - .descr = "Set parameters only (don't read them back)", - .store = wu_store_set_only, - }, -}; - -#define wu_num_options ARRAY_SIZE(wu_options) - -static int wu_show_version(int unused) -{ - printf("%s Version %s\n", prog_name, wu_version); - return 0; -} - -static int wu_show_help(int unused) -{ - int i; - int n; - - wu_show_version(unused); - printf(" A program for interfacing with the Watts Up? Power Meter\n"); - printf("\n"); - - printf("Usage: %s [ ... ] [ ... ]\n", - prog_name); - printf("\n"); - - printf(" is the serial port the device is connected at.\n"); - printf("\n"); - - printf(" are any of the following:\n"); - for (i = 0; i < wu_num_options; i++) { - n = printf(" -%c", wu_options[i].shortopt); - - if (wu_options[i].param == 0) - n = printf(" "); - else if (wu_options[i].param == 1) - n = printf(" %s", wu_options[i].option); - else if (wu_options[i].param == 2) - n = printf(" [%s]", wu_options[i].option); - - n += printf("%*c| ", n - 12, ' '); - n += printf("--%s", wu_options[i].longopt); - - if (wu_options[i].param == 0) - n += printf(" "); - else if (wu_options[i].param == 1) - n += printf("=%s", wu_options[i].option); - else if (wu_options[i].param == 2) - n += printf("[=%s]", wu_options[i].option); - - printf("%*c%s\n", - 40 - n, ' ', wu_options[i].descr); - } - printf("\n"); - printf(" specifies which of these to print out (default: ALL)\n"); - for (i = 0; i < wu_num_fields; i++) { - printf("%12s -- %s\n", wu_fields[i].name, wu_fields[i].descr); - } - printf("\n"); - - return 0; -} - - -static char * wu_option_value(unsigned int index) -{ - return (index < wu_num_options) ? wu_options[index].value : NULL; -} - - -static int wu_check_option_show(int index) -{ - /* - * Return 1 if we need to print something out for - * a particular option. - */ - if (index < wu_num_options) { - if (wu_options[index].flag) { - return 1; - } - } - return 0; -} - -static int wu_check_option_store(int index) -{ - /* - * Return a 1 if this option is set. - */ - - if (index < wu_num_options) { - if (wu_options[index].flag) { - return 1; - } - } - return 0; -} - - -static int wu_show(int index, int dev_fd) -{ - if (wu_options[index].show) - return wu_options[index].show(dev_fd); - return 0; -} - -/* - * Check if the option is set, and call its method if so. - * Return whether or not we did anything.. - */ - -static int wu_check_show(int index, int dev_fd) -{ - if (wu_check_option_show(index)) { - wu_show(index, dev_fd); - return 1; - } - return 0; -} - - -/* - * Check if the option is set and if so, call it - * Return the value from the ->store() method. - */ - -static int wu_check_store(int index, int dev_fd) -{ - if (wu_check_option_store(index)) { - if (wu_options[index].store) - return wu_options[index].store(dev_fd); - } - return 0; -} - - -static void make_longopt(struct option * l) -{ - int i; - - for (i = 0; i < wu_num_options; i++) { - l[i].name = wu_options[i].longopt; - l[i].has_arg = wu_options[i].param; - l[i].flag = &wu_options[i].flag; - l[i].val = 0; - } -} - -static void make_shortopt(char * str) -{ - int i; - char * s = str; - - for (i = 0; i < wu_num_options; i++) { - *s++ = wu_options[i].shortopt; - if (wu_options[i].param) - *s++ = wu_options[i].param == 1 ? ':' : ';'; - } -} - -static void enable_short_option(int c, char * arg) -{ - int i; - - /* - * Friggin' getopt_long() will return the - * character if we get a short option (e.g. '-h'), - * instead of returning 0 like it does when it - * gets a long option (e.g. "--help"). Ugh. - */ - for (i = 0; i < wu_num_options; i++) { - if (wu_options[i].shortopt == c) { - wu_options[i].flag = 1; - if (arg) - wu_options[i].value = strdup(arg); - break; - } - } -} - -static int parse_args(int argc, char ** argv) -{ - struct option longopts[wu_num_options + 1] = { }; - char shortopts[wu_num_options * 2] = ""; - - make_longopt(longopts); - make_shortopt(shortopts); - - - while (1) { - int c; - int index; - - c = getopt_long(argc, argv, shortopts, - longopts, &index); - if (c == -1) - break; - - switch (c) { - case 0: - wu_options[index].flag = 1; - if (optarg) - wu_options[index].value = strdup(optarg); - - printf("long option: val = %c, optarg = %s\n", - wu_options[index].shortopt, optarg); - break; - case '?': - err("Bad parameter"); - return ret_err(EINVAL); - break; - default: - enable_short_option(c, optarg); - break; - } - } - - /* - * Check for help request now and bail after - * printing it, if it's set. - */ - if (wu_check_show(wu_option_help, 0)) - exit(0); - - if (wu_check_show(wu_option_version, 0)) - exit(0); - - /* - * Fields to print out - */ - if (optind < argc) { - int i; - - wu_device = argv[optind++]; - - if (optind < argc) { - for (i = optind; i < argc; i++) - enable_field(argv[i]); - } else - enable_all_fields(); - - } else { - wu_show(wu_option_help, 0); - return ret_err(EINVAL); - } - return 0; -} +#include "wattsup_common.h" +#include "globals.h" int main(int argc, char ** argv) { diff --git a/wattsup_common.h b/wattsup_common.h new file mode 100644 index 0000000..676502c --- /dev/null +++ b/wattsup_common.h @@ -0,0 +1,122 @@ +#ifndef WATTSUP_COMMON_H +#define WATTSUP_COMMON_H + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +#define wu_strlen 256 +#define wu_num_fields 18 +#define wu_param_len 16 + +struct wu_packet { + unsigned int cmd; + unsigned int sub_cmd; + unsigned int count; + char buf[wu_strlen]; + int len; + char * field[wu_num_fields]; + char * label[wu_num_fields]; +}; + + +struct wu_data { + unsigned int watts; + unsigned int volts; + unsigned int amps; + unsigned int watt_hours; + + unsigned int cost; + unsigned int mo_kWh; + unsigned int mo_cost; + unsigned int max_watts; + + unsigned int max_volts; + unsigned int max_amps; + unsigned int min_watts; + unsigned int min_volts; + + unsigned int min_amps; + unsigned int power_factor; + unsigned int duty_cycle; + unsigned int power_cycle; + + unsigned int frequency; + unsigned int VA; +}; + +struct wu_option { + char * longopt; + int shortopt; + int param; + int flag; + char * value; + + char * descr; + char * option; + char * format; + + int (*show)(int dev_fd); + int (*store)(int dev_fd); +}; + +enum { + wu_option_help = 0, + wu_option_version, + + wu_option_debug, + + wu_option_count, + wu_option_final, + + wu_option_delim, + wu_option_newline, + wu_option_localtime, + wu_option_gmtime, + wu_option_label, + + wu_option_suppress, + + wu_option_cal, + wu_option_header, + + wu_option_interval, + wu_option_mode, + wu_option_user, + + wu_option_info_all, + wu_option_no_data, + wu_option_set_only, +}; + +enum { + wu_field_watts = 0, + wu_field_volts, + wu_field_amps, + + wu_field_watt_hours, + wu_field_cost, + wu_field_mo_kwh, + wu_field_mo_cost, + + wu_field_max_watts, + wu_field_max_volts, + wu_field_max_amps, + + wu_field_min_watts, + wu_field_min_volts, + wu_field_min_amps, + + wu_field_power_factor, + wu_field_duty_cycle, + wu_field_power_cycle, + + wu_field_frequency, + wu_field_va +}; + +struct wu_field { + unsigned int enable; + char * name; + char * descr; +}; + +#endif