Changeset - eab37541abf2
[Not reviewed]
default
0 3 0
Hasan Yavuz ÖZDERYA - 7 years ago 2018-08-20 07:24:20
hy@ozderya.net
simplified ascii parsing, in auto num channels won't change on error #39

also if a parsing error occurs all of the line is ignored
3 files changed with 62 insertions and 35 deletions:
0 comments (0 inline, 0 general)
misc/pseudo_device.py
Show inline comments
 
#!/usr/bin/python3
 
#
 
# This script will create a pseudo terminal, and send dummy data over
 
# it for testing purposes. Note that pseuodo terminal is a unix thing,
 
# this script will not work on Windows.
 
#
 
# Currently this script only outputs ASCII(comma separated) data.
 
#
 
# Copyright © 2015 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/>.
 
#
 

	
 
import os, pty, time, struct, math
 

	
 
def ascii_test_str(port):
 
    text = """
 
1,2,3
 
2,4,6
 
3,8,11
 
1,2,3
 
-1,-1,-1
 
nana
 
0,0,0
 
1,na,na
 
    """
 
    while True:
 
        for line in text.splitlines():
 
            os.write(port, bytes(line+"\r\n", 'utf8'))
 
            time.sleep(1)
 

	
 
def ascii_test(port):
 
    """Put ASCII test data through pseudo terminal."""
 
    print("\n")
 
    nc = 4 # number of channels
 
    for i in range(0, 1000):
 
        data = []
 
        for ci in range(0, nc):
 
            data.append(i*(ci+1))
 
        data = ",".join([str(num) for num in data])
 
        print("<< " + data, end="\r")
 
        os.write(port, bytes(data + "\r\n", 'ASCII'))
 
        time.sleep(0.1)
 
@@ -100,21 +116,22 @@ def frame_test(port, fixed_size=False, h
 
        time.sleep(0.1)
 

	
 
def run():
 
    # create the pseudo terminal
 
    master, slave = pty.openpty()
 

	
 
    master_name = os.ttyname(master)
 
    slave_name = os.ttyname(slave)
 
    print("Master terminal: {}\nSlave terminal: {}".format(master_name, slave_name))
 

	
 
    try:
 
        # float_sine(master)
 
        frame_test(master)
 
        # frame_test(master)
 
        # ascii_test(master)
 
        ascii_test_str(master)
 
    finally:
 
        # close the pseudo terminal files
 
        os.close(master)
 
        os.close(slave)
 

	
 
if __name__=="__main__":
 
    run()
src/asciireader.cpp
Show inline comments
 
@@ -101,72 +101,74 @@ void AsciiReader::onDataReady()
 

	
 
        // parse data
 
        line = line.trimmed();
 

	
 
        // Note: When data coming from pseudo terminal is buffered by
 
        // system CR is converted to LF for some reason. This causes
 
        // empty lines in the input when the port is just opened.
 
        if (line.isEmpty())
 
        {
 
            continue;
 
        }
 

	
 
        auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
 
        const SamplePack* samples = parseLine(line);
 
        if (samples != nullptr) {
 
            // update number of channels if in auto mode
 
            if (autoNumOfChannels ) {
 
                unsigned nc = samples->numChannels();
 
                if (nc != _numChannels) {
 
                    _numChannels = nc;
 
                    updateNumChannels();
 
                    // TODO: is `numOfChannelsChanged` signal still used?
 
                    emit numOfChannelsChanged(nc);
 
                }
 
            }
 

	
 
        unsigned numReadChannels; // effective number of channels to read
 
            Q_ASSERT(samples->numChannels() == _numChannels);
 

	
 
            // commit data
 
            feedOut(*samples);
 
        }
 
    }
 
}
 

	
 
SamplePack* AsciiReader::parseLine(const QString& line) const
 
{
 
    auto separatedValues = line.split(delimiter, QString::SkipEmptyParts);
 
        unsigned numComingChannels = separatedValues.length();
 

	
 
        if (autoNumOfChannels)
 
        {
 
            // did number of channels changed?
 
            if (numComingChannels != _numChannels)
 
    // check number of channels (skipped if auto num channels is enabled)
 
    if ((!numComingChannels) || (!autoNumOfChannels && numComingChannels != _numChannels))
 
            {
 
                _numChannels = numComingChannels;
 
                updateNumChannels();
 
                emit numOfChannelsChanged(numComingChannels);
 
            }
 
            numReadChannels = numComingChannels;
 
        }
 
        else if (numComingChannels >= _numChannels)
 
        {
 
            numReadChannels = _numChannels;
 
        }
 
        else // there is missing channel data
 
        {
 
            numReadChannels = separatedValues.length();
 
            qWarning() << "Incoming data is missing data for some channels!";
 
        qWarning() << "Line parsing error: invalid number of channels!";
 
            qWarning() << "Read line: " << line;
 
        return nullptr;
 
        }
 

	
 
        // parse read line
 
        unsigned numDataBroken = 0;
 
        SamplePack samples(1, _numChannels);
 
        for (unsigned ci = 0; ci < numReadChannels; ci++)
 
    // parse data per channel
 
    auto samples = new SamplePack(1, numComingChannels);
 
    for (unsigned ci = 0; ci < numComingChannels; ci++)
 
        {
 
            bool ok;
 
            samples.data(ci)[0] = separatedValues[ci].toDouble(&ok);
 
        samples->data(ci)[0] = separatedValues[ci].toDouble(&ok);
 
            if (!ok)
 
            {
 
                qWarning() << "Data parsing error for channel: " << ci;
 
                qWarning() << "Read line: " << line;
 
                samples.data(ci)[0] = 0;
 
                numDataBroken++;
 

	
 
            delete samples;
 
            return nullptr;
 
            }
 
        }
 

	
 
        if (numReadChannels > numDataBroken)
 
        {
 
            // commit data
 
            feedOut(samples);
 
        }
 
    }
 
    return samples;
 
}
 

	
 
void AsciiReader::saveSettings(QSettings* settings)
 
{
 
    _settingsWidget.saveSettings(settings);
 
}
 

	
 
void AsciiReader::loadSettings(QSettings* settings)
 
{
 
    _settingsWidget.loadSettings(settings);
 
}
src/asciireader.h
Show inline comments
 
@@ -12,25 +12,27 @@
 
  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/>.
 
*/
 

	
 
#ifndef ASCIIREADER_H
 
#define ASCIIREADER_H
 

	
 
#include <QSettings>
 
#include <QString>
 

	
 
#include "samplepack.h"
 
#include "abstractreader.h"
 
#include "asciireadersettings.h"
 

	
 
class AsciiReader : public AbstractReader
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit AsciiReader(QIODevice* device, QObject *parent = 0);
 
    QWidget* settingsWidget();
 
    unsigned numChannels() const;
 
    void enable(bool enabled) override;
 
@@ -41,15 +43,21 @@ public:
 

	
 
private:
 
    AsciiReaderSettings _settingsWidget;
 
    unsigned _numChannels;
 
    /// number of channels will be determined from incoming data
 
    unsigned autoNumOfChannels;
 
    QChar delimiter; ///< selected column delimiter
 

	
 
    bool firstReadAfterEnable = false;
 

	
 
private slots:
 
    void onDataReady() override;
 
    /**
 
     * Parses given line and returns sample pack.
 
     *
 
     * Returns `nullptr` in case of error.
 
     */
 
    SamplePack* parseLine(const QString& line) const;
 
};
 

	
 
#endif // ASCIIREADER_H
0 comments (0 inline, 0 general)