From: florian Date: Wed, 30 Mar 2011 20:10:45 +0000 (+0000) Subject: [package] add wattsup X-Git-Url: http://git.archive.openwrt.org/?a=commitdiff_plain;h=6a6b46554aba3f143f5e78041ac51c23f88a3850;p=packages.git [package] add wattsup This patch adds a command line utility to interface with the WattsUp power meters (https://www.wattsupmeters.com/) Signed-off-by: Roberto Riggio git-svn-id: svn://svn.openwrt.org/openwrt/packages@26380 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- diff --git a/utils/wattsup/Makefile b/utils/wattsup/Makefile new file mode 100644 index 000000000..ced69d833 --- /dev/null +++ b/utils/wattsup/Makefile @@ -0,0 +1,31 @@ +# Copyright (C) 2006 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=wattsup +PKG_RELEASE:=1 +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +define Package/wattsup + SECTION:=utils + CATEGORY:=Utilities + TITLE:=Wattsup polling utility +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Package/wattsup/install + $(INSTALL_DIR) $(1)/usr/bin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/wattsup $(1)/usr/bin/ +endef + +$(eval $(call BuildPackage,wattsup)) diff --git a/utils/wattsup/src/Makefile b/utils/wattsup/src/Makefile new file mode 100644 index 000000000..b9a687a31 --- /dev/null +++ b/utils/wattsup/src/Makefile @@ -0,0 +1,8 @@ +wattsup: wattsup.o + $(CC) $(LDFLAGS) wattsup.o -o wattsup + +wattsup.o: wattsup.c + $(CC) $(CFLAGS) -c wattsup.c + +clean: + rm *.o wattsup diff --git a/utils/wattsup/src/wattsup.c b/utils/wattsup/src/wattsup.c new file mode 100644 index 000000000..8b26ad62c --- /dev/null +++ b/utils/wattsup/src/wattsup.c @@ -0,0 +1,1877 @@ +/* + * 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 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; +}; + +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, 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", + } + }; + 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; +} + +