florian 6a6b46554a [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<roberto.riggio@create-net.org>

git-svn-id: svn://svn.openwrt.org/openwrt/packages@26380 3c298f89-4303-0410-b956-a3cf2f4a3e73
2011-03-30 20:10:45 +00:00

1878 lines
32 KiB
C

/*
* 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<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
#include<termios.h>
#include<ctype.h>
#include<getopt.h>
#include<signal.h>
#include<time.h>
#include<sys/stat.h>
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/<name>/ 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 = "<n>",
.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 = "<str>",
.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 = "<n>",
.show = wu_show_interval,
.store = wu_store_interval,
},
[wu_option_mode] = {
.longopt = "mode",
.shortopt = 'm',
.param = 2,
.descr = "Get/Set display mode",
.option = "<n>",
.show = wu_show_mode,
.store = wu_store_mode,
},
[wu_option_user] = {
.longopt = "user",
.shortopt = 'u',
.param = 2,
.descr = "Get/Set user parameters",
.option = "<str>",
.format = "<cost per kwh>,<2nd tier cost>,"
"<2nd tier threshold>,"
"<duty cycle 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 [<options> ... ]<device> [<values> ... ]\n",
prog_name);
printf("\n");
printf("<device> is the serial port the device is connected at.\n");
printf("\n");
printf("<options> 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("<value> 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;
}