Isobuffer refactor - part 2 (#65)

* Split isoBuffer::maybeOutputSampleToFile into two functions

* reorder isoBuffer data members. clean up comments and formatting. clean isoBuffer::writeBuffer

* Remove my name from a comment

* Use type-safe Qt connect. Move comments. change IO column comparison.

* Give isoBuffer the proper ringBuffer behaviour

* Change the behaviour of isoBuffer::readBuffer; Change comments to reflect this.

* Fix a silly mistake in isobuffer.cpp (compiler warnings are awesome)

* Modify isoBuffer::readBuffer

* add destructor to isoBuffer, reorder initializer list. isobuffer.cpp now compiles with no warnings.

* Change isoBuffer::m_buffer to be a unique_ptr

* Change isoBuffer::m_stopDecoding to isoBuffer::m_isDecoding

* Refactor isoBuffer::readBuffer to use unique_ptr. Currently a memory leak

* Change signature of isoBuffer::readBuffer to return unique_ptr. adapt isoDriver acordingly.

* Clean up comments on isobuffer .h and .cpp

* Incorporate review comments: formatting, explicit unique_ptr, spelling
This commit is contained in:
Sebastián Mestre 2019-01-27 21:14:04 -03:00 committed by Chris Esposito
parent 31f68faf4e
commit 57c7f5f61f
5 changed files with 107 additions and 112 deletions

View File

@ -1,9 +1,13 @@
#include "isobuffer.h"
#include <algorithm>
#include "isodriver.h"
#include "uartstyledecoder.h"
namespace {
static char const * fileHeaderFormat =
namespace
{
constexpr char const* fileHeaderFormat =
"EspoTek Labrador DAQ V1.0 Output File\n"
"Averaging = %d\n"
"Mode = %d\n";
@ -23,18 +27,20 @@ namespace {
isoBuffer::isoBuffer(QWidget* parent, int bufferLen, isoDriver* caller, unsigned char channel_value)
: QWidget(parent)
, m_buffer((short*)calloc(bufferLen*2, sizeof(short)))
, m_channel(channel_value)
, m_buffer(std::make_unique<short[]>(bufferLen*2))
, m_bufferEnd(bufferLen-1)
, m_samplesPerSecond(bufferLen/21.0/375*VALID_DATA_PER_375)
, m_sampleRate_bit(bufferLen/21.0/375*VALID_DATA_PER_375*8)
, m_virtualParent(caller)
, m_channel(channel_value)
{
}
// NOTE: the length of half of the allocated buffer is m_bufferEnd+1
void isoBuffer::insertIntoBuffer(short item)
{
m_buffer[m_back] = item;
m_buffer[m_back+m_bufferEnd+1] = item;
m_back++;
m_insertedCount++;
@ -51,49 +57,58 @@ void isoBuffer::insertIntoBuffer(short item)
short isoBuffer::bufferAt(int idx) const
{
return m_buffer[m_back - idx];
// NOTE: this is only correct if idx < m_insertedCount
return m_buffer[m_back + (m_bufferEnd+1) - idx];
}
bool isoBuffer::maybeOutputSampleToFile(double convertedSample)
void isoBuffer::outputSampleToFile(double averageSample)
{
/*
* This function adds a sample to an accumulator and bumps the sample count.
* After the sample count hits some threshold, the accumulated sample is
* outputted to a file. If this 'saturates' the file, then fileIO is disabled.
*/
m_average_sample_temp += convertedSample;
m_fileIO_sampleCount++;
// Check to see if we can write a new sample to file
if (m_fileIO_sampleCount == m_fileIO_maxIncrementedSampleValue)
{
char numStr[32];
sprintf(numStr,"%7.5f, ", m_average_sample_temp/((double)m_fileIO_maxIncrementedSampleValue));
sprintf(numStr,"%7.5f, ", averageSample);
m_currentFile->write(numStr);
m_currentColumn++;
if (m_currentColumn >= COLUMN_BREAK)
if (m_currentColumn == COLUMN_BREAK)
{
m_currentFile->write("\n");
m_currentColumn = 0;
}
}
// Reset the average and sample count for next data point
void isoBuffer::maybeOutputSampleToFile(double convertedSample)
{
/*
* This function adds a sample to an accumulator and bumps a sample count.
* After the sample count hits some threshold the samples are averaged
* and the average is written to a file.
* If this makes us hit the max. file size, then fileIO is disabled.
*/
m_fileIO_sampleAccumulator += convertedSample;
m_fileIO_sampleCount++;
if (m_fileIO_sampleCount == m_fileIO_sampleCountPerWrite)
{
double averageSample = m_fileIO_sampleAccumulator / m_fileIO_sampleCount;
outputSampleToFile(averageSample);
// Reset the accumulator and sample count for next data point.
m_fileIO_sampleAccumulator = 0;
m_fileIO_sampleCount = 0;
m_average_sample_temp = 0;
// Check to see if we've reached the max file size.
if (m_fileIO_max_file_size != 0) // value of 0 means "no limit"
// value of 0 means "no limit", meaning we must skip the check by returning.
if (m_fileIO_maxFileSize == 0)
return;
// 7 chars(number) + 1 char(comma) + 1 char(space) = 9 bytes/sample.
m_fileIO_numBytesWritten += 9;
if (m_fileIO_numBytesWritten >= m_fileIO_maxFileSize)
{
m_fileIO_numBytesWritten += 9; // 7 chars for the number, 1 for the comma and 1 for the space = 9 bytes per sample.
if (m_fileIO_numBytesWritten >= m_fileIO_max_file_size)
{
m_fileIOEnabled = false; // Just in case signalling fails.
fileIOinternalDisable();
return false;
}
m_fileIOEnabled = false; // Just in case signalling fails.
fileIOinternalDisable();
}
}
return true;
}
template<typename T, typename Function>
@ -109,15 +124,13 @@ void isoBuffer::writeBuffer(T* data, int len, int TOP, Function transform)
{
bool isUsingAC = m_channel == 1
? m_virtualParent->AC_CH1
: m_virtualParent->AC_CH2;
: m_virtualParent->AC_CH2;
for (int i = 0; i < len; i++)
for (int i = 0; i < len && m_fileIOEnabled; i++)
{
double convertedSample = sampleConvert(data[i], TOP, isUsingAC);
bool keepOutputting = maybeOutputSampleToFile(convertedSample);
if (!keepOutputting) break;
maybeOutputSampleToFile(convertedSample);
}
}
}
@ -132,57 +145,39 @@ void isoBuffer::writeBuffer_short(short* data, int len)
writeBuffer(data, len, 2048, [](short item) -> short {return item >> 4;});
}
short* isoBuffer::readBuffer(double sampleWindow, int numSamples, bool singleBit, double delayOffset)
std::unique_ptr<short[]> isoBuffer::readBuffer(double sampleWindow, int numSamples, bool singleBit, double delayOffset)
{
/* Refactor Note:
/*
* The expected behavior is to run backwards over the buffer with a stride
* of timeBetweenSamples steps, and push the touched elements into readData.
* If more elements are requested than how many are stored (1), the buffer
* will be populated only partially. Modifying this function to return null
* or a zero-filled buffer instead should be simple enough.
*
* Refactoring this function took a few passes were i made some assumptions:
* - round() should be replaced by floor() where it was used
* - int(floor(x)) and int(x) are equivalent (since we are always positive)
* - free(NULL) is a no-op. This is mandated by the C standard, and virtually all
* implementations comply. A few known exceptions are:
* - PalmOS
* - 3BSD
* - UNIX 7
* I do not know of any non-compliant somewhat modern implementations.
*
* The expected behavior is to cycle backwards over the buffer, taking into
* acount only the part of the buffer that has things stored, with a stride
* of timeBetweenSamples steps, and insert the touched elements into readData.
*
* ~Sebastian Mestre
* (1) m_insertedCount < (delayOffset + sampleWindow) * m_samplesPerSecond
*/
const double timeBetweenSamples = sampleWindow * m_samplesPerSecond / numSamples;
const int delaySamples = delayOffset * m_samplesPerSecond;
free(m_readData);
std::unique_ptr<short[]> readData = std::make_unique<short[]>(numSamples);
m_readData = (short*) calloc(numSamples, sizeof(short));
std::fill (readData.get(), readData.get() + numSamples, short(0));
// TODO: replace by return nullptr and add error handling upstream
if(delaySamples+1 > m_insertedCount)
double itr = delaySamples;
for (int i = 0; i < numSamples && itr < m_insertedCount; i++)
{
return m_readData;
}
double itr = delaySamples + 1;
for (int i = 0; i < numSamples; i++)
{
while (itr > m_insertedCount)
itr -= m_insertedCount;
m_readData[i] = bufferAt(int(itr));
readData[i] = bufferAt(int(itr));
if (singleBit)
{
int subIdx = 8*(-itr-floor(-itr));
m_readData[i] &= (1 << subIdx);
readData[i] &= (1 << subIdx);
}
itr += timeBetweenSamples;
}
return m_readData;
return readData;
}
void isoBuffer::clearBuffer()
@ -220,18 +215,17 @@ void isoBuffer::enableFileIO(QFile* file, int samplesToAverage, qulonglong max_f
m_currentFile->write(headerLine);
// Set up the isoBuffer for DAQ
m_fileIO_maxIncrementedSampleValue = samplesToAverage;
m_fileIO_max_file_size = max_file_size;
m_fileIO_maxFileSize = max_file_size;
m_fileIO_sampleCountPerWrite = samplesToAverage;
m_fileIO_sampleCount = 0;
m_fileIO_sampleAccumulator = 0;
m_fileIO_numBytesWritten = 0;
m_average_sample_temp = 0;
// Enable DAQ
m_fileIOEnabled = true;
qDebug("File IO enabled, averaging %d samples, max file size %lluMB", samplesToAverage, max_file_size/1000000);
qDebug() << max_file_size;
return;
}
void isoBuffer::disableFileIO()
@ -282,6 +276,8 @@ short isoBuffer::inverseSampleConvert(double voltageLevel, int TOP, bool AC) con
return sample;
}
// For capacitance measurement. x0, x1 and x2 are all various time points
// used to find the RC coefficient.
template<typename Function>
int isoBuffer::capSample(int offset, int target, double seconds, double value, Function comp)
{
@ -307,7 +303,6 @@ int isoBuffer::capSample(int offset, int target, double seconds, double value, F
return -1;
}
// For capacitance measurement. x0, x1 and x2 are all various time points used to find the RC coefficient.
int isoBuffer::cap_x0fromLast(double seconds, double vbot)
{
return capSample(0, kSamplesSeekingCap, seconds, vbot, fX0Comp);
@ -328,15 +323,14 @@ void isoBuffer::serialManage(double baudRate, UartParity parity)
if (m_decoder == NULL)
{
m_decoder = new uartStyleDecoder(this);
// TODO: Look into using the type safe version of connect.
// NOTE: I believe Qt has a type-safe version of this, without the macros and the
// explicit signature and stuff, i think it uses member-function pointers instead.
connect(m_decoder, SIGNAL(wireDisconnected(int)), m_virtualParent, SLOT(serialNeedsDisabling(int)));
connect(m_decoder, &uartStyleDecoder::wireDisconnected,
m_virtualParent, &isoDriver::serialNeedsDisabling);
}
if (m_stopDecoding)
if (!m_isDecoding)
{
m_decoder->updateTimer->start(CONSOLE_UPDATE_TIMER_PERIOD);
m_stopDecoding = false;
m_isDecoding = true;
}
m_decoder->setParityMode(parity);
m_decoder->serialDecode(baudRate);

View File

@ -1,8 +1,9 @@
#ifndef ISOBUFFER_H
#define ISOBUFFER_H
// TODO: Make object macros constexprs or globals
// TODO: Move headers used only in implementation to isobuffer.cpp
#include <memory>
#include <QWidget>
#include <QString>
#include <QByteArray>
@ -28,15 +29,13 @@ enum class UartParity : uint8_t;
constexpr uint32_t CONSOLE_UPDATE_TIMER_PERIOD = ISO_PACKETS_PER_CTX * 4;
// TODO: Make private what should be private
// TODO: Add m_ prefix to member variables
// TODO: Change integer types to cstdint types
class isoBuffer : public QWidget
{
Q_OBJECT
public:
// TODO: Add consoles as constructor arguments
isoBuffer(QWidget* parent = 0, int bufferLen = 0, isoDriver* caller = 0, unsigned char channel_value = 0);
// TODO?: Add a destructor
~isoBuffer() = default;
// Basic buffer operations
short bufferAt(int idx) const;
@ -52,11 +51,13 @@ public:
void writeBuffer_char(char* data, int len);
void writeBuffer_short(short* data, int len);
// TODO: Change return value to unique_ptr
short* readBuffer(double sampleWindow, int numSamples, bool singleBit, double delayOffset);
std::unique_ptr<short[]> readBuffer(double sampleWindow, int numSamples, bool singleBit, double delayOffset);
// file I/O
bool maybeOutputSampleToFile(double convertedSample);
private:
void outputSampleToFile(double averageSample);
void maybeOutputSampleToFile(double convertedSample);
public:
double sampleConvert(short sample, int TOP, bool AC) const;
short inverseSampleConvert(double voltageLevel, int TOP, bool AC) const;
@ -71,43 +72,41 @@ public:
// ---- MEMBER VARIABLES ----
// Presentantion?
// NOTE: it seems like these are never initialized but they are used as though they were...
// Presentation?
// TODO: Add consoles as constructor arguments
// NOTE: These are initialized in mainwindow.cpp
QPlainTextEdit* m_console1;
QPlainTextEdit* m_console2;
unsigned char m_channel = 255;
bool m_serialAutoScroll = true;
// Internal Storage
std::unique_ptr<short[]> m_buffer;
int m_back = 0;
int m_insertedCount = 0;
int m_bufferEnd;
// Conversion And Sampling
double m_voltage_ref = 1.65;
double m_frontendGain = (R4 / (R3 + R4));
int m_samplesPerSecond;
int m_sampleRate_bit;
// Internal Storage
int m_back = 0;
int m_insertedCount = 0;
int m_bufferEnd;
// TODO: Change buffer to be a unique_ptr
short* m_buffer;
short* m_readData = NULL;
// UARTS decoding
uartStyleDecoder* m_decoder = NULL;
// TODO: change this to keepDecoding
bool m_stopDecoding = false;
bool m_isDecoding = true;
private:
// File I/O
bool m_fileIOEnabled = false;
FILE* m_fptr = NULL;
QFile* m_currentFile;
int m_fileIO_maxIncrementedSampleValue;
int m_fileIO_sampleCountPerWrite;
int m_fileIO_sampleCount;
qulonglong m_fileIO_max_file_size;
double m_fileIO_sampleAccumulator;
qulonglong m_fileIO_maxFileSize;
qulonglong m_fileIO_numBytesWritten;
unsigned int m_currentColumn = 0;
isoDriver* m_virtualParent;
double m_average_sample_temp;
signals:
void fileIOinternalDisable();
public slots:

View File

@ -806,23 +806,23 @@ void isoDriver::frameActionGeneric(char CH1_mode, char CH2_mode) //0 for off, 1
if (CH1_mode == 1){
analogConvert(readData375_CH1, &CH1, 128, AC_CH1, 1);
analogConvert(readData375_CH1.get(), &CH1, 128, AC_CH1, 1);
xmin = (currentVmin < xmin) ? currentVmin : xmin;
xmax = (currentVmax > xmax) ? currentVmax : xmax;
broadcastStats(0);
}
if (CH1_mode == 2) digitalConvert(readData375_CH1, &CH1);
if (CH1_mode == 2) digitalConvert(readData375_CH1.get(), &CH1);
if (CH2_mode == 1){
analogConvert(readData375_CH2, &CH2, 128, AC_CH2, 2);
analogConvert(readData375_CH2.get(), &CH2, 128, AC_CH2, 2);
ymin = (currentVmin < ymin) ? currentVmin : ymin;
ymax = (currentVmax > ymax) ? currentVmax : ymax;
broadcastStats(1);
}
if (CH2_mode == 2) digitalConvert(readData375_CH2, &CH2);
if (CH2_mode == 2) digitalConvert(readData375_CH2.get(), &CH2);
if(CH1_mode == -1) {
analogConvert(readData750, &CH1, 128, AC_CH1, 1);
analogConvert(readData750.get(), &CH1, 128, AC_CH1, 1);
xmin = (currentVmin < xmin) ? currentVmin : xmin;
xmax = (currentVmax > xmax) ? currentVmax : xmax;
broadcastStats(0);
@ -947,7 +947,7 @@ void isoDriver::multimeterAction(){
readData375_CH1 = internalBuffer375_CH1->readBuffer(window,GRAPH_SAMPLES, false, delay + ((triggerEnabled&&!paused_multimeter) ? triggerDelay + window/2 : 0));
QVector<double> x(GRAPH_SAMPLES), CH1(GRAPH_SAMPLES);
analogConvert(readData375_CH1, &CH1, 2048, 0, 1); //No AC coupling!
analogConvert(readData375_CH1.get(), &CH1, 2048, 0, 1); //No AC coupling!
for (double i=0; i<GRAPH_SAMPLES; i++){
x[i] = -(window*i)/((double)(GRAPH_SAMPLES-1)) - delay;
@ -1337,7 +1337,7 @@ double isoDriver::meanVoltageLast(double seconds, unsigned char channel, int TOP
break;
}
short * tempBuffer = currentBuffer->readBuffer(seconds,1024, 0, 0);
std::unique_ptr<short[]> tempBuffer = currentBuffer->readBuffer(seconds, 1024, 0, 0);
double sum = 0;
double temp;
for(int i = 0; i<1024; i++){

View File

@ -96,7 +96,9 @@ private:
void frameActionGeneric(char CH1_mode, char CH2_mode);
//Variables that are just pointers to other classes/vars
QCustomPlot *axes;
short *readData375_CH1, *readData375_CH2, *readData750;
std::unique_ptr<short[]> readData375_CH1;
std::unique_ptr<short[]> readData375_CH2;
std::unique_ptr<short[]> readData750;
float *readDataFile;
char *isoTemp = NULL;
short *isoTemp_short = NULL;

View File

@ -83,7 +83,7 @@ void uartStyleDecoder::serialDecode(double baudRate)
if(allZeroes){
qDebug() << "Wire Disconnect detected!";
wireDisconnected(parent->m_channel);
parent->m_stopDecoding = true;
parent->m_isDecoding = false;
updateTimer->stop();
}
}