From 3bcfb8ccce9d1e07c1e3426a52cbf85e14dae936 Mon Sep 17 00:00:00 2001
From: Bram Veenboer <bram.veenboer@gmail.com>
Date: Mon, 31 Oct 2016 14:14:21 +0100
Subject: [PATCH] Add src

---
 src/Arduino/Makefile        |   9 ++
 src/Arduino/PowerSensor.ino | 240 ++++++++++++++++++++++++++++++++++++
 src/Makefile                |  30 +++++
 src/PowerSensor.cc          | 239 +++++++++++++++++++++++++++++++++++
 src/PowerSensor.h           |  70 +++++++++++
 src/psconfig                | 195 +++++++++++++++++++++++++++++
 src/testPowerSensor.cc      |  59 +++++++++
 7 files changed, 842 insertions(+)
 create mode 100644 src/Arduino/Makefile
 create mode 100644 src/Arduino/PowerSensor.ino
 create mode 100644 src/Makefile
 create mode 100644 src/PowerSensor.cc
 create mode 100644 src/PowerSensor.h
 create mode 100755 src/psconfig
 create mode 100644 src/testPowerSensor.cc

diff --git a/src/Arduino/Makefile b/src/Arduino/Makefile
new file mode 100644
index 0000000..a1f4a20
--- /dev/null
+++ b/src/Arduino/Makefile
@@ -0,0 +1,9 @@
+#NO_CORE=	1
+#MCU=		m328p
+#F_CPU=		16000000
+#VARIANT=	standard
+TARGET=		PowerSensor
+BOARD_TAG=	uno
+#ARDUINO_PORT=	/dev/ttyACM0
+ARDUINO_PORT=	/dev/ttyUSB0
+include /usr/share/arduino/Arduino.mk
diff --git a/src/Arduino/PowerSensor.ino b/src/Arduino/PowerSensor.ino
new file mode 100644
index 0000000..b0385ee
--- /dev/null
+++ b/src/Arduino/PowerSensor.ino
@@ -0,0 +1,240 @@
+//  Copyright (C) 2016
+//  ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
+//  P.O. Box 2, 7990 AA  Dwingeloo, the Netherlands
+
+//  This file is part of PowerSensor.
+
+//  PowerSensor 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 3 of the License, or
+//  (at your option) any later version.
+
+//  PowerSensor 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 PowerSensor.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#include <avr/eeprom.h>
+#include <Wire.h>
+#include <LiquidCrystal.h>
+
+LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
+
+
+#define MAX_SENSORS 5
+
+struct EEPROM
+{
+  struct Sensor {
+    double volt;
+    double type;
+    int16_t nullLevels;
+  } sensors[MAX_SENSORS];
+} eeprom __attribute__((section(".eeprom")));
+
+int64_t accumulated_values[MAX_SENSORS];
+int16_t levels[MAX_SENSORS];
+long    previous_time;
+double  weights[MAX_SENSORS];
+int16_t nullLevels[MAX_SENSORS];
+bool    inUse[MAX_SENSORS];
+
+
+int64_t total_accumulated_value()
+{
+  int64_t sum = 0;
+
+  for (uint8_t sensor = 0; sensor < MAX_SENSORS; sensor ++)
+    if (inUse[sensor])
+      sum += weights[sensor] * accumulated_values[sensor];
+
+  return sum;
+}
+
+
+inline uint8_t next_sensor(uint8_t current_sensor)
+{
+  do
+    if (++ current_sensor == MAX_SENSORS)
+      current_sensor = 0;
+  while (!inUse[current_sensor]);
+
+  return current_sensor;
+}
+
+
+ISR(ADC_vect)
+{
+  static uint8_t current_sensor = 0;
+
+  uint8_t low = ADCL, high = ADCH; // read in this order
+  levels[current_sensor] = (high << 8) | low;
+
+  // start next ADC ASAP.
+  uint8_t previous_sensor = current_sensor;
+  current_sensor = next_sensor(current_sensor);
+
+  ADMUX = _BV(REFS0) | (current_sensor + 1); // ADC0 reads the LCD buttons; skip it
+  ADCSRA |= _BV(ADSC); // start ADC conversion
+
+  // handle measured value
+
+  if (current_sensor <= previous_sensor) {
+    // we had one round of all sensors.  now handle them in one go
+    long measured_time = micros();
+    long time_difference = measured_time - previous_time;
+    previous_time = measured_time;
+
+    for (uint8_t sensor = 0; sensor < MAX_SENSORS; sensor ++)
+      // compiler will generate 16x16 bit multiply producing 32-bit result
+      accumulated_values[sensor] += (long) (int) time_difference * (levels[sensor] - nullLevels[sensor]);
+  }
+}
+
+
+void readState()
+{
+  noInterrupts();
+
+  int32_t total = total_accumulated_value() >> 16;
+  Serial.write((const uint8_t *) &total, sizeof total);
+  Serial.write((const uint8_t *) &previous_time, sizeof previous_time);
+
+  interrupts();
+}
+
+
+void configureFromEEPROM()
+{
+  EEPROM copy;
+  eeprom_read_block(&copy, &eeprom, sizeof copy);
+
+  for (unsigned i = 0; i < MAX_SENSORS; i ++) {
+    inUse[i] = copy.sensors[i].volt != 0;
+    weights[i] = inUse[i] ? copy.sensors[i].volt * 2.5 / copy.sensors[i].type : 0;
+    nullLevels[i] = copy.sensors[i].nullLevels;
+  }
+}
+
+
+void readConfig()
+{
+  EEPROM copy;
+  eeprom_read_block(&copy, &eeprom, sizeof copy);
+  Serial.write((const uint8_t *) &copy, sizeof copy);
+}
+
+
+void writeConfig()
+{
+  Serial.begin(115200); // serial comm from host to Arduino seems less reliable --> reduce baud rate
+  EEPROM copy;
+
+  for (unsigned i = 0; i < sizeof copy; i ++) {
+    while (Serial.available() == 0)
+      ;
+
+    ((uint8_t *) &copy)[i] = Serial.read();
+  }
+
+  Serial.begin(2000000); // probably only neecssary on Leonardo, that does not automatically reset after creating a new connection
+  eeprom_update_block(&copy, &eeprom, sizeof copy);
+  configureFromEEPROM();
+}
+
+
+void readLevels()
+{
+  Serial.write((const uint8_t *) levels, sizeof levels);
+}
+
+
+void serialEventRun()
+{
+  switch (Serial.read()) {
+    case 'l': readLevels();
+	      break;
+
+    case 'r': readConfig();
+	      break;
+
+    case 's': readState();
+	      break;
+
+    case 'w': writeConfig();
+	      break;
+  }
+}
+
+
+void setup()
+{
+  configureFromEEPROM();
+
+  lcd.begin(16, 2);
+  lcd.print("Sensor  :      W");
+  lcd.setCursor(0, 1);
+  lcd.print("Total:         W");
+
+  Serial.begin(2000000);
+
+  ADMUX   = _BV(REFS0) + 1;
+  ADCSRA |= _BV(ADIE); // enable ADC interrupts
+  ADCSRA |= _BV(ADSC); // start ADC conversion
+}
+
+
+void loop()
+{
+  static unsigned long previous_print_time;
+  static int64_t previous_value_of_next_sensor, previous_value_total;
+  static uint8_t count, current_sensor = next_sensor(MAX_SENSORS - 1), current_line;
+
+  noInterrupts();
+
+  unsigned long current_time = micros(), time_difference = current_time - previous_print_time;
+
+  if (time_difference > 333333) {
+    double power;
+
+    if (current_line == 0) {
+      // print top line; rotate among the used sensors
+      int64_t total = weights[current_sensor] * accumulated_values[current_sensor];
+
+      if ((++ count & 7) == 0)
+	current_sensor = next_sensor(current_sensor);
+
+      power = (double) (total - previous_value_of_next_sensor) / (512 * time_difference);
+      previous_value_of_next_sensor = weights[current_sensor] * accumulated_values[current_sensor];
+
+      interrupts();
+
+      if ((count & 7) == 1) {
+	lcd.setCursor(7, 0);
+	lcd.print((char) ('0' + current_sensor));
+      }
+    } else { // current_line == 1
+      // print bottom line; the sum of all sensors
+      int64_t total = total_accumulated_value();
+
+      interrupts();
+
+      power = (double) (total - previous_value_total) / (512 * time_difference);
+      previous_value_total = total;
+      previous_print_time = current_time;
+    }
+
+    char buffer[16];
+    dtostrf(power, 6, 1, buffer);
+    lcd.setCursor(9, current_line);
+    lcd.print(buffer);
+
+    current_line ^= 1;
+  }
+
+  interrupts();
+}
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..4ac9732
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,30 @@
+ARCH=$(shell arch)
+CXX=			g++
+CXXFLAGS=		-O2 -g -pthread -fopenmp
+
+obj/$(ARCH)/%.o:	%.cc
+			$(CXX) -c $(CXXFLAGS) $< -o $@
+
+all::			lib/$(ARCH)/libPowerSensor.a bin/$(ARCH)/testPowerSensor arduino
+
+lib/$(ARCH)/libPowerSensor.a:	obj/$(ARCH)/PowerSensor.o lib/$(ARCH)
+			$(AR) cr $@ $<
+
+bin/$(ARCH)/testPowerSensor:	obj/$(ARCH)/testPowerSensor.o lib/$(ARCH)/libPowerSensor.a bin/$(ARCH)
+			$(CXX) $(CXXFLAGS) obj/$(ARCH)/testPowerSensor.o -Llib/$(ARCH) -lPowerSensor -o $@
+
+obj/$(ARCH)/testPowerSensor.o:	testPowerSensor.cc PowerSensor.h obj/$(ARCH)
+
+obj/$(ARCH)/PowerSensor.o:	PowerSensor.cc PowerSensor.h obj/$(ARCH)
+
+obj/$(ARCH) lib/$(ARCH) bin/$(ARCH):
+			mkdir -p $@
+
+arduino::
+			+make -C Arduino
+
+upload::		all
+			+make -C Arduino upload
+
+clean::
+			make -C Arduino clean
diff --git a/src/PowerSensor.cc b/src/PowerSensor.cc
new file mode 100644
index 0000000..c9bedad
--- /dev/null
+++ b/src/PowerSensor.cc
@@ -0,0 +1,239 @@
+//  Copyright (C) 2016
+//  ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
+//  P.O. Box 2, 7990 AA  Dwingeloo, the Netherlands
+
+//  This file is part of PowerSensor.
+
+//  PowerSensor 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 3 of the License, or
+//  (at your option) any later version.
+
+//  PowerSensor 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 PowerSensor.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#include "PowerSensor.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <omp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+
+
+PowerSensor::PowerSensor(const char *device, const char *dumpFileName)
+:
+  dumpFile(dumpFileName == 0 ? 0 : new std::ofstream(dumpFileName)),
+  stop(false)
+{
+  lastState.microSeconds = 0;
+
+  if ((fd = open(device, O_RDWR)) < 0) {
+    perror("open device");
+    exit(1);
+  }
+
+
+  //Configure port for 8N1 transmission
+  struct termios options;
+
+  tcgetattr(fd, &options);		//Gets the current options for the port
+  cfsetispeed(&options, B2000000);	//Sets the Input Baud Rate
+  cfsetospeed(&options, B2000000);	//Sets the Output Baud Rate
+
+  options.c_cflag = (options.c_cflag & ~CSIZE) | CS8;
+  options.c_iflag = IGNBRK;
+  options.c_lflag = 0;
+  options.c_oflag = 0;
+  options.c_cflag |= CLOCAL | CREAD;
+  options.c_cc[VMIN] = sizeof(State::MC_State);
+  options.c_cc[VTIME] = 0;
+  options.c_iflag &= ~(IXON | IXOFF | IXANY);
+  options.c_cflag &= ~(PARENB | PARODD);
+
+  /* commit the options */
+  tcsetattr(fd, TCSANOW, &options);
+
+  /* Wait for the Arduino to reset */
+  sleep(2);
+  /* Flush anything already in the serial buffer */
+  tcflush(fd, TCIFLUSH);
+
+  if ((errno = pthread_mutex_init(&mutex, 0)) != 0) {
+    perror("pthread_mutex_init");
+    exit(1);
+  }
+
+  doMeasurement(); // initialise
+
+  if ((errno = pthread_create(&thread, 0, &PowerSensor::IOthread, this)) != 0) {
+    perror("pthread_create");
+    exit(1);
+  }
+}
+
+
+PowerSensor::~PowerSensor()
+{
+  stop = true;
+
+  if ((errno = pthread_join(thread, 0)) != 0)
+    perror("pthread_join");
+
+  if ((errno = pthread_mutex_destroy(&mutex)) != 0)
+    perror("pthread_mutex_destroy");
+
+  if (close(fd))
+    perror("close device");
+
+  if (dumpFile != 0)
+    delete dumpFile;
+}
+
+
+void PowerSensor::lock()
+{
+  if ((errno = pthread_mutex_lock(&mutex)) != 0) {
+    perror("pthread_mutex_lock");
+    exit(1);
+  }
+}
+
+
+void PowerSensor::unlock()
+{
+  if ((errno = pthread_mutex_unlock(&mutex)) != 0) {
+    perror("pthread_mutex_unlock");
+    exit(1);
+  }
+}
+
+
+void *PowerSensor::IOthread(void *arg)
+{
+  return static_cast<PowerSensor *>(arg)->IOthread();
+}
+
+
+void *PowerSensor::IOthread()
+{
+  while (!stop)
+    doMeasurement();
+
+  return 0;
+}
+
+
+void PowerSensor::doMeasurement()
+{
+  State::MC_State currentState;
+
+  ssize_t retval, bytesRead = 0;
+
+  if (write(fd, "s", 1) != 1) {
+    perror("write device");
+    exit(1);
+  }
+
+  do {
+    if ((retval = ::read(fd, (char *) &currentState.consumedEnergy + bytesRead, sizeof(State::MC_State) - bytesRead)) < 0) {
+      perror("read device");
+      exit(1);
+    }
+  } while ((bytesRead += retval) < sizeof(State::MC_State));
+
+  lock();
+
+  if (lastState.microSeconds != currentState.microSeconds) {
+    previousState = lastState;
+    lastState = currentState;
+
+    if (dumpFile != 0)
+      *dumpFile << "S " << currentState.microSeconds / 1e6 << ' ' << (currentState.consumedEnergy - previousState.consumedEnergy) * (65536.0 / 512) / (currentState.microSeconds - previousState.microSeconds) << std::endl;
+  }
+
+#if 0
+  static double lastTime;
+  static unsigned count;
+  ++ count;
+
+  if (lastTime + 1 < omp_get_wtime()) {
+#pragma omp critical (cout)
+    std::cout << "nr times read = " << count << std::endl;
+    lastTime = omp_get_wtime();
+    count = 0;
+  }
+#endif
+
+  unlock();
+}
+
+
+void PowerSensor::mark(const State &state, const char *name, unsigned tag)
+{
+  if (dumpFile != 0) {
+    lock();
+    *dumpFile << "M " << state.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl;
+    unlock();
+  }
+}
+
+
+void PowerSensor::mark(const State &startState, const State &stopState, const char *name, unsigned tag)
+{
+  if (dumpFile != 0) {
+    lock();
+    *dumpFile << "M " << startState.lastState.microSeconds * 1e-6 << ' ' << stopState.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl;
+    unlock();
+  }
+}
+
+
+PowerSensor::State PowerSensor::read()
+{
+  State state;
+
+  lock();
+  state.previousState = previousState;
+  state.lastState = lastState;
+  state.timeAtRead = omp_get_wtime();
+  unlock();
+
+  return state;
+}
+
+
+double PowerSensor::Joules(const State &firstState, const State &secondState)
+{
+  return Watt(firstState, secondState) * seconds(firstState, secondState);
+}
+
+
+double PowerSensor::seconds(const State &firstState, const State &secondState)
+{
+  return secondState.timeAtRead - firstState.timeAtRead;
+}
+
+
+double PowerSensor::Watt(const State &firstState, const State &secondState)
+{
+  uint32_t microSeconds = secondState.lastState.microSeconds - firstState.lastState.microSeconds;
+
+  if (microSeconds != 0)
+    return (secondState.lastState.consumedEnergy - firstState.lastState.consumedEnergy) * (65536.0 / 512) / microSeconds;
+  else // very short time
+    return (secondState.lastState.consumedEnergy - secondState.previousState.consumedEnergy) * (65536.0 / 512) / (secondState.lastState.microSeconds - secondState.previousState.microSeconds);
+}
diff --git a/src/PowerSensor.h b/src/PowerSensor.h
new file mode 100644
index 0000000..783d8b0
--- /dev/null
+++ b/src/PowerSensor.h
@@ -0,0 +1,70 @@
+//  Copyright (C) 2016
+//  ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
+//  P.O. Box 2, 7990 AA  Dwingeloo, the Netherlands
+
+//  This file is part of PowerSensor.
+
+//  PowerSensor 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 3 of the License, or
+//  (at your option) any later version.
+
+//  PowerSensor 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 PowerSensor.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#if !defined POWER_SENSOR_H
+#define POWER_SENSOR_H
+
+#include <inttypes.h>
+#include <pthread.h>
+
+#include <fstream>
+
+
+class PowerSensor
+{
+  public:
+    struct State
+    {
+      struct MC_State
+      {
+	int32_t  consumedEnergy;
+	uint32_t microSeconds;
+      } previousState, lastState;
+
+      double timeAtRead;
+    };
+
+    PowerSensor(const char *device = "/dev/ttyACM0", const char *dumpFileName = 0);
+    ~PowerSensor();
+
+    State read();
+    void mark(const State &, const char *name = 0, unsigned tag = 0);
+    void mark(const State &start, const State &stop, const char *name = 0, unsigned tag = 0);
+
+    static double Joules(const State &firstState, const State &secondState);
+    static double seconds(const State &firstState, const State &secondState);
+    static double Watt(const State &firstState, const State &secondState);
+
+  private:
+    int		  fd;
+    std::ofstream *dumpFile;
+    pthread_t	  thread;
+    pthread_mutex_t mutex;
+    volatile bool stop;
+    State::MC_State previousState, lastState;
+
+    void	  lock(), unlock();
+
+    static void   *IOthread(void *);
+    void	  *IOthread();
+    void	  doMeasurement();
+};
+
+#endif
diff --git a/src/psconfig b/src/psconfig
new file mode 100755
index 0000000..2b4356a
--- /dev/null
+++ b/src/psconfig
@@ -0,0 +1,195 @@
+#!/usr/bin/python
+
+#  Copyright (C) 2016
+#  ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
+#  P.O. Box 2, 7990 AA  Dwingeloo, the Netherlands
+
+#  This file is part of PowerSensor.
+
+#  PowerSensor 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 3 of the License, or
+#  (at your option) any later version.
+
+#  PowerSensor 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 PowerSensor.  If not, see <http://www.gnu.org/licenses/>.
+
+
+import getopt, serial, sys, time, struct
+
+maxSensors = 5
+
+
+def approximates(a, b):
+  return a / b > .999999 and a / b < 1.000001
+
+class Sensor:
+  def readEEPROM(self, ser):
+    self.volt, self.voltPerAmpere, self.nullLevel = struct.unpack("<ffh", ser.read(10))
+
+  def writeEEPROM(self, ser):
+    ser.write(struct.pack("<ffh", self.volt, self.voltPerAmpere, self.nullLevel))
+
+  def readLevel(self, ser):
+    self.level = struct.unpack("<h", ser.read(2))[0]
+
+
+class EEPROM:
+  def __init__(self, device = "/dev/ttyUSB0"):
+    self.ser = None
+
+    try:
+      self.ser = serial.Serial(device, 2000000);
+    except serial.SerialException:
+      print "Could not open", device
+      sys.exit(1)
+
+    time.sleep(2) # wait until device reset complete
+
+    # read EEPROM sensor configuration
+    self.sensors = []
+    self.ser.write('r')
+
+    for i in range(maxSensors):
+      self.sensors.append(Sensor())
+      self.sensors[i].readEEPROM(self.ser)
+
+    # read current sensor levels
+    self.ser.write('l')
+
+    for sensor in self.sensors:
+      sensor.readLevel(self.ser)
+
+    self.currentSensor = self.sensors[0]
+
+
+  def __del__(self):
+    if self.ser != None: # may be None if constructor threw exception
+      self.ser.write('w')
+      self.ser.flush() # serial comm from host to Arduino seems less reliable --> reduce baud rate
+      time.sleep(.2)
+      self.ser.baudrate = 115200
+
+      for sensor in self.sensors:
+	sensor.writeEEPROM(self.ser)
+
+      self.ser.close()
+
+
+  def setSensor(self, arg):
+    sensorNumber = int(arg)
+
+    if sensorNumber < 0 or sensorNumber >= maxSensors:
+      print >> sys.stderr, 'illegal sensor number'
+      sys.exit(1)
+
+    self.currentSensor = self.sensors[sensorNumber]
+
+
+  def setNullLevel(self, arg):
+    if arg == "auto":
+      arg = self.currentSensor.level - 512
+
+    if int(arg) < -512 or int(arg) >= 512:
+      print >> sys.stderr, 'illegal nullLevel value'
+      sys.exit(1)
+
+    self.currentSensor.nullLevel = int(arg) + 512
+
+
+  def setVolt(self, arg):
+    self.currentSensor.volt = float(arg)
+
+
+  def setType(self, arg):
+    if arg in ("acs712-5", "ACS712-5"):
+      voltPerAmpere = 0.185
+    elif arg in ("acs712-20", "ACS712-20"):
+      voltPerAmpere = 0.1
+    elif arg in ("acs712-30", "ACS712-30"):
+      voltPerAmpere = 0.66
+    else:
+      voltPerAmpere = float(arg)
+
+    self.currentSensor.voltPerAmpere = voltPerAmpere
+
+
+  def doPrint(self):
+    totalWatt = 0.0
+
+    for i in range(len(self.sensors)):
+      sensor = self.sensors[i]
+
+      if approximates(sensor.voltPerAmpere, 0.185):
+	sensorType = "type = ACS712-5"
+      elif approximates(sensor.voltPerAmpere, 0.1):
+	sensorType = "type = ACS712-20"
+      elif approximates(sensor.voltPerAmpere, 0.66):
+	sensorType = "type = ACS712-30"
+      else:
+	sensorType = "voltPerAmpere = " + str(sensor.voltPerAmpere)
+
+      if sensor.volt == 0:
+	print "sensor %u: off" % i
+      else:
+	watt = sensor.volt * 2.5 / sensor.voltPerAmpere * (sensor.level - sensor.nullLevel) / 512
+	totalWatt += watt
+	print "sensor %u: volt = %f, %s, nullLevel = %d, level = %d, Watt = %f" % (i, sensor.volt, sensorType, sensor.nullLevel - 512, sensor.level - 512, watt)
+
+    print "total Watt = %f" % totalWatt
+
+
+def printHelp():
+  print "Usage: [-d device] [-s sensor_nr] [-v volatage] [-t sensor_type] [-n null_level] [-s next_sensor_nr] [-o] ... [-p]"
+  print
+  print "the order in which the arguments appear is important"
+  print "-h (--help): print this help message"
+  print "-d dev (--device=dev): use this device (default: /dev/ttyUSB0)"
+  print "-s sensor_nr (--sensor=sensor_nr): from now on, configuration parameters apply to this sennsor number"
+  print "-v volt (--voltage=volt: voltage of the rail being measured"
+  print "-t sensor_type (--type=sensor_type): use this sensor type (ACS712-5, ACS712-20, ACS712-30, or a number indicating the volt/ampere ratio of the sensor"
+  print "-n null_level (--nulllevel=null_level): null level to be used for calibration.  If no current is flowing, use \"-n auto\""
+  print "-o (--off): do not use this sensor number"
+  print "-p (--print): print configuration parameterss and the currently measured sensor values"
+
+
+def getopts(argv):
+  eeprom = None
+
+  try:
+    opts, argv = getopt.getopt(argv, "d:hn:ops:t:v:", ["device=", "help", "nulllevel=", "off", "print", "sensor=", "type=", "voltage="])
+  except getopt.GetoptError:
+    printHelp()
+    sys.exit(1)
+
+  for opt, arg in opts:
+    if opt in ("-h", "--help"):
+      printHelp()
+      sys.exit()
+
+    if opt in ("-d", "--device"):
+      del eeprom
+      eeprom = EEPROM(arg)
+    elif eeprom == None:
+      eeprom = EEPROM()
+
+    if opt in ("-n", "--nullLevel"):
+      eeprom.setNullLevel(arg)
+    elif opt in ("-o", "--off"):
+      eeprom.setVolt(0.0)
+    elif opt in ("-p", "--print"):
+      eeprom.doPrint()
+    elif opt in ("-s", "--sensor"):
+      eeprom.setSensor(arg)
+    elif opt in ("-t", "--type"):
+      eeprom.setType(arg)
+    elif opt in ("-v", "--volt"):
+      eeprom.setVolt(arg)
+
+if __name__ == "__main__":
+  getopts(sys.argv[1:])
diff --git a/src/testPowerSensor.cc b/src/testPowerSensor.cc
new file mode 100644
index 0000000..a992c4c
--- /dev/null
+++ b/src/testPowerSensor.cc
@@ -0,0 +1,59 @@
+//  Copyright (C) 2016
+//  ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
+//  P.O. Box 2, 7990 AA  Dwingeloo, the Netherlands
+
+//  This file is part of PowerSensor.
+
+//  PowerSensor 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 3 of the License, or
+//  (at your option) any later version.
+
+//  PowerSensor 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 PowerSensor.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#include <iostream>
+#include <iomanip>
+#include "PowerSensor.h"
+
+#include <cstdlib>
+#include <iostream>
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#define MAX_MICRO_SECONDS 4000000
+
+
+int main(int argc, char **argv)
+{
+  if (argc > 2) {
+    std::cerr << "usage: " << argv[0] << " [device]" << std::endl;
+    exit(1);
+  }
+
+  const char *device = argc == 2 ? argv[1] : "/dev/ttyUSB0";
+  PowerSensor powerSensor(device, "/tmp/sensor_values");
+
+  PowerSensor::State states[2];
+  states[0] = powerSensor.read();
+
+  for (uint32_t micros = 100, i = 1; micros <= MAX_MICRO_SECONDS; micros *= 2, i ^= 1) {
+    usleep(micros);
+    states[i] = powerSensor.read();
+
+    std::cout << "exp. time: " << micros * 1e-6 << " s, "
+      "measured: " << PowerSensor::seconds(states[i ^ 1], states[i]) << " s, " <<
+      PowerSensor::Joules(states[i ^ 1], states[i]) << " J, " <<
+      PowerSensor::Watt(states[i ^ 1], states[i]) << " W" <<
+      std::endl;
+  }
+  
+  return 0;
+}
-- 
GitLab