diff --git a/Makefile b/Makefile index 4ac9732fd6310275df214bbdc99592e43f757cc6..df72e4686464f379274b299df9523ea05ac39ada 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,40 @@ ARCH=$(shell arch) CXX= g++ -CXXFLAGS= -O2 -g -pthread -fopenmp +CXXFLAGS= -std=c++11 -O2 -g -pthread -fopenmp obj/$(ARCH)/%.o: %.cc + @mkdir -p obj/$(ARCH) $(CXX) -c $(CXXFLAGS) $< -o $@ -all:: lib/$(ARCH)/libPowerSensor.a bin/$(ARCH)/testPowerSensor arduino +all:: lib/$(ARCH)/libPowerSensor.a\ + bin/$(ARCH)/psconfig\ + bin/$(ARCH)/psrun\ + bin/$(ARCH)/pstest\ + arduino -lib/$(ARCH)/libPowerSensor.a: obj/$(ARCH)/PowerSensor.o lib/$(ARCH) +lib/$(ARCH)/libPowerSensor.a: obj/$(ARCH)/PowerSensor.o + -mkdir -p 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 $@ +bin/$(ARCH)/psconfig: obj/$(ARCH)/psconfig.o lib/$(ARCH)/libPowerSensor.a + -mkdir -p bin/$(ARCH) + $(CXX) $(CXXFLAGS) obj/$(ARCH)/psconfig.o -Llib/$(ARCH) -lPowerSensor -o $@ -obj/$(ARCH)/testPowerSensor.o: testPowerSensor.cc PowerSensor.h obj/$(ARCH) +bin/$(ARCH)/psrun: obj/$(ARCH)/psrun.o lib/$(ARCH)/libPowerSensor.a + -mkdir -p bin/$(ARCH) + $(CXX) $(CXXFLAGS) obj/$(ARCH)/psrun.o -Llib/$(ARCH) -lPowerSensor -o $@ -obj/$(ARCH)/PowerSensor.o: PowerSensor.cc PowerSensor.h obj/$(ARCH) +bin/$(ARCH)/pstest: obj/$(ARCH)/pstest.o lib/$(ARCH)/libPowerSensor.a + -mkdir -p bin/$(ARCH) + $(CXX) $(CXXFLAGS) obj/$(ARCH)/pstest.o -Llib/$(ARCH) -lPowerSensor -o $@ -obj/$(ARCH) lib/$(ARCH) bin/$(ARCH): - mkdir -p $@ +obj/$(ARCH)/psconfig.o: psconfig.cc PowerSensor.h + +obj/$(ARCH)/psrun.o: psrun.cc PowerSensor.h + +obj/$(ARCH)/pstest.o: pstest.cc PowerSensor.h + +obj/$(ARCH)/PowerSensor.o: PowerSensor.cc PowerSensor.h Semaphore.h arduino:: +make -C Arduino @@ -28,3 +44,4 @@ upload:: all clean:: make -C Arduino clean + $(RM) -r bin/$(ARCH) lib/$(ARCH) obj/$(ARCH) diff --git a/PowerSensor.cc b/PowerSensor.cc index c9bedad0c3bcc2266da3c793aa06628fcc4b6419..e205d3dc4212e0bb2ba78591dca9ba120db3aecc 100644 --- a/PowerSensor.cc +++ b/PowerSensor.cc @@ -22,44 +22,130 @@ #include <cstdio> #include <cstdlib> +#include <cstring> #include <iostream> +#include <byteswap.h> #include <errno.h> #include <fcntl.h> #include <omp.h> -#include <sys/types.h> +#include <sys/file.h> #include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> #include <termios.h> #include <unistd.h> +namespace PowerSensor { -PowerSensor::PowerSensor(const char *device, const char *dumpFileName) -: - dumpFile(dumpFileName == 0 ? 0 : new std::ofstream(dumpFileName)), - stop(false) + +void PowerSensor::Sensor::readFromEEPROM(int fd) +{ + struct EEPROM eeprom; + ssize_t retval, bytesRead = 0; + + do { + if ((retval = ::read(fd, (char *) &eeprom + bytesRead, sizeof eeprom - bytesRead)) < 0) { + perror("read device"); + exit(1); + } + } while ((bytesRead += retval) < sizeof eeprom); + +#if defined __BIG_ENDIAN__ + eeprom.volt = __bswap_32(eeprom.volt); + eeprom.type = __bswap_32(eeprom.type); + eeprom.nullLevel = __bswap_32(eeprom.nullLevel); +#endif + + setVolt(eeprom.volt); + setType(eeprom.type); + setNullLevel(eeprom.nullLevel); +} + + +void PowerSensor::Sensor::writeToEEPROM(int fd) const +{ + struct EEPROM eeprom; + + eeprom.volt = volt; + eeprom.type = type; + eeprom.nullLevel = nullLevel; + +#if defined __BIG_ENDIAN__ + eeprom.volt = __bswap_32(eeprom.volt); + eeprom.type = __bswap_32(eeprom.type); + eeprom.nullLevel = __bswap_32(eeprom.nullLevel); +#endif + + ssize_t retval, bytesWritten = 0; + + do { + if ((retval = ::write(fd, (char *) &eeprom + bytesWritten, sizeof eeprom - bytesWritten)) < 0) { + perror("write device"); + exit(1); + } + } while ((bytesWritten += retval) < sizeof eeprom); +} + + +void PowerSensor::Sensor::updateDerivedValues() +{ + weight = volt != 0 ? 2.5 / 512 * volt / type : 0; + consumedEnergy = 0; + wattAtlastMeasurement = 0; + timeAtLastMeasurement = omp_get_wtime(); +} + + +void PowerSensor::Sensor::setVolt(float volt) +{ + this->volt = volt; + updateDerivedValues(); +} + + +void PowerSensor::Sensor::setType(float type) +{ + this->type = type; + updateDerivedValues(); +} + + +void PowerSensor::Sensor::setNullLevel(float nullLevel) +{ + this->nullLevel = nullLevel; + updateDerivedValues(); +} + + +int PowerSensor::openDevice(const char *device) { - lastState.microSeconds = 0; + int fd; if ((fd = open(device, O_RDWR)) < 0) { perror("open device"); exit(1); } + if (flock(fd, LOCK_EX) < 0) { + perror("flock"); + 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 + cfsetispeed(&options, B9600); //Sets the Input Baud Rate + cfsetospeed(&options, B9600); //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[VMIN] = 2; options.c_cc[VTIME] = 0; options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_cflag &= ~(PARENB | PARODD); @@ -67,173 +153,368 @@ PowerSensor::PowerSensor(const char *device, const char *dumpFileName) /* commit the options */ tcsetattr(fd, TCSANOW, &options); +#if defined UNO /* Wait for the Arduino to reset */ sleep(2); +#endif + /* Flush anything already in the serial buffer */ tcflush(fd, TCIFLUSH); - if ((errno = pthread_mutex_init(&mutex, 0)) != 0) { - perror("pthread_mutex_init"); - exit(1); - } + return fd; +} - doMeasurement(); // initialise - if ((errno = pthread_create(&thread, 0, &PowerSensor::IOthread, this)) != 0) { - perror("pthread_create"); - exit(1); - } +PowerSensor::PowerSensor(const char *device) +: + startTime(omp_get_wtime()), + fd(openDevice(device)), + thread(nullptr) +{ + startCleanupProcess(); + readSensorsFromEEPROM(); + startIOthread(); } 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"); + stopIOthread(); if (close(fd)) perror("close device"); +} + - if (dumpFile != 0) - delete dumpFile; +void PowerSensor::readSensorsFromEEPROM() +{ + if (write(fd, "r", 1) != 1) { + perror("write device"); + exit(1); + } + + for (Sensor &sensor : sensors) + sensor.readFromEEPROM(fd); } -void PowerSensor::lock() +void PowerSensor::writeSensorsToEEPROM() { - if ((errno = pthread_mutex_lock(&mutex)) != 0) { - perror("pthread_mutex_lock"); + stopIOthread(); + + if (write(fd, "w", 1) != 1) { + perror("write device"); exit(1); } + +#if defined UNO + struct termios options; + + usleep(200000); + tcgetattr(fd, &options); + cfsetospeed(&options, B115200); + tcsetattr(fd, TCSANOW, &options); +#endif + + for (const Sensor &sensor : sensors) + sensor.writeToEEPROM(fd); + +#if defined UNO + usleep(200000); + tcgetattr(fd, &options); + cfsetospeed(&options, B2000000); + tcsetattr(fd, TCSANOW, &options); +#endif + + startIOthread(); } -void PowerSensor::unlock() +void PowerSensor::startCleanupProcess() { - if ((errno = pthread_mutex_unlock(&mutex)) != 0) { - perror("pthread_mutex_unlock"); + // spawn child process to make sure that the Arduino receives a 'T', no matter how the application terminates + + int pipe_fds[2]; + + if (pipe(pipe_fds) < 0) { + perror("pipe"); exit(1); } + + switch (fork()) + { + ssize_t retval; + + case -1 : perror("fork"); + exit(1); + + case 0 : // detach from the parent process, so signals to the parent are not caught by the child + setsid(); + + // close all file descriptors, except pipe read end and Arduino fd + for (int i = 3, last = getdtablesize(); i < last; i ++) + if (i != fd && i != pipe_fds[0]) + close(i); + + // wait until parent closes pipe_fds[1] so that read fails + char byte; + retval = ::read(pipe_fds[0], &byte, sizeof byte); + + // tell Arduino to stop sending data + retval = write(fd, "T", 1); + + // drain garbage + usleep(100000); + tcflush(fd, TCIFLUSH); + + exit(0); + + default: close(pipe_fds[0]); + } } -void *PowerSensor::IOthread(void *arg) +bool PowerSensor::Sensor::inUse() const { - return static_cast<PowerSensor *>(arg)->IOthread(); + return volt != 0; } -void *PowerSensor::IOthread() +void PowerSensor::Sensor::updateLevel(int16_t level) { - while (!stop) - doMeasurement(); + double now = omp_get_wtime(); - return 0; + wattAtlastMeasurement = (level - 512) * weight - nullLevel; + consumedEnergy += wattAtlastMeasurement * (now - timeAtLastMeasurement); + timeAtLastMeasurement = now; } -void PowerSensor::doMeasurement() +double PowerSensor::Sensor::totalEnergy(double now) const { - State::MC_State currentState; + return /* weight == 0 ? 0 : */ consumedEnergy + wattAtlastMeasurement * (now - timeAtLastMeasurement); +} - ssize_t retval, bytesRead = 0; - if (write(fd, "s", 1) != 1) { - perror("write device"); - exit(1); - } +double PowerSensor::Sensor::currentWatt() const +{ + return /* weight == 0 ? 0 : */ wattAtlastMeasurement; +} - do { - if ((retval = ::read(fd, (char *) ¤tState.consumedEnergy + bytesRead, sizeof(State::MC_State) - bytesRead)) < 0) { + +bool PowerSensor::readLevelFromDevice(unsigned &sensorNumber, unsigned &level) +{ + uint8_t msg[2]; + ssize_t retval, bytesRead = 0; + + while (true) { + if ((retval = ::read(fd, (char *) &msg + bytesRead, sizeof msg - bytesRead)) < 0) { perror("read device"); exit(1); + } else if ((bytesRead += retval) == sizeof msg) { + if (msg[0] == 0xFF && msg[1] == 0xE0) { + return false; + } else if ((sensorNumber = msg[0] >> 5) < MAX_SENSORS && (msg[1] & 0xE0) == 0xE0) { + level = ((msg[0] & 0x1F) << 5) | (msg[1] & 0x1F); + return true; + } else { + msg[0] = msg[1]; // byte lost? drop first byte and try again + bytesRead = 1; + } } - } while ((bytesRead += retval) < sizeof(State::MC_State)); + } +} - lock(); - if (lastState.microSeconds != currentState.microSeconds) { - previousState = lastState; - lastState = currentState; +void PowerSensor::dumpCurrentWattToFile() +{ + std::unique_lock<std::mutex> lock(dumpFileMutex); + double totalWatt = 0; + double time = omp_get_wtime(); + + *dumpFile << "S " << time - startTime; + +#if 1 + static double previousTime; + *dumpFile << ' ' << 1e6 * (time - previousTime); + previousTime = time; +#endif + + for (const Sensor &sensor : sensors) + if (sensor.inUse()) { + *dumpFile << ' ' << sensor.currentWatt(); + totalWatt += sensor.currentWatt(); + } - if (dumpFile != 0) - *dumpFile << "S " << currentState.microSeconds / 1e6 << ' ' << (currentState.consumedEnergy - previousState.consumedEnergy) * (65536.0 / 512) / (currentState.microSeconds - previousState.microSeconds) << std::endl; + *dumpFile << ' ' << totalWatt << std::endl; +} + + +void PowerSensor::IOthread() +{ + threadStarted.up(); + + unsigned sensorNumber, level; + + while (readLevelFromDevice(sensorNumber, level)) { + std::unique_lock<std::mutex> lock(mutex); + sensors[sensorNumber].updateLevel(level); + + if (dumpFile != nullptr) + dumpCurrentWattToFile(); } +} -#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; +void PowerSensor::startIOthread() +{ + if (thread == nullptr) { + thread = new std::thread(&PowerSensor::IOthread, this); + + if (write(fd, "S", 1) != 1) { + perror("write device"); + exit(1); + } } -#endif - unlock(); + threadStarted.down(); // wait for the IOthread to run smoothly +} + + +void PowerSensor::stopIOthread() +{ + if (thread != nullptr) { + if (write(fd, "X", 1) < 0) { + perror("write"); + exit(1); + } + + thread->join(); + delete thread; + thread = 0; + } +} + + +void PowerSensor::dump(const char *dumpFileName) +{ + dumpFile = std::unique_ptr<std::ofstream>(dumpFileName != nullptr ? new std::ofstream(dumpFileName) : nullptr); } -void PowerSensor::mark(const State &state, const char *name, unsigned tag) +void PowerSensor::mark(const State &state, const char *name, unsigned tag) const { - if (dumpFile != 0) { - lock(); - *dumpFile << "M " << state.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl; - unlock(); + if (dumpFile != nullptr) { + std::unique_lock<std::mutex> lock(dumpFileMutex); + *dumpFile << "M " << state.timeAtRead - startTime << ' ' << tag << " \"" << (name == nullptr ? "" : name) << '"' << std::endl; } } -void PowerSensor::mark(const State &startState, const State &stopState, const char *name, unsigned tag) +void PowerSensor::mark(const State &startState, const State &stopState, const char *name, unsigned tag) const { - if (dumpFile != 0) { - lock(); - *dumpFile << "M " << startState.lastState.microSeconds * 1e-6 << ' ' << stopState.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl; - unlock(); + if (dumpFile != nullptr) { + std::unique_lock<std::mutex> lock(dumpFileMutex); + *dumpFile << "M " << startState.timeAtRead - startTime << ' ' << stopState.timeAtRead << ' ' << tag << " \"" << (name == nullptr ? "" : name) << '"' << std::endl; } } -PowerSensor::State PowerSensor::read() +State PowerSensor::read() const { State state; - lock(); - state.previousState = previousState; - state.lastState = lastState; + std::unique_lock<std::mutex> lock(mutex); state.timeAtRead = omp_get_wtime(); - unlock(); + + for (unsigned sensorID = 0; sensorID < MAX_SENSORS; sensorID ++) + state.consumedEnergy[sensorID] = sensors[sensorID].totalEnergy(state.timeAtRead); return state; } -double PowerSensor::Joules(const State &firstState, const State &secondState) +double Joules(const State &firstState, const State &secondState, int sensorID) { - return Watt(firstState, secondState) * seconds(firstState, secondState); + if (sensorID >= (signed) MAX_SENSORS) + return 0; + + if (sensorID >= 0) + return secondState.consumedEnergy[sensorID] - firstState.consumedEnergy[sensorID]; + + double joules = 0; + + for (double consumedEnergy : secondState.consumedEnergy) + joules += consumedEnergy; + + for (double consumedEnergy : firstState.consumedEnergy) + joules -= consumedEnergy; + + return joules; } -double PowerSensor::seconds(const State &firstState, const State &secondState) +double seconds(const State &firstState, const State &secondState) { return secondState.timeAtRead - firstState.timeAtRead; } -double PowerSensor::Watt(const State &firstState, const State &secondState) +double Watt(const State &firstState, const State &secondState, int sensorID) +{ + return Joules(firstState, secondState, sensorID) / seconds(firstState, secondState); +} + + +float PowerSensor::getVolt(unsigned sensorID) const { - uint32_t microSeconds = secondState.lastState.microSeconds - firstState.lastState.microSeconds; + return sensorID < MAX_SENSORS ? sensors[sensorID].volt : 0; +} + + +void PowerSensor::setVolt(unsigned sensorID, float volt) +{ + if (sensorID < MAX_SENSORS) { + sensors[sensorID].setVolt(volt); + writeSensorsToEEPROM(); + } +} + + +float PowerSensor::getType(unsigned sensorID) const +{ + return sensorID < MAX_SENSORS ? sensors[sensorID].type : 0; +} + + +void PowerSensor::setType(unsigned sensorID, float type) +{ + if (sensorID < MAX_SENSORS) { + sensors[sensorID].setType(type); + writeSensorsToEEPROM(); + } +} + + +float PowerSensor::getNullLevel(unsigned sensorID) const +{ + return sensorID < MAX_SENSORS ? sensors[sensorID].nullLevel : 0; +} + + +void PowerSensor::setNullLevel(unsigned sensorID, float nullLevel) +{ + if (sensorID < MAX_SENSORS) { + sensors[sensorID].setNullLevel(nullLevel); + writeSensorsToEEPROM(); + } +} + + +bool PowerSensor::inUse(unsigned sensorID) const +{ + return sensorID < MAX_SENSORS && sensors[sensorID].inUse(); +} + - 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/PowerSensor.h b/PowerSensor.h index 783d8b0cdde82f4749aaea67f4ed7ba7fe25d9aa..a76c84b02c612c978cbcd2497ba193af5df8b6fc 100644 --- a/PowerSensor.h +++ b/PowerSensor.h @@ -21,50 +21,98 @@ #if !defined POWER_SENSOR_H #define POWER_SENSOR_H +#include "Semaphore.h" + #include <inttypes.h> -#include <pthread.h> #include <fstream> +#include <mutex> +#include <thread> -class PowerSensor +namespace PowerSensor { + + +const static unsigned MAX_SENSORS = 5; + + +struct State { - public: - struct State - { - struct MC_State - { - int32_t consumedEnergy; - uint32_t microSeconds; - } previousState, lastState; + double consumedEnergy[MAX_SENSORS]; + double timeAtRead; +}; - double timeAtRead; - }; - PowerSensor(const char *device = "/dev/ttyACM0", const char *dumpFileName = 0); +class PowerSensor +{ + public: + PowerSensor(const char *device = "/dev/ttyACM0"); ~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); + State read() const; - 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); + void dump(const char *dumpFileName); // dumpFileName == 0 --> stop dumping + void mark(const State &, const char *name = 0, unsigned tag = 0) const; + void mark(const State &start, const State &stop, const char *name = 0, unsigned tag = 0) const; + + float getVolt(unsigned sensorID) const; + void setVolt(unsigned sensorID, float volt); + float getType(unsigned sensorID) const; + void setType(unsigned sensorID, float type); + float getNullLevel(unsigned sensorID) const; + void setNullLevel(unsigned sensorID, float nullLevel); + bool inUse(unsigned sensorID) const; private: + struct Sensor + { + struct EEPROM + { + float volt __attribute__((packed)); + float type __attribute__((packed)); + float nullLevel __attribute__((packed)); + }; + + float volt, type, nullLevel; + double weight; + double consumedEnergy; + double wattAtlastMeasurement; + double timeAtLastMeasurement; + + bool inUse() const; + void readFromEEPROM(int fd), writeToEEPROM(int fd) const; + void setVolt(float), setType(float), setNullLevel(float); + void updateDerivedValues(); + void updateLevel(int16_t level); + double totalEnergy(double now) const; + double currentWatt() const; + } sensors[MAX_SENSORS]; + + double startTime; int fd; - std::ofstream *dumpFile; - pthread_t thread; - pthread_mutex_t mutex; - volatile bool stop; - State::MC_State previousState, lastState; + std::unique_ptr<std::ofstream> dumpFile; + //volatile bool stop; + mutable std::mutex mutex, dumpFileMutex; + Semaphore threadStarted; + std::thread *thread; + void startIOthread(), stopIOthread(); + + int openDevice(const char *device); + double totalEnergy() const; + + void readSensorsFromEEPROM(), writeSensorsToEEPROM(); + bool readLevelFromDevice(unsigned &sensorNumber, unsigned &level); + void dumpCurrentWattToFile(); + void IOthread(); + + void startCleanupProcess(); +}; - void lock(), unlock(); - static void *IOthread(void *); - void *IOthread(); - void doMeasurement(); -}; +double Joules(const State &firstState, const State &secondState, int sensorID = -1 /* default: all sensors */); +double seconds(const State &firstState, const State &secondState); +double Watt(const State &firstState, const State &secondState, int sensorID = -1 /* default: all sensors */); + +} #endif