| /* |
| * 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 <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 <time.h> |
| #include <signal.h> |
| |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| |
| #include <sys/stat.h> |
| |
| #include "measurement.h" |
| #include "extech.h" |
| #include <iostream> |
| #include <fstream> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| 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; |
| } |