Program Listing for File PowerSensor.cc

Return to documentation for file (host/src/PowerSensor.cc)

#include <fcntl.h>
#include <omp.h>
#include <termios.h>
#include <unistd.h>
#include <sys/file.h>

#include <chrono>
#include <iostream>
#include <cstring>

#include "PowerSensor.hpp"


namespace PowerSensor3 {

  void checkPairID(int pairID) {
    if (pairID >= (signed)MAX_PAIRS) {
      std::cerr << "Invalid pairID: " << pairID << ", maximum value is " << MAX_PAIRS - 1 << std::endl;
      exit(1);
    }
  }

  double Joules(const State &firstState, const State &secondState, int pairID) {
    checkPairID(pairID);

    if (pairID >= 0) {
      return secondState.consumedEnergy[pairID] - firstState.consumedEnergy[pairID];
    }

    double joules = 0;
    for (double consumedEnergy : secondState.consumedEnergy) {
      joules += consumedEnergy;
    }
    for (double consumedEnergy : firstState.consumedEnergy) {
      joules -= consumedEnergy;
    }
    return joules;
  }

  double seconds(const State &firstState, const State &secondState) {
    return secondState.timeAtRead - firstState.timeAtRead;
  }

  double Watt(const State &firstState, const State &secondState, int pairID) {
    return Joules(firstState, secondState, pairID) / seconds(firstState, secondState);
  }

  PowerSensor::PowerSensor(std::string device):
    fd(openDevice(device)),
    pipe_fd(startCleanupProcess()),
    thread(nullptr),
    startTime(omp_get_wtime()) {
      readSensorsFromEEPROM();
      initializeSensorPairs();
      startIOThread();
    }

  PowerSensor::~PowerSensor() {
    stopIOThread();
    if (close(pipe_fd)) {
      perror("close child pipe fd");
    }

    if (close(fd)) {
      perror("close device");
    }
  }

  State PowerSensor::read() const {
    State state;

    std::unique_lock<std::mutex> lock(mutex);

    for (uint8_t pairID=0; pairID < MAX_PAIRS; pairID++) {
      state.consumedEnergy[pairID] = sensorPairs[pairID].consumedEnergy;
      state.current[pairID] = sensorPairs[pairID].currentAtLastMeasurement;
      state.voltage[pairID] = sensorPairs[pairID].voltageAtLastMeasurement;
      // Note: timeAtLastMeasurement is the same for each _active_ sensor pair
      if (sensorPairs[pairID].inUse) {
        state.timeAtRead = sensorPairs[pairID].timeAtLastMeasurement;
      }
    }
    return state;
  }

  int PowerSensor::openDevice(std::string device) {
    int fileDescriptor;

    // opens the file specified by pathname;
    if ((fileDescriptor = open(device.c_str(), O_RDWR)) < 0) {
      perror(device.c_str());
      exit(1);
    }
    // block if an incompatible lock is held by another process;
    if (flock(fileDescriptor, LOCK_EX) < 0) {
      perror("flock");
      exit(1);
    }

    // struct for configuring the port for communication with stm32;
    struct termios terminalOptions;

    // gets the current options for the port;
    tcgetattr(fileDescriptor, &terminalOptions);

    // set control mode flags;
    terminalOptions.c_cflag = (terminalOptions.c_cflag & ~CSIZE) | CS8;
    terminalOptions.c_cflag |= CLOCAL | CREAD;
    terminalOptions.c_cflag &= ~(PARENB | PARODD);


    // set input mode flags;
    terminalOptions.c_iflag |= IGNBRK;
    terminalOptions.c_iflag &= ~(IXON | IXOFF | IXANY);

    // clear local mode flag
    terminalOptions.c_lflag = 0;

    // clear output mode flag;
    terminalOptions.c_oflag = 0;

    // set control characters;
    terminalOptions.c_cc[VMIN] = 1;
    terminalOptions.c_cc[VTIME] = 0;

    // commit the options;
    tcsetattr(fileDescriptor, TCSANOW, &terminalOptions);

    // flush anything already in the serial buffer;
    tcflush(fileDescriptor, TCIFLUSH);

    return fileDescriptor;
  }

  inline char PowerSensor::readCharFromDevice() {
    ssize_t bytesRead;
    char buffer;
    do {
      if ((bytesRead = ::read(fd, &buffer, 1)) < 0) {
        perror("read");
        exit(1);
      }
    } while ((bytesRead) < 1);
    return buffer;
  }

  inline void PowerSensor::writeCharToDevice(char buffer) {
    if (write(fd, &buffer, 1) != 1) {
      perror("write device");
      exit(1);
    }
  }

  void PowerSensor::readSensorsFromEEPROM() {
    // signal device to send EEPROM data
    writeCharToDevice('R');
    // read data per sensor
    for (Sensor& sensor : sensors) {
      sensor.readFromEEPROM(fd);
      // trigger device to send next sensor
      // it does not matter what char is sent
      writeCharToDevice('s');
    }
    // when done, the device sends D
    char buffer;
    if ((buffer = readCharFromDevice()) != 'D') {
      std::cerr << "Expected to receive 'D' from device after reading configuration, but got " << buffer << std::endl;
      exit(1);
    }
  }

  void PowerSensor::writeSensorsToEEPROM() {
    // ensure no data is streaming to host
    stopIOThread();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    // drain any remaining incoming data
    tcflush(fd, TCIFLUSH);
    // signal device to receive EEPROM data
    writeCharToDevice('W');
    // send EEPROM data
    // device sends S after each sensor and D when completely done
    char buffer;
    for (const Sensor& sensor : sensors) {
      sensor.writeToEEPROM(fd);
      if ((buffer = readCharFromDevice()) != 'S') {
        std::cerr << "Expected to receive S from device, but got " << buffer << std::endl;
        exit(1);
      }
    }

    if ((buffer = readCharFromDevice()) != 'D') {
      std::cerr << "Expected to receive 'D' from device after writing configuration, but got " << buffer << std::endl;
      exit(1);
    }

    // restart IO thread
    startIOThread();
  }

  void PowerSensor::initializeSensorPairs() {
    for (uint8_t pairID = 0; pairID < MAX_PAIRS; pairID++) {
      sensorPairs[pairID].timeAtLastMeasurement = startTime;
      sensorPairs[pairID].wattAtLastMeasurement = 0;
      sensorPairs[pairID].consumedEnergy = 0;
      sensorPairs[pairID].currentAtLastMeasurement = 0;
      sensorPairs[pairID].voltageAtLastMeasurement = 0;


      bool currentSensorActive = sensors[2*pairID].inUse;
      bool voltageSensorActive = sensors[2*pairID+1].inUse;

      if (currentSensorActive && voltageSensorActive) {
        sensorPairs[pairID].inUse = true;
      } else if (currentSensorActive ^ voltageSensorActive) {
        std::cerr << "Found incompatible sensor pair: current sensor (ID " << 2*pairID << ") "
        "is " << (currentSensorActive ? "" : "not ") << "active, while "
        "voltage sensor (ID " << 2*pairID+1 << ") is " << (voltageSensorActive ? "" : "not ") << "active. "
        "Please check sensor configuration." << std::endl;
      } else {
        sensorPairs[pairID].inUse = false;
      }
    }
  }

  bool PowerSensor::readLevelFromDevice(unsigned int* sensorNumber, uint16_t* level, unsigned int* marker) {
      // buffer for one set of sensor data (2 bytes)
      uint8_t buffer[2];
      unsigned int retVal, bytesRead = 0;
      // loop exits when a valid value has been read from the device
      while (true) {
        // read full buffer
        do {
          if ((retVal = ::read(fd, reinterpret_cast<char*>(&buffer) + bytesRead, sizeof(buffer) - bytesRead)) < 0) {
            perror("read");
            exit(1);
          }
        } while ((bytesRead += retVal) < sizeof buffer);

        // buffer is full, check if stop was received
        if (buffer[0] == 0xFF && buffer[1] == 0x3F) {
          return false;
        } else if (((buffer[0] >> 7) == 1) & ((buffer[1] >> 7) == 0)) {
          // marker bits are ok, extract the values
          *sensorNumber = (buffer[0] >> 4) & 0x7;
          *level = ((buffer[0] & 0xF) << 6) | (buffer[1] & 0x3F);
          *marker |= (buffer[1] >> 6) & 0x1;
          return true;
        } else {
          // marker bits are wrong. Assume a byte was dropped: drop first byte and try again
          buffer[0] = buffer[1];
          bytesRead = 1;
        }
      }
    }

  void PowerSensor::mark(char name) {
    markers.push(name);
    writeCharToDevice('M');
  }

  void PowerSensor::writeMarker() {
    std::unique_lock<std::mutex> lock(dumpFileMutex);
    *dumpFile << "M " << markers.front() << std::endl;
    markers.pop();
  }

  void PowerSensor::mark(const State &startState, const State &stopState, std::string name, unsigned int tag) const {
    if (dumpFile != nullptr) {
      std::unique_lock<std::mutex> lock(dumpFileMutex);
      *dumpFile << "M " << startState.timeAtRead - startTime << ' ' << stopState.timeAtRead - startTime << ' ' \
        << tag << " \"" << name << '"' << std::endl;
    }
  }

  void PowerSensor::IOThread() {
    unsigned int sensorNumber, marker = 0, sensorsRead = 0;
    uint16_t level;

    // wait until we can read values from the device
    readLevelFromDevice(&sensorNumber, &level, &marker);
    threadStarted.up();

    while (readLevelFromDevice(&sensorNumber, &level, &marker)) {
      std::unique_lock<std::mutex> lock(mutex);

      // detect timestamp packet, value is in microseconds
      // note that an actual marker can only set set for sensor zero in the device firmware
      if ((marker == 1) && (sensorNumber == (MAX_SENSORS-1))) {
        timestamp = level;
        marker = 0;
        continue;
      }

      sensors[sensorNumber].updateLevel(level);
      sensorsRead++;

      if (sensorsRead >= MAX_SENSORS) {
        sensorsRead = 0;
        updateSensorPairs();

        if (dumpFile != nullptr) {
          if (marker != 0) {
            writeMarker();
            marker = 0;
          }
          dumpCurrentWattToFile();
        }
      }
    }
  }

  void PowerSensor::startIOThread() {
    if (thread == nullptr) {
      thread = new std::thread(&PowerSensor::IOThread, this);
    }
    writeCharToDevice('S');
    threadStarted.down();  // wait for the IOthread to run smoothly
  }

  void PowerSensor::stopIOThread() {
    if (thread != nullptr) {
      writeCharToDevice('X');
      thread->join();
      delete thread;
      thread = nullptr;
    }
  }

  void PowerSensor::dump(std::string dumpFileName) {
    std::unique_lock<std::mutex> lock(dumpFileMutex);
    dumpFile = std::unique_ptr<std::ofstream>(dumpFileName.empty() ? nullptr: new std::ofstream(dumpFileName));
    if (!dumpFileName.empty()) {
      *dumpFile << "marker time dt_micro device_timestamp";
      for (unsigned int pairID=0; pairID < MAX_PAIRS; pairID++) {
        if (sensorPairs[pairID].inUse)
          *dumpFile << " current" << pairID << " voltage" << pairID << " power" << pairID;
      }
      *dumpFile << " power_total" << std::endl;
    }
  }

  void PowerSensor::dumpCurrentWattToFile() {
    std::unique_lock<std::mutex> lock(dumpFileMutex);
    double totalWatt = 0;
    double time = omp_get_wtime();
    static double previousTime = startTime;

    *dumpFile << "S " << time - startTime;
    *dumpFile << ' ' << static_cast<int>(1e6 * (time - previousTime));
    *dumpFile << ' ' << timestamp;
    previousTime = time;

    for (uint8_t pairID=0; pairID < MAX_PAIRS; pairID++) {
      if (sensorPairs[pairID].inUse) {
        totalWatt += sensorPairs[pairID].wattAtLastMeasurement;
        *dumpFile << ' ' << sensorPairs[pairID].currentAtLastMeasurement;
        *dumpFile << ' ' << sensorPairs[pairID].voltageAtLastMeasurement;
        *dumpFile << ' ' << sensorPairs[pairID].wattAtLastMeasurement;
      }
    }
    *dumpFile << ' ' << totalWatt << std::endl;
  }

  void PowerSensor::updateSensorPairs() {
    double now = omp_get_wtime();
    for (unsigned int pairID=0; pairID < MAX_PAIRS; pairID++) {
      if (sensorPairs[pairID].inUse) {
        Sensor currentSensor = sensors[2*pairID];
        Sensor voltageSensor = sensors[2*pairID+1];
        SensorPair& sensorPair = sensorPairs[pairID];

        sensorPair.currentAtLastMeasurement = currentSensor.valueAtLastMeasurement;
        sensorPair.voltageAtLastMeasurement = voltageSensor.valueAtLastMeasurement;
        sensorPair.wattAtLastMeasurement = currentSensor.valueAtLastMeasurement * voltageSensor.valueAtLastMeasurement;
        sensorPair.consumedEnergy += sensorPair.wattAtLastMeasurement * (now - sensorPair.timeAtLastMeasurement);
        sensorPair.timeAtLastMeasurement = now;
      }
    }
  }

  int PowerSensor::startCleanupProcess() {
    int pipe_fds[2];

    if (pipe(pipe_fds) < 0) {
      perror("pipe");
      exit(1);
    }

    switch (fork()) {
      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 device 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;
        ::read(pipe_fds[0], &byte, sizeof byte);

        // tell device to stop sending data
        writeCharToDevice('T');

        // drain garbage
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        tcflush(fd, TCIFLUSH);

        exit(0);

    default:
      return pipe_fds[1];
    }
  }

  double PowerSensor::totalEnergy(unsigned int pairID) const {
    double energy = sensorPairs[pairID].wattAtLastMeasurement *
      (omp_get_wtime() - sensorPairs[pairID].timeAtLastMeasurement);

    return sensorPairs[pairID].consumedEnergy + energy;
  }

  void PowerSensor::reset(bool dfuMode) {
    stopIOThread();  // to avoid writing to device _after_ reset
    if (dfuMode) {
        writeCharToDevice('Y');
    } else {
        writeCharToDevice('Z');
    }
  }

  std::string PowerSensor::getVersion() {
    std::string version;
    stopIOThread();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    // drain any remaining incoming data
    tcflush(fd, TCIFLUSH);
    writeCharToDevice('V');
    char c = readCharFromDevice();
    version += c;
    while (c != '\n') {
        c = readCharFromDevice();
        version += c;
    }
    startIOThread();
    return version;
  }

  std::string PowerSensor::getType(unsigned int sensorID) const {
    return sensors[sensorID].type;
  }

  std::string PowerSensor::getPairName(unsigned int pairID) const {
    checkPairID(pairID);
    // warn if sensor pair names of the two sensors are not equal
    if (sensors[2 * pairID].pairName != sensors[2 * pairID + 1].pairName) {
      std::cerr << "Sensor pair names of pair " << pairID << " do not match, use psconfig to correct the values. "
        "Returning value of first sensor" << std::endl;
    }
    return sensors[2 * pairID].pairName;
  }

  float PowerSensor::getVref(unsigned int sensorID) const {
    return sensors[sensorID].vref;
  }

  float PowerSensor::getSensitivity(unsigned int sensorID) const {
    // negative sensitivity corresponds to inverted polarity
    // polarity is considered a separate variable instead so we return the abs value here
    return std::abs(sensors[sensorID].sensitivity);
  }

  bool PowerSensor::getInUse(unsigned int sensorID) const {
    return sensors[sensorID].inUse;
  }

  int PowerSensor::getPolarity(unsigned int sensorID) const {
    int polarity;
    if (sensors[sensorID].sensitivity >= 0) {
      polarity = 1;
    } else {
      polarity = -1;
    }
    return polarity;
  }

  void PowerSensor::setType(unsigned int sensorID, const std::string type) {
    sensors[sensorID].setType(type);
  }

  void PowerSensor::setPairName(unsigned int pairID, const std::string pairName) {
    checkPairID(pairID);
    // set name of both sensors of the pair
    sensors[2 * pairID].setPairName(pairName);
    sensors[2 * pairID + 1].setPairName(pairName);
  }

  void PowerSensor::setVref(unsigned int sensorID, const float vref) {
    sensors[sensorID].setVref(vref);
  }

  void PowerSensor::setSensitivity(unsigned int sensorID, const float sensitivity) {
    sensors[sensorID].setSensitivity(sensitivity);
  }

  void PowerSensor::setInUse(unsigned int sensorID, const bool inUse) {
    sensors[sensorID].setInUse(inUse);
  }

  void PowerSensor::setPolarity(unsigned int sensorID, const int polarity) {
    sensors[sensorID].setPolarity(polarity);
  }

}  // namespace PowerSensor3