/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * For a copy of the GNU Library General Public License * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. or go to http://www.gnu.org * * $Id: mlogger.c,v 1.2 2005/04/15 16:25:25 mefemal Exp $ */ /* * Use select to read from 1 file descriptor (and wait on kbd input for stop) * based on Serial Programming Guide for POSIX Operating Systems */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "passivesock.h" #include "meter.h" /* thread stack size for servicing new connections */ #define THREAD_STACK_SIZE 65535L /* for power measurements, simulate this number for volts */ #ifndef SIM_VOLTS # define SIM_VOLTS 110.0 #endif /* maximum number of connections handled by server */ #ifndef MAXCONNS # define MAXCONNS 20 #endif extern int errno; extern char *optarg; extern int optind, opterr, optopt; extern int mdebug; /* prototypes */ void *request_handler(void *arg); void integrate(struct timeval begin, struct timeval end, mm_t *mm, mm_t *volts, float *interval, float *energy); void initialize(pthread_t *thread, unsigned short port); void *client_req(void *arg); void show_usage(char *argv[]); int monitor(int type, char *serialport, FILE *output, int count, float sim_volts, unsigned int to_sec); typedef struct { unsigned int id; pthread_t thread; short int sock; struct sockaddr_in client; } connection_t; static connection_t *connection; static float power; static unsigned int cumulative_energy; static int running = 1; static int console = 0; static mm_t last_mm = { 0.0, 0, 0 }; void send_reading(int s) { static unsigned int msgsz = sizeof(netmeter_req_t); netmeter_req_t msg; int r; msg.cmd = NETMETER_CMD_READ; msg.units = last_mm.units; msg.value = (void *)(unsigned int)(last_mm.value * 100000); if ((r = write(s, &msg, msgsz)) != msgsz) fprintf(stderr, "send_reading: wrote only %i of %i bytes\n", r, msgsz); } void send_energy(int s) { static unsigned int msgsz = sizeof(netmeter_req_t); netmeter_req_t msg; int r; msg.cmd = NETMETER_CMD_ENERGY; msg.value = (void *)cumulative_energy; msg.units = MUNITS_JOULES; if ((r = write(s, &msg, msgsz)) != msgsz) fprintf(stderr, "send_energy: wrote only %i of %i bytes\n", r, msgsz); } /* * Try to avoid any issues with network packets and data types and send * the return data last measured for power as the truncated version of * the real float value (milliwatts). */ void send_power(int s) { static unsigned int msgsz = sizeof(netmeter_req_t); netmeter_req_t msg; int r; mm_t mm; float value; /* construct the return value */ mm.value = power; mm.units = MUNITS_WATTS; value = meter_to_milli(&mm); /* setup the return data */ msg.cmd = NETMETER_CMD_POWER; msg.value = (void *)(unsigned int)value; msg.units = MUNITS_MILLI | MUNITS_WATTS; if ((r = write(s, &msg, msgsz)) != msgsz) fprintf(stderr, "send_power: wrote only %i of %i bytes\n", r, msgsz); } void *request_handler(void *arg) { int s; struct sockaddr_in raddr; int rlen; int i; pthread_attr_t thr_attr; unsigned short port; struct pollfd pfd; port = (unsigned short)(int)arg; printf("listening on port %i for clients\n", port); pfd.fd = s = tcp_passive_port(port, 5); if (s == -1) return (void *)-1; pfd.events = POLLIN; if (pthread_attr_init(&thr_attr)) { perror("pthread_attr_init"); exit(1); } if (pthread_attr_setstacksize(&thr_attr, THREAD_STACK_SIZE)) { perror("pthread_attr_setstacksize"); exit(1); } while (running) { memset(&raddr, 0, sizeof(raddr)); rlen = sizeof(raddr); for (i = 0; ; i++) { if (i > MAXCONNS) { struct timespec req, rem; req.tv_sec = 1; req.tv_nsec = 0; nanosleep(&req, &rem); i = 0; continue; } if (connection[i].sock == 0) break; } poll(&pfd, 1, 1000); if (pfd.revents == POLLIN) { connection[i].sock = accept(s, (struct sockaddr *)&raddr, &rlen); if (connection[i].sock < 0) { connection[i].sock = 0; fprintf(stderr, "connection %d: error accept: %s\n", i, strerror(errno)); continue; } else { if (pthread_create(&connection[i].thread, &thr_attr, client_req, (void *)i) != 0) { fprintf(stderr, "connection %d: error pthread_create: %s\n", i, strerror(errno)); close(connection[i].sock); connection[i].sock = 0; } } } } close(s); return 0; } void *client_req(void *arg) { netmeter_req_t msg; /* message received from peer */ int id = (int)arg; /* connection id number in array */ ssize_t size = 0; /* amount of data read from socket */ struct sockaddr_in fsin; /* socket info of connected peer */ int alen = sizeof(fsin); /* useful for debugging */ int connected = 1; /* loop while connected */ struct timeval start, stop; /* track timings for connections */ unsigned int hr = 0, min = 0, sec; /* connection service time info */ unsigned long commands = 0; /* number of commands serviced */ gettimeofday(&start, NULL); connection[id].id = pthread_self(); pthread_detach(connection[id].id); if (getpeername(connection[id].sock, (struct sockaddr *)&fsin, &alen) == -1) return (void *)-1; /* client abort */ printf("connection %d: start client %s\n", id, inet_ntoa(fsin.sin_addr)); while (running && connected) { memset(&msg, 0, sizeof(msg)); if ((size = read(connection[id].sock, &msg, sizeof(msg))) == -1 || size != sizeof(netmeter_req_t)) { fprintf(stderr, "connection %d: error request size %d errno %d\n", id, size, errno); connected = 0; } else if (!running) { connected = 0; } else { switch(msg.cmd) { case NETMETER_CMD_ENERGY: send_energy(connection[id].sock); commands++; break; case NETMETER_CMD_POWER: send_power(connection[id].sock); commands++; break; case NETMETER_CMD_READ: send_reading(connection[id].sock); commands++; break; case NETMETER_CMD_RESET: cumulative_energy = 0; commands++; break; case NETMETER_CMD_QUIT: /* gracefully exit */ connected = 0; break; default: fprintf(stderr, "connection %d: error unknown command %i\n", id, msg.cmd); connected = 0; break; } } } close(connection[id].sock); connection[id].sock = 0; gettimeofday(&stop, NULL); sec = stop.tv_sec - start.tv_sec; if (sec >= 3600) { /* seconds per hr */ hr = sec / 3600; sec -= hr * 3600; } if (sec >= 60) { /* seconds per min */ min = sec / 60; sec -= min * 60; } printf("connection %d: stop service time %uh:%um:%.3fs for %lu command%s\n", id, hr, min, sec + (stop.tv_usec / 1000000.0), commands, commands == 1 ? "": "s"); pthread_exit(0); } /* termination signal received */ void signal_exit(int sig) { running = 0; } /* initialize the signal handlers and additional threads */ void initialize(pthread_t *thread, unsigned short port) { signal(SIGINT, signal_exit); if (port == 0) return; connection = (connection_t *)calloc(MAXCONNS, sizeof(connection_t)); if (connection == NULL) { fprintf(stderr, "out of memory creating %d connections\n", MAXCONNS); exit(1); } if (pthread_create(thread, NULL, request_handler, (void *)(int)port) != 0) { fprintf(stderr, "unable to start network listener: %s\n", strerror(errno)); exit(1); } } /* perform simple rectangular integration */ void integrate(struct timeval begin, struct timeval end, mm_t *mm, mm_t *volts, float *interval, float *energy) { if (mm == NULL || volts == NULL) return; if (!(mm->units & MUNITS_WATTS)) power = mm->value * volts->value; else power = mm->value; *interval = (end.tv_sec - begin.tv_sec) + ((end.tv_usec - begin.tv_usec) / 1000000.0); *energy = power * (*interval); cumulative_energy += *energy; } int main(int argc, char *argv[]) { int count = 0; FILE *output = NULL; int c; float volts = SIM_VOLTS; char *serialport = SERIAL_PORT; unsigned short port = METER_TCP_PORT; pthread_t thread; unsigned int to_sec = METER_TIMEOUT; int type = METER_TYPE; int rc; opterr = 0; while ((c = getopt(argc, argv, "m:t:s:p:v:l:c:doh")) != -1) { switch (c) { case 'm': if ((type = meter_type(optarg)) == 0) { fprintf(stderr, "unknown meter %s for -m\n", optarg); exit(1); } break; case 'd': mdebug = 1; break; case 't': to_sec = atoi(optarg); break; case 'p': port = atoi(optarg); break; case 's': serialport = strdup(optarg); break; case 'v': volts = atof(optarg); break; case 'l': if (optarg[0] != '-') { output = fopen(optarg, "a"); if (output == NULL) { perror(argv[1]); exit(1); } } break; case 'o': console = 1; break; case 'c': count = atoi(optarg); break; case 'h': show_usage(argv); exit(0); default: fprintf(stderr, "unknown option -%c, use -h for help\n", optopt); exit(1); } } initialize(&thread, port); rc = monitor(type, serialport, output, count, volts, to_sec); if (port) { printf("disconnecting any clients...\n"); pthread_join(thread, NULL); } printf("finished.\n"); if (output != NULL) fclose(output); return rc; } int monitor(int type, char *serialport, FILE *output, int count, float sim_volts, unsigned int to_sec) { struct termios oldtio; int fd; int rc = 0; mm_t *mm, volts; struct timeval begin_tv, tv, last_tv; int loops = 0; int num; int i; if (count) loops = count - 1; if ((num = meter_alloc_mms(type, &mm)) == -1) { fprintf(stderr, "meter_alloc_mms: %s\n", strerror(errno)); return -1; } if ((fd = meter_init(serialport, &oldtio, type)) == -1) { fprintf(stderr, "meter_init: %s\n", strerror(errno)); running = 0; return -1; } volts.value = sim_volts; for (i = 0; i < num; i++) mm[i].to_sec = to_sec; volts.to_sec = to_sec; cumulative_energy = 0; gettimeofday(&begin_tv, NULL); memcpy(&last_tv, &begin_tv, sizeof(struct timeval)); while (running && loops >= 0) { rc = meter_read(fd, type, mm); if (rc == -1) { fprintf(stderr, "meter_read: %s\n", strerror(errno)); loops = -1; break; } last_mm.units = mm[0].units; last_mm.value = mm[0].value; if (rc != 1 && console) { /* no timeout and need to display */ for (i = 0; i < num; i++) printf("%f %s%s ", mm[i].value, meter_units_prefix(&mm[0]), meter_units_measure(&mm[i])); printf("\n"); } if (mm[0].value > 0.0) { float interval, energy; if (gettimeofday(&tv, NULL) == -1) { fprintf(stderr, "gettimeofday error: %s\n", strerror(errno)); loops = -1; } else { integrate(last_tv, tv, &mm[0], &volts, &interval, &energy); if (output != NULL) fprintf(output, "%u %f %f %f %f %f %f\n", (unsigned int)tv.tv_sec, (tv.tv_sec - begin_tv.tv_sec) + (tv.tv_usec / 1000000.0), mm[0].value, volts.value, power, interval, energy); memcpy(&last_tv, &tv, sizeof(struct timeval)); } if (count) loops--; if (output != NULL && (loops % 10 == 0)) fflush(output); } } running = 0; meter_close(fd, &oldtio); return rc; } void show_usage(char *argv[]) { char *name = basename(argv[0]); printf("Usage: %s [-l log] [-c count] [-v volts] [-s serialport] [-p port] [-t timeout] [-m meter] [-o] [-d]\n", name); printf(" %s -h\n", name); printf("\t-o Output the value read to standard out\n"); printf("\t-d Turn on debugging\n"); printf("\t-t timeout Timeout waiting for data from meter (sec) [%d]\n", METER_TIMEOUT); printf("\t-l log Write data to the indicated log file\n"); printf("\t-c count Read count values and then stop, 0 for no limit [0]\n"); printf("\t-v volts Simulate the indicated volts for power [%.1f]\n", SIM_VOLTS); printf("\t-m meter Name of the meter (see README) [%s]\n", meter_name(METER_TYPE)); printf("\t-s serialport Serial port for meter [%s]\n", SERIAL_PORT); printf("\t-p port Network port for clients, 0 to disable [%d]\n", METER_TCP_PORT); printf("\t-h This help screen\n"); }