/* * wattsup - Program for controlling the Watts Up? Pro Device * * * Copyright (c) 2005 Patrick Mochel * * This program is released under the GPLv2 * * * Compiled with: * * gcc -O2 -Wall -o wattsup wattsup.c * */ #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 16 #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; }; 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, }; 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", }, }; 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"); } 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, B9600); cfsetospeed(&t, B9600); 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", } }; 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 val = strtod(p->field[i], NULL); snprintf(buf, 256, "%.1f", val / 10.0); 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", }, }; 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; } int main(int argc, char ** argv) { int ret; int fd = 0; ret = parse_args(argc, argv); if (ret) return 0; /* * Try to enable debugging early */ if ((ret = wu_check_store(wu_option_debug, 0))) goto Close; ret = open_device(wu_device, &fd); if (ret) return ret; dbg("%s: Open for business", wu_device); ret = setup_serial_device(fd); if (ret) goto Close; wu_clear(fd); wu_fd = fd; /* * Set delimeter before we print out any fields. */ if ((ret = wu_check_store(wu_option_delim, fd))) goto Close; /* * Ditto for 'label' and 'newline' flags. */ if ((ret = wu_check_store(wu_option_label, fd))) goto Close; if ((ret = wu_check_store(wu_option_newline, fd))) goto Close; if ((ret = wu_check_store(wu_option_suppress, fd))) goto Close; if ((ret = wu_check_store(wu_option_localtime, fd))) goto Close; if ((ret = wu_check_store(wu_option_gmtime, fd))) goto Close; if ((ret = wu_check_store(wu_option_set_only, fd))) goto Close; if ((ret = wu_check_store(wu_option_no_data, fd))) goto Close; if ((ret = wu_check_store(wu_option_info_all, fd))) goto Close; /* * Options to set device parameters. */ if ((ret = wu_check_store(wu_option_interval, fd))) goto Close; if ((ret = wu_check_store(wu_option_mode, fd))) goto Close; if ((ret = wu_check_store(wu_option_user, fd))) goto Close; /* * Check for options to print device info */ if (wu_info_all) { wu_show(wu_option_cal, fd); wu_show(wu_option_header, fd); wu_show(wu_option_interval, fd); wu_show(wu_option_mode, fd); wu_show(wu_option_user, fd); } else { wu_check_show(wu_option_cal, fd); wu_check_show(wu_option_header, fd); if (!wu_set_only) { wu_check_show(wu_option_interval, fd); wu_check_show(wu_option_mode, fd); wu_check_show(wu_option_user, fd); } } if (!wu_no_data) { if ((ret = wu_check_store(wu_option_count, fd))) goto Close; if ((ret = wu_check_store(wu_option_final, fd))) goto Close; if ((ret = wu_start_log())) goto Close; wu_read_data(fd); wu_stop_log(); } Close: close(fd); return ret; }