/* * OpenPSU remote control * RS232 control host software * * Copyright (C) 2007 Michael Buesch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "es51984.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VERSION "001" #define BAUDRATE B2400 typedef uint32_t le32_t; typedef _Bool bool; /* Get the API definitions. */ #include "../firmware/ext_control.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) struct command { struct extctl_command d; bool calibrate; struct command *next; }; static struct { const char *tty; const char *meter_node; int debug; int verbose; struct command *commands; } cmdargs = { .tty = "/dev/ttyUSB0", }; static int fd; static void msleep(unsigned int msecs) { int err; struct timespec time; time.tv_sec = 0; while (msecs >= 1000) { time.tv_sec++; msecs -= 1000; } time.tv_nsec = msecs; time.tv_nsec *= 1000000; do { err = nanosleep(&time, &time); } while (err && errno == EINTR); if (err) { fprintf(stderr, "nanosleep() failed with: %s\n", strerror(errno)); } } static le32_t cpu_to_le32(uint32_t x) { uint8_t res[4]; res[0] = (x & 0x000000FF) >> 0; res[1] = (x & 0x0000FF00) >> 8; res[2] = (x & 0x00FF0000) >> 16; res[3] = (x & 0xFF000000) >> 24; return *((le32_t *)res); } static uint32_t le32_to_cpu(le32_t x) { const uint8_t *in = (const uint8_t *)(&x); uint32_t res = 0; res |= in[0] << 0; res |= in[1] << 8; res |= in[2] << 16; res |= in[3] << 24; return res; } static const char * result_string(uint8_t result) { switch (result) { case EXTCTL_CMD_RESULT_OK: return "OK"; case EXTCTL_CMD_RESULT_ENOTIMPL: return "Not implemented"; case EXTCTL_CMD_RESULT_ENOBUFS: return "No buffer space available"; case EXTCTL_CMD_RESULT_EBUSY: return "Busy"; case EXTCTL_CMD_RESULT_ENOTAVAIL: return "Not available"; case EXTCTL_CMD_RESULT_EINVAL: return "Invalid value"; case EXTCTL_CMD_RESULT_ECSUM: return "Checksum error"; } return "Unknown error code"; } static const char * command_string(uint8_t cmd) { switch (cmd) { case EXTCTL_CMD_NOP: return "NOP"; case EXTCTL_CMD_SETVOLTAGE: return "SETVOLTAGE"; case EXTCTL_CMD_GETVOLTAGE: return "GETVOLTAGE"; case EXTCTL_CMD_SETMAXCUR: return "SETMAXCUR"; case EXTCTL_CMD_GETMAXCUR: return "GETMAXCUR"; case EXTCTL_CMD_SWITCHPROF: return "SWITCHPROF"; case EXTCTL_CMD_GETPROF: return "GETPROF"; } return "Unknown command code"; } static int rs232_read(uint8_t *data, unsigned int timeout) { int err = 0; ssize_t res; int use_timeout = ((timeout != 0) && (timeout != ~0)); do { res = read(fd, data, sizeof(*data)); if (res == sizeof(*data)) break; err = errno; if (err == 0 || err == ESPIPE) { if (use_timeout) timeout--; msleep(100); } else { perror("RS232 read"); return err; } } while (timeout); if (use_timeout && (timeout == 0)) { fprintf(stderr, "RS232 read timeout. Is the device connected?\n"); return ETIMEDOUT; } return err; } static int rs232_write(uint8_t data) { int err = 0; ssize_t res; res = write(fd, &data, sizeof(data)); if (res != sizeof(data)) { err = errno; perror("rs232_write"); } return err; } static uint8_t crc8(uint8_t crc, uint8_t data) { uint8_t i, tmp; for (i = 8; i > 0; i--) { tmp = ((crc ^ data) & 0x01); if (tmp) { crc ^= 0x18; crc >>= 1; crc |= 0x80; } else crc >>= 1; data >>= 1; } return crc; } /* Create an XOR checksum out of the buffer. */ static uint8_t checksum_buffer(const void *_buf, uint8_t size) { uint8_t crc = 0; const uint8_t *buf = _buf; uint8_t i; for (i = 0; i < size; i++) crc = crc8(crc, buf[i]); crc ^= 0xFF; return crc; } static int send_command(const struct extctl_command *_cmd, uint8_t *result) { const uint8_t *buf; int i, err; struct extctl_command cmd; memcpy(&cmd, _cmd, sizeof(cmd)); if (cmdargs.debug) { printf("Command %s(0x%08X): ", command_string(cmd.id), cmd.data); fflush(stdout); } cmd.data = cpu_to_le32(cmd.data); cmd.checksum = checksum_buffer(&cmd, sizeof(cmd) - sizeof(uint8_t)); buf = (const uint8_t *)(&cmd); for (i = 0; i < sizeof(cmd); i++) { err = rs232_write(buf[i]); if (err) return err; } err = rs232_read(result, 10); if (err) return err; if (cmdargs.debug) printf("%s\n", result_string(*result)); return 0; } static int read_reply(struct extctl_reply *reply) { int err, i; uint8_t *buf = (uint8_t *)reply; uint8_t crc; for (i = 0; i < sizeof(*reply); i++) { err = rs232_read(buf + i, 10); if (err) return err; } crc = checksum_buffer(reply, sizeof(*reply) - sizeof(uint8_t)); reply->data = le32_to_cpu(reply->data); if (crc != reply->checksum) { fprintf(stderr, "Reply checksum error. was 0x%02X, expected 0x%02X\n", reply->checksum, crc); return -1; } return 0; } static int rs232_init(void) { int res, err = -1; struct termios ios; uint8_t data; fd = open(cmdargs.tty, O_RDWR | O_NOCTTY); if (fd < 0) { fprintf(stderr, "Open %s: %s\n", cmdargs.tty, strerror(errno)); goto out; } tcgetattr(fd, &ios); cfsetispeed(&ios, BAUDRATE); cfsetospeed(&ios, BAUDRATE); cfmakeraw(&ios); ios.c_cflag &= ~(CSIZE | CLOCAL | CREAD | CSTOPB | PARENB | PARODD); ios.c_cflag |= CS8 | CLOCAL | CREAD | PARENB; ios.c_iflag &= ~(INPCK | PARMRK | IXON | IXOFF | BRKINT | INLCR | IGNCR | ICRNL | IUCLC | IMAXBEL | ISTRIP | IGNBRK | IGNPAR); ios.c_iflag |= IGNBRK; ios.c_lflag &= ~(NOFLSH | ECHO | ECHOE | ECHOK | ECHONL | XCASE | ECHOCTL | ECHOPRT | ECHOKE | PENDIN | ICANON | ISIG); ios.c_lflag |= 0; ios.c_cc[VINTR] = 0; /* Ctrl-c */ ios.c_cc[VQUIT] = 0; /* Ctrl-\ */ ios.c_cc[VERASE] = 0; /* del */ ios.c_cc[VKILL] = 0; /* @ */ ios.c_cc[VEOF] = 4; /* Ctrl-d */ ios.c_cc[VTIME] = 0; /* inter-character timer unused */ ios.c_cc[VMIN] = 0; /* blocking read until 1 character arrives */ ios.c_cc[VSWTC] = 0; /* '\0' */ ios.c_cc[VSTART] = 0; /* Ctrl-q */ ios.c_cc[VSTOP] = 0; /* Ctrl-s */ ios.c_cc[VSUSP] = 0; /* Ctrl-z */ ios.c_cc[VEOL] = 0; /* '\0' */ ios.c_cc[VREPRINT] = 0; /* Ctrl-r */ ios.c_cc[VDISCARD] = 0; /* Ctrl-u */ ios.c_cc[VWERASE] = 0; /* Ctrl-w */ ios.c_cc[VLNEXT] = 0; /* Ctrl-v */ ios.c_cc[VEOL2] = 0; /* '\0' */ tcflush(fd, TCIFLUSH); err = tcsetattr(fd, TCSANOW, &ios); if (err < 0) { fprintf(stderr, "Set tty attributes on %s: %s\n", cmdargs.tty, strerror(errno)); goto err_close; } err = tcflow(fd, TCION); if (err) { fprintf(stderr, "Enable input on %s: %s\n", cmdargs.tty, strerror(errno)); goto err_close; } err = tcflow(fd, TCOON); if (err) { fprintf(stderr, "Enable output on %s: %s\n", cmdargs.tty, strerror(errno)); goto err_close; } /* Drain the RX buffer. */ while (1) { res = read(fd, &data, sizeof(data)); if (!res) break; } err = 0; out: return err; err_close: close(fd); return err; } static void rs232_exit(void) { close(fd); } static int cmd_getprof(unsigned int *profile) { struct extctl_command command = { .id = EXTCTL_CMD_GETPROF, }; struct extctl_reply reply; uint8_t result; int err; err = send_command(&command, &result); if (!err && result == EXTCTL_CMD_RESULT_OK) { err = read_reply(&reply); if (!err) { *profile = reply.data >> 16; return 0; } } fprintf(stderr, "Failed to get active profile\n"); return -1; } static int cmd_switchprof(unsigned int profile) { struct extctl_command command = { .id = EXTCTL_CMD_SWITCHPROF, .data = profile << 16, }; uint8_t result; int err; err = send_command(&command, &result); if (err || result != EXTCTL_CMD_RESULT_OK) { fprintf(stderr, "Failed to switch to profile %u\n", profile); return -1; } return 0; } static int cmd_getvoltage(unsigned int profile, unsigned int *voltage) { struct extctl_command command = { .id = EXTCTL_CMD_GETVOLTAGE, .data = profile << 16, }; struct extctl_reply reply; uint8_t result; int err; err = send_command(&command, &result); if (!err && result == EXTCTL_CMD_RESULT_OK) { err = read_reply(&reply); if (!err) { *voltage = reply.data; return 0; } } fprintf(stderr, "Failed to get profile %u voltage\n", profile); return -1; } static int cmd_setvoltage(unsigned int profile, unsigned int voltage) { struct extctl_command command = { .id = EXTCTL_CMD_SETVOLTAGE, .data = (profile << 16) | (voltage & 0xFFFF), }; uint8_t result; int err; err = send_command(&command, &result); if (err || result != EXTCTL_CMD_RESULT_OK) { fprintf(stderr, "Failed to set voltage to %u in profile %u\n", voltage, profile); return -1; } return 0; } static int cmd_getmaxcur(unsigned int profile, unsigned int *maxcurrent) { struct extctl_command command = { .id = EXTCTL_CMD_GETMAXCUR, .data = profile << 16, }; struct extctl_reply reply; uint8_t result; int err; err = send_command(&command, &result); if (!err && result == EXTCTL_CMD_RESULT_OK) { err = read_reply(&reply); if (!err) { *maxcurrent = reply.data; return 0; } } fprintf(stderr, "Failed to get profile %u max-current\n", profile); return -1; } static int cmd_setmaxcur(unsigned int profile, unsigned int maxcurrent) { struct extctl_command command = { .id = EXTCTL_CMD_SETMAXCUR, .data = (profile << 16) | (maxcurrent & 0xFFFF), }; uint8_t result; int err; err = send_command(&command, &result); if (err || result != EXTCTL_CMD_RESULT_OK) { fprintf(stderr, "Failed to set max-current to %u in profile %u\n", maxcurrent, profile); return -1; } return 0; } static int calib_measure(struct es51984 *es, int voltage, double *val) { struct es51984_sample sample; unsigned int retries, i; double value = 0.0l; int err; const char *message = NULL; err = es51984_discard(es); if (err) { fprintf(stderr, "Failed to discard samples\n"); return -1; } retries = 5; retry: if (retries-- == 0) { if (message) fprintf(stderr, "%s\n", message); return -1; } value = 0.0l; for (i = 0; i < 3; i++) { err = es51984_get_sample(es, &sample, 1); if (err == -EPIPE) { fprintf(stderr, "Lost synchronization to multimeter.\n"); return -1; } if (err) { fprintf(stderr, "Failed to get sample from multimeter.\n"); return -1; } if (sample.overflow) { message = "Multimeter has overflow condition. Aborting."; goto retry; } if (sample.batt_low) { static int warned; if (!warned) { warned = 1; fprintf(stderr, "Warning: Multimeter battery low! This may " "affect the calibration accuracy.\n"); } } if (voltage) { /* Voltage measurement */ if (sample.function != ES51984_FUNC_VOLTAGE) { fprintf(stderr, "Voltage measurement: The multimeter is not " "switched to voltage measurement. Aborting.\n"); return -1; } value += sample.value; } else { /* Current measurement */ if (sample.function != ES51984_FUNC_AUTO_CURRENT) { fprintf(stderr, "Voltage measurement: The multimeter is not " "switched to auto-current measurement. Aborting.\n"); return -1; } value += sample.value; } } value /= 3.0l; *val = value; return 0; } static int calibrate_voltage(struct es51984 *es) { unsigned int voltage; double measured; int err; const unsigned int vstep = 1000; voltage = vstep; while (voltage <= 30000) { err = cmd_setvoltage(0, voltage); if (err) goto error; msleep(100); err = calib_measure(es, 1, &measured); if (err) goto error; printf("Set %u, measured %lf\n", voltage, measured); //TODO voltage += vstep; } return 0; error: fprintf(stderr, "Voltage calibration failed\n"); return -1; } static int calibrate_current(struct es51984 *es) { //TODO return 0; } static int calibrate_device(void) { struct es51984 *es = NULL; int retval = 0, err; unsigned int old_profile, old_prof0_voltage, old_prof0_maxcurrent; err = cmd_getprof(&old_profile); err |= cmd_getvoltage(0, &old_prof0_voltage); err |= cmd_getmaxcur(0, &old_prof0_maxcurrent); if (err) { fprintf(stderr, "Failed to fetch active PSU parameters\n"); return -1; } err = cmd_switchprof(0); if (err) return -1; /* Initialize the multimeter. */ es = es51984_init(ES51984_BOARD_AMPROBE_35XPA, cmdargs.meter_node); if (!es) goto err_restore; err = es51984_sync(es); if (err) { fprintf(stderr, "Is the Amprobe 35XP-A multimeter connected to %s" " and turned on?\n", cmdargs.meter_node); goto err_restore; } err = calibrate_voltage(es); if (err) goto err_restore; err = calibrate_current(es); if (err) goto err_restore; restore: es51984_exit(es); err = cmd_switchprof(old_profile); err |= cmd_setvoltage(0, old_prof0_voltage); err |= cmd_setmaxcur(0, old_prof0_maxcurrent); if (err) { fprintf(stderr, "Failed to restore PSU parameters\n"); if (!retval) retval = -1; } return retval; err_restore: retval = -1; goto restore; } static int send_commands(void) { struct command *cmd, *next; struct extctl_reply reply; int err; uint8_t result; cmd = cmdargs.commands; while (cmd) { if (cmd->calibrate) { err = calibrate_device(); if (err) goto error; goto next; } err = send_command(&cmd->d, &result); if (err) goto error; if (result != EXTCTL_CMD_RESULT_OK) { fprintf(stderr, "Command %s(0x%08X) failed: %s\n", command_string(cmd->d.id), cmd->d.data, result_string(result)); goto next; } switch (cmd->d.id) { case EXTCTL_CMD_GETVOLTAGE: { err = read_reply(&reply); if (err) goto error; if (cmdargs.verbose) { printf("Voltage in profile %u: ", ((cmd->d.data & 0x00FF0000) >> 16) + 1); } printf("%u.%02uV\n", reply.data / 1000, (reply.data % 1000) / 10); break; } case EXTCTL_CMD_GETMAXCUR: { err = read_reply(&reply); if (err) goto error; if (cmdargs.verbose) { printf("Maximum current in profile %u: ", ((cmd->d.data & 0x00FF0000) >> 16) + 1); } printf("%u.%02uA\n", reply.data / 1000, (reply.data % 1000) / 10); break; } case EXTCTL_CMD_GETPROF: { err = read_reply(&reply); if (err) goto error; if (cmdargs.verbose) printf("Active profile: "); printf("%u\n", ((reply.data & 0x00FF0000) >> 16) + 1); break; }} next: next = cmd->next; free(cmd); cmd = next; } return 0; error: /* Free all commands */ while (cmd) { next = cmd->next; free(cmd); cmd = next; } return err; } static void version(void) { printf("OpenPSU Remote Control utility\n" "Version " VERSION ", Copyright 2007 Michael Buesch \n"); } static void usage(int argc, char **argv) { version(); printf("\nUsage: %s [OPTIONS]\n",argv[0]); printf("\n\n" " -v|--version Print the version number\n" " -h|--help Print this help text\n" " -d|--debug Print debugging messages\n" " -t|--tty The serial device (/dev/ttySx)\n" " -e|--verbose Verbose text output\n" "\n" " -c|--calibrate METER Start a device calibration using Amprobe 35XP-A multimeter\n" " METER is the /dev/ttySX device node the multimeter\n" " is connected to\n" "\n" " -V|--cmd-setvoltage prof,volt Set the voltage in a profile\n" " -W|--cmd-getvoltage prof Get the voltage from a profile\n" " -C|--cmd-setmaxcur prof,curr Set the MaxCurrent in a profile\n" " -D|--cmd-getmaxcur prof Get the MaxCurrent from a profile\n" " -P|--cmd-switchprof prof Switch to a profile\n" " -Q|--cmd-getprof Get the active profile\n" " -N|--cmd-nop Do nothing\n" ); } static struct command * alloc_command(void) { struct command *ret; ret = malloc(sizeof(struct command)); if (!ret) { fprintf(stderr, "Out of memory\n"); exit(1); } memset(ret, 0, sizeof(struct command)); return ret; } static void add_command(struct command *cmd) { struct command *c; if (!cmdargs.commands) { cmdargs.commands = cmd; return; } c = cmdargs.commands; while (c->next) c = c->next; c->next = cmd; } /* Convert all commas to NUL */ static void comma_to_nul(char *str) { char *c = str; while (1) { c = strchr(c, ','); if (!c) break; *c = '\0'; c++; } } enum arg_type { ARG_PROFILE_NUM, /* --cmd-foo profilenum */ ARG_PROFILE_VOLTAGE, /* --cmd-foo profilenum,voltageval */ ARG_PROFILE_CURRENT, /* --cmd-foo profilenum,currentval */ }; static int parse_arg(struct command *cmd, char *str, enum arg_type type) { size_t len; unsigned long l; float f; char *tail; cmd->d.data = 0; len = strlen(str); if (!len) { fprintf(stderr, "No arg given\n"); return -1; } comma_to_nul(str); /* Profile number arg */ switch (type) { case ARG_PROFILE_NUM: case ARG_PROFILE_VOLTAGE: case ARG_PROFILE_CURRENT: { size_t plen = strlen(str); if (!plen) { fprintf(stderr, "No profile arg given\n"); return -1; } l = strtoul(str, &tail, 0); if (*tail != '\0') { fprintf(stderr, "Invalid profile arg given\n"); return -1; } if (l < 1) { fprintf(stderr, "Profile arg value too small\n"); return -1; } l--; if (l > 0xFF) { fprintf(stderr, "Profile arg value too big\n"); return -1; } cmd->d.data |= ((l & 0xFF) << 16); len -= plen; str += plen + 1; break; }} /* Voltage or Current arg */ switch (type) { case ARG_PROFILE_VOLTAGE: { unsigned int millivolt; if (!len) { fprintf(stderr, "No Voltage arg given\n"); return -1; } if (sscanf(str, "%f", &f) != 1) { fprintf(stderr, "Invalid Voltage arg given\n"); return -1; } if (f > 30) { fprintf(stderr, "Voltage arg value too big\n"); return -1; } millivolt = f * 1000; cmd->d.data |= (millivolt & 0xFFFF); break; } case ARG_PROFILE_CURRENT: { unsigned int milliamps; if (!len) { fprintf(stderr, "No Current arg given\n"); return -1; } if (sscanf(str, "%f", &f) != 1) { fprintf(stderr, "Invalid Current arg given\n"); return -1; } if (f > 3) { fprintf(stderr, "Current arg value too big\n"); return -1; } milliamps = f * 1000; cmd->d.data |= (milliamps & 0xFFFF); break; } case ARG_PROFILE_NUM: /* nothing */ break; } return 0; } static int parse_args(int argc, char **argv) { static const struct option long_options[] = { { "help", no_argument, NULL, 'h', }, { "version", no_argument, NULL, 'v', }, { "debug", no_argument, NULL, 'd', }, { "tty", required_argument, NULL, 't', }, { "verbose", no_argument, NULL, 'e', }, { "calibrate", required_argument, NULL, 'c', }, { "cmd-nop", no_argument, NULL, 'N', }, { "cmd-setvoltage", required_argument, NULL, 'V', }, { "cmd-getvoltage", required_argument, NULL, 'W', }, { "cmd-setmaxcur", required_argument, NULL, 'C', }, { "cmd-getmaxcur", required_argument, NULL, 'D', }, { "cmd-switchprof", required_argument, NULL, 'P', }, { "cmd-getprof", no_argument, NULL, 'Q', }, { NULL, }, }; int c, idx, err; struct command *cmd; while (1) { c = getopt_long(argc, argv, "hvdt:ec:NV:W:C:D:P:Q", long_options, &idx); if (c == -1) break; switch (c) { case 'h': usage(argc, argv); return 1; case 'v': version(); return 1; case 'd': cmdargs.debug = 1; break; case 't': cmdargs.tty = optarg; break; case 'e': cmdargs.verbose = 1; break; case 'c': if (cmdargs.meter_node) { fprintf(stderr, "Can only have one --calibrate parameter\n"); return -1; } cmdargs.meter_node = optarg; cmd = alloc_command(); cmd->calibrate = 1; add_command(cmd); break; case 'N': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_NOP; add_command(cmd); break; case 'V': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_SETVOLTAGE; err = parse_arg(cmd, optarg, ARG_PROFILE_VOLTAGE); if (err) return err; add_command(cmd); break; case 'W': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_GETVOLTAGE; err = parse_arg(cmd, optarg, ARG_PROFILE_NUM); if (err) return err; add_command(cmd); break; case 'C': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_SETMAXCUR; err = parse_arg(cmd, optarg, ARG_PROFILE_CURRENT); if (err) return err; add_command(cmd); break; case 'D': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_GETMAXCUR; err = parse_arg(cmd, optarg, ARG_PROFILE_NUM); if (err) return err; add_command(cmd); break; case 'P': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_SWITCHPROF; err = parse_arg(cmd, optarg, ARG_PROFILE_NUM); if (err) return err; add_command(cmd); break; case 'Q': cmd = alloc_command(); cmd->d.id = EXTCTL_CMD_GETPROF; add_command(cmd); break; default: return -1; } } if (!cmdargs.commands) { usage(argc, argv); return -1; } return 0; } int main(int argc, char **argv) { int ret = 1; int err; err = parse_args(argc, argv); if (err > 0) ret = 0; if (err) goto out_rs232_exit; err = rs232_init(); if (err) goto out; err = send_commands(); if (err) goto out_rs232_exit; ret = 0; out_rs232_exit: rs232_exit(); out: return ret; }