Changeset - 5c809411253d
[Not reviewed]
stream
0 4 0
Hasan Yavuz ÖZDERYA - 7 years ago 2018-04-08 13:28:06
hy@ozderya.net
added reworked FramedReader with basic tests
4 files changed with 69 insertions and 25 deletions:
0 comments (0 inline, 0 general)
src/framedreader.cpp
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot 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.
 

	
 
  serialplot 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 serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtDebug>
 
#include <QtEndian>
 
#include "floatswap.h"
 

	
 
#include "framedreader.h"
 

	
 
FramedReader::FramedReader(QIODevice* device, ChannelManager* channelMan,
 
                           DataRecorder* recorder, QObject* parent) :
 
    AbstractReader(device, channelMan, recorder, parent)
 
FramedReader::FramedReader(QIODevice* device, QObject* parent) :
 
    AbstractReader(device, parent)
 
{
 
    paused = false;
 

	
 
    // initial settings
 
    settingsInvalid = 0;
 
    _numOfChannels = _settingsWidget.numOfChannels();
 
    _numChannels = _settingsWidget.numOfChannels();
 
    hasSizeByte = _settingsWidget.frameSize() == 0;
 
    frameSize = _settingsWidget.frameSize();
 
    syncWord = _settingsWidget.syncWord();
 
    checksumEnabled = _settingsWidget.isChecksumEnabled();
 
    onNumberFormatChanged(_settingsWidget.numberFormat());
 
    debugModeEnabled = _settingsWidget.isDebugModeEnabled();
 
    checkSettings();
 

	
 
    // init setting connections
 
    connect(&_settingsWidget, &FramedReaderSettings::numberFormatChanged,
 
            this, &FramedReader::onNumberFormatChanged);
 

	
 
@@ -64,35 +63,36 @@ FramedReader::FramedReader(QIODevice* de
 
}
 

	
 
void FramedReader::enable(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        connect(_device, &QIODevice::readyRead,
 
                this, &FramedReader::onDataReady);
 
    }
 
    else
 
    {
 
        QObject::disconnect(_device, 0, this, 0);
 
        disconnectSinks();
 
    }
 
}
 

	
 
QWidget* FramedReader::settingsWidget()
 
{
 
    return &_settingsWidget;
 
}
 

	
 
unsigned FramedReader::numOfChannels()
 
unsigned FramedReader::numChannels() const
 
{
 
    return _numOfChannels;
 
    return _numChannels;
 
}
 

	
 
void FramedReader::pause(bool enabled)
 
{
 
    paused = enabled;
 
}
 

	
 
void FramedReader::onNumberFormatChanged(NumberFormat numberFormat)
 
{
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
@@ -136,55 +136,55 @@ void FramedReader::checkSettings()
 
{
 
    // sync word is invalid (empty or missing a nibble at the end)
 
    if (!syncWord.size())
 
    {
 
        settingsInvalid |= SYNCWORD_INVALID;
 
    }
 
    else // sync word is valid
 
    {
 
        settingsInvalid &= ~SYNCWORD_INVALID;
 
    }
 

	
 
    // check if fixed frame size is multiple of a sample set size
 
    if (!hasSizeByte && frameSize % (_numOfChannels * sampleSize) != 0)
 
    if (!hasSizeByte && frameSize % (_numChannels * sampleSize) != 0)
 
    {
 
        settingsInvalid |= FRAMESIZE_INVALID;
 
    }
 
    else
 
    {
 
        settingsInvalid &= ~FRAMESIZE_INVALID;
 
    }
 

	
 
    // show an error message
 
    if (settingsInvalid & SYNCWORD_INVALID)
 
    {
 
        _settingsWidget.showMessage("Sync word is invalid!", true);
 
    }
 
    else if (settingsInvalid & FRAMESIZE_INVALID)
 
    {
 
        QString errorMessage =
 
            QString("Frame size must be multiple of %1 (#channels * sample size)!")\
 
            .arg(_numOfChannels * sampleSize);
 
            .arg(_numChannels * sampleSize);
 

	
 
        _settingsWidget.showMessage(errorMessage, true);
 
    }
 
    else
 
    {
 
        _settingsWidget.showMessage("All is well!");
 
    }
 
}
 

	
 
void FramedReader::onNumOfChannelsChanged(unsigned value)
 
{
 
    _numOfChannels = value;
 
    _numChannels = value;
 
    checkSettings();
 
    reset();
 
    emit numOfChannelsChanged(value);
 
}
 

	
 
void FramedReader::onSyncWordChanged(QByteArray word)
 
{
 
    syncWord = word;
 
    checkSettings();
 
    reset();
 
}
 

	
 
@@ -229,29 +229,29 @@ void FramedReader::onDataReady()
 
            }
 
        }
 
        else if (hasSizeByte && !gotSize) // skipped if fixed frame size
 
        {
 
            frameSize = 0;
 
            _device->getChar((char*) &frameSize);
 

	
 
            if (frameSize == 0) // check size
 
            {
 
                qCritical() << "Frame size is 0!";
 
                reset();
 
            }
 
            else if (frameSize % (_numOfChannels * sampleSize) != 0)
 
            else if (frameSize % (_numChannels * sampleSize) != 0)
 
            {
 
                qCritical() <<
 
                    QString("Frame size is not multiple of %1 (#channels * sample size)!") \
 
                    .arg(_numOfChannels * sampleSize);
 
                    .arg(_numChannels * sampleSize);
 
                reset();
 
            }
 
            else
 
            {
 
                if (debugModeEnabled) qDebug() << "Frame size:" << frameSize;
 
                gotSize = true;
 
            }
 
        }
 
        else // read data bytes
 
        {
 
            // have enough data bytes? (+1 for checksum)
 
            if (bytesAvailable < (checksumEnabled ? frameSize+1 : frameSize))
 
@@ -278,56 +278,53 @@ void FramedReader::reset()
 

	
 
// Important: this function assumes device has enough bytes to read a full frames data and checksum
 
void FramedReader::readFrameDataAndCheck()
 
{
 
    // if paused just read and waste data
 
    if (paused)
 
    {
 
        _device->read((checksumEnabled ? frameSize+1 : frameSize));
 
        return;
 
    }
 

	
 
    // a package is 1 set of samples for all channels
 
    unsigned numOfPackagesToRead = frameSize / (_numOfChannels * sampleSize);
 
    double* channelSamples = new double[numOfPackagesToRead * _numOfChannels];
 

	
 
    unsigned numOfPackagesToRead = frameSize / (_numChannels * sampleSize);
 
    SamplePack samples(numOfPackagesToRead, _numChannels);
 
    for (unsigned i = 0; i < numOfPackagesToRead; i++)
 
    {
 
        for (unsigned int ci = 0; ci < _numOfChannels; ci++)
 
        for (unsigned int ci = 0; ci < _numChannels; ci++)
 
        {
 
            channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
 
            samples.data(ci)[i] = (this->*readSample)();
 
        }
 
    }
 

	
 
    // read checksum
 
    unsigned rChecksum = 0;
 
    bool checksumPassed = false;
 
    if (checksumEnabled)
 
    {
 
        _device->read((char*) &rChecksum, 1);
 
        calcChecksum &= 0xFF;
 
        checksumPassed = (calcChecksum == rChecksum);
 
    }
 

	
 
    if (!checksumEnabled || checksumPassed)
 
    {
 
        // commit data
 
        addData(channelSamples, numOfPackagesToRead*_numOfChannels);
 
        feedOut(samples);
 
    }
 
    else
 
    {
 
        qCritical() << "Checksum failed! Received:" << rChecksum << "Calculated:" << calcChecksum;
 
    }
 

	
 
    delete[] channelSamples;
 
}
 

	
 
template<typename T> double FramedReader::readSampleAs()
 
{
 
    T data;
 

	
 
    _device->read((char*) &data, sizeof(data));
 

	
 
    if (checksumEnabled)
 
    {
 
        for (unsigned i = 0; i < sizeof(data); i++)
 
        {
src/framedreader.h
Show inline comments
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot 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.
 

	
 
  serialplot 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.
 
@@ -24,48 +24,47 @@
 

	
 
#include "abstractreader.h"
 
#include "framedreadersettings.h"
 

	
 
/**
 
 * Reads data in a customizable framed format.
 
 */
 
class FramedReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit FramedReader(QIODevice* device, ChannelManager* channelMan,
 
                          DataRecorder* recorder, QObject *parent = 0);
 
    explicit FramedReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    unsigned numOfChannels();
 
    unsigned numChannels() const;
 
    void enable(bool enabled = true);
 
    /// Stores settings into a `QSettings`
 
    void saveSettings(QSettings* settings);
 
    /// Loads settings from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    void pause(bool);
 

	
 
private:
 
    /// bit wise fields for `settingsValid` member
 
    enum SettingInvalidFlag
 
    {
 
        SYNCWORD_INVALID = 1,
 
        FRAMESIZE_INVALID = 2
 
    };
 

	
 
    // settings related members
 
    FramedReaderSettings _settingsWidget;
 
    unsigned _numOfChannels;
 
    unsigned _numChannels;
 
    unsigned sampleSize;
 
    bool paused;
 
    unsigned settingsInvalid;   /// settings are all valid if this is 0, if not no reading is done
 
    QByteArray syncWord;
 
    bool checksumEnabled;
 
    bool hasSizeByte;
 
    unsigned frameSize;
 
    bool debugModeEnabled;
 

	
 
    /// Checks the validity of syncWord and frameSize then shows an
 
    /// error message. Also updates `settingsInvalid`. If settings are
 
    /// valid `settingsInvalid` should be `0`.
tests/CMakeLists.txt
Show inline comments
 
@@ -34,38 +34,42 @@ add_executable(Test EXCLUDE_FROM_ALL
 
  ../src/ringbuffer.cpp
 
  ../src/readonlybuffer.cpp
 
  ../src/stream.cpp
 
  ../src/streamchannel.cpp
 
  ../src/channelinfomodel.cpp
 
  )
 
add_test(NAME test1 COMMAND Test)
 
qt5_use_modules(Test Widgets)
 

	
 
qt5_wrap_ui(UI_FILES_T
 
  ../src/binarystreamreadersettings.ui
 
  ../src/asciireadersettings.ui
 
  ../src/framedreadersettings.ui
 
  ../src/numberformatbox.ui
 
  ../src/endiannessbox.ui
 
  )
 

	
 
# test for readers
 
add_executable(TestReaders EXCLUDE_FROM_ALL
 
  test_readers.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/abstractreader.cpp
 
  ../src/binarystreamreader.cpp
 
  ../src/binarystreamreadersettings.cpp
 
  ../src/asciireader.cpp
 
  ../src/asciireadersettings.cpp
 
  ../src/framedreader.cpp
 
  ../src/framedreadersettings.cpp
 
  ../src/commandedit.cpp
 
  ../src/endiannessbox.cpp
 
  ../src/numberformatbox.cpp
 
  ../src/numberformat.cpp
 
  ${UI_FILES_T}
 
  )
 
qt5_use_modules(TestReaders Widgets Test)
 
add_test(NAME test_readers COMMAND TestReaders)
 

	
 
set(CMAKE_CTEST_COMMAND ctest -V)
 
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
 
add_dependencies(check Test TestReaders)
tests/test_readers.cpp
Show inline comments
 
@@ -16,24 +16,25 @@
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
// This tells Catch to provide a main() - only do this in one cpp file per executable
 
#define CATCH_CONFIG_RUNNER
 
#include "catch.hpp"
 

	
 
#include <QSignalSpy>
 
#include <QBuffer>
 
#include "binarystreamreader.h"
 
#include "asciireader.h"
 
#include "framedreader.h"
 

	
 
#include "test_helpers.h"
 

	
 
static const int READYREAD_TIMEOUT = 10; // milliseconds
 

	
 
TEST_CASE("reading data with BinaryStreamReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    BinaryStreamReader bs(&bufferDev);
 
    bs.enable(true);
 

	
 
    TestSink sink;
 
@@ -112,22 +113,65 @@ TEST_CASE("AsciiReader shouldn't read wh
 
    // inject data to the buffer
 
    bufferDev.open(QIODevice::ReadWrite);
 
    bufferDev.write("0,1,3\n0,1,3\n0,1,3\n0,1,3\n");
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
TEST_CASE("reading data with FramedReader", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 
    reader.enable(true);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 4);
 
}
 

	
 
TEST_CASE("FramedReader shouldn't read when disabled", "[reader]")
 
{
 
    QBuffer bufferDev;
 
    FramedReader reader(&bufferDev);
 

	
 
    TestSink sink;
 
    reader.connectSink(&sink);
 

	
 
    REQUIRE(sink._numChannels == 1);
 
    REQUIRE(sink._hasX == false);
 

	
 
    bufferDev.open(QIODevice::ReadWrite);
 
    const uint8_t data[] = {0xAA, 0xBB, 4, 0x01, 0x02, 0x03, 0x04};
 
    bufferDev.write((const char*) data, 7);
 
    bufferDev.seek(0);
 

	
 
    QSignalSpy spy(&bufferDev, SIGNAL(readyRead()));
 
    REQUIRE_FALSE(spy.wait(READYREAD_TIMEOUT));
 
    REQUIRE(sink.totalFed == 0);
 
}
 

	
 
// Note: this is added because `QApplication` must be created for widgets
 
#include <QApplication>
 
int main(int argc, char* argv[])
 
{
 
    QApplication a(argc, argv);
 

	
 
    int result = Catch::Session().run( argc, argv );
 

	
 
    return result;
 
}
0 comments (0 inline, 0 general)