/* * extech - Program for controlling the extech Device * This file is part of PowerTOP * * Based on earlier client by Patrick Mochel for Wattsup Pro device * Copyright (c) 2005 Patrick Mochel * Copyright (c) 2006 Intel Corporation * Copyright (c) 2011 Intel Corporation * * Authors: * Patrick Mochel * Venkatesh Pallipadi * Arjan van de Ven * * Thanks to Rajiv Kapoor for finding out the DTR, RTS line bits issue below * Without that this program would never work. * * * This program file 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; version 2 of the License. * * 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. * * You should have received a copy of the GNU General Public License * along with this program in a file named COPYING; if not, write to the * Free Software Foundation, Inc, * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * or just google for it. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "measurement.h" #include "extech.h" #include #include #include #include #include using namespace std; struct packet { char buf[256]; char op[32]; double watts; int len; }; static int open_device(const char *device_name) { struct stat s; int ret; ret = stat(device_name, &s); if (ret < 0) return -1; if (!S_ISCHR(s.st_mode)) return -1; ret = access(device_name, R_OK | W_OK); if (ret) return -1; ret = open(device_name, O_RDWR | O_NONBLOCK | O_NOCTTY); if (ret < 0) return -1; return ret; } static int setup_serial_device(int dev_fd) { struct termios t; int ret; int flgs; 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 = B9600 | CS8 | CREAD | CLOCAL; t.c_oflag = 0; t.c_lflag = 0; t.c_cc[VMIN] = 2; t.c_cc[VTIME] = 0; t.c_iflag &= ~(IXON | IXOFF | IXANY); t.c_oflag &= ~(IXON | IXOFF | IXANY); ret = tcsetattr(dev_fd, TCSANOW, &t); if (ret) return ret; /* * Root caused by Rajiv Kapoor. Without this extech reads * will fail */ /* get DTR and RTS line bits settings */ ioctl(dev_fd, TIOCMGET, &flgs); /* set DTR to 1 and RTS to 0 */ flgs |= TIOCM_DTR; flgs &= ~TIOCM_RTS; ioctl(dev_fd, TIOCMSET, &flgs); return 0; } static unsigned int decode_extech_value(unsigned char byt3, unsigned char byt4, char *a) { unsigned int input = ((unsigned int)byt4 << 8) + byt3; unsigned int i; unsigned int idx; unsigned char revnum[] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf}; unsigned char revdec[] = { 0x0, 0x2, 0x1, 0x3}; unsigned int digit_map[] = {0x2, 0x3c, 0x3c0, 0x3c00}; unsigned int digit_shift[] = {1, 2, 6, 10}; unsigned int sign; unsigned int decimal; /* this is basically BCD encoded floating point... but kinda weird */ decimal = (input & 0xc000) >> 14; decimal = revdec[decimal]; sign = input & 0x1; idx = 0; if (sign) a[idx++] = '+'; else a[idx++] = '-'; /* first digit is only one or zero */ a[idx] = '0'; if ((input & digit_map[0]) >> digit_shift[0]) a[idx] += 1; idx++; /* Reverse the remaining three digits and store in the array */ for (i = 1; i < 4; i++) { int dig = ((input & digit_map[i]) >> digit_shift[i]); dig = revnum[dig]; if (dig > 0xa) goto error_exit; a[idx++] = '0' + dig; } /* Fit the decimal point where appropriate */ for (i = 0; i < decimal; i++) a[idx - i] = a[idx - i - 1]; a[idx - decimal] = '.'; a[++idx] = '0'; a[++idx] = '\0'; return 0; error_exit: return -1; } static int parse_packet(struct packet * p) { int i; int ret; p->buf[p->len] = '\0'; /* * First character in 5 character block should be '02' * Fifth character in 5 character block should be '03' */ for (i = 0; i < 4; i++) { if (p->buf[i * 0] != 2 || p->buf[i * 0 + 4] != 3) { printf("Invalid packet\n"); return -1; } } for (i = 0; i < 1; i++) { ret = decode_extech_value(p->buf[5 * i + 2], p->buf[5 * i + 3], &(p->op[8 * i])); if (ret) { printf("Invalid packet, conversion failed\n"); return -1; } p->watts = strtod( &(p->op[8 * i]), NULL); } return 0; } static double extech_read(int fd) { struct packet p; fd_set read_fd; struct timeval tv; int ret; if (fd < 0) return 0.0; FD_ZERO(&read_fd); FD_SET(fd, &read_fd); tv.tv_sec = 0; tv.tv_usec = 500000; memset(&p, 0, sizeof(p)); ret = select(fd + 1, &read_fd, NULL, NULL, &tv); if (ret <= 0) return -1; ret = read(fd, &p.buf, 250); if (ret < 0) return ret; p.len = ret; if (!parse_packet(&p)) return p.watts; return -1000.0; } extech_power_meter::extech_power_meter(const char *extech_name) { rate = 0.0; strncpy(dev_name, extech_name, sizeof(dev_name)); int ret; fd = open_device(dev_name); if (fd < 0) return; ret = setup_serial_device(fd); if (ret) { close(fd); fd = -1; return; } } void extech_power_meter::measure(void) { /* trigger the extech to send data */ write(fd, " ", 1); rate = extech_read(fd); } void extech_power_meter::sample(void) { ssize_t ret; struct timespec tv; tv.tv_sec = 0; tv.tv_nsec = 200000000; while (!end_thread) { nanosleep(&tv, NULL); /* trigger the extech to send data */ ret = write(fd, " ", 1); if (ret < 0) continue; sum += extech_read(fd); samples++; } } extern "C" { void* thread_proc(void *arg) { class extech_power_meter *parent; parent = (class extech_power_meter*)arg; parent->sample(); return 0; } } void extech_power_meter::end_measurement(void) { end_thread = 1; pthread_join( thread, NULL); if (samples){ rate = sum / samples; } else measure(); } void extech_power_meter::start_measurement(void) { end_thread = 0; sum = samples = 0; if (pthread_create(&thread, NULL, thread_proc, this)) fprintf(stderr, "ERROR: extech measurement thread creation failed\n"); } double extech_power_meter::joules_consumed(void) { return rate; }