(qRound(m_sampleTime / 10.)));
QDateTime dt = m_graphStartDateTime.addMSecs(i * m_sampleTime * 100);
- QString line = QString("%1\t%2\t%3\n").arg(dt.toString("dd.MM.yyyy\tHH:mm:ss:zzz")).arg((*m_array)[i], 0, 'f').arg(m_unit);
+ //timestamp: ISO8601
+ double deltaTime = (dt.toMSecsSinceEpoch()-m_graphStartDateTime.toMSecsSinceEpoch())/1000.0f;
+ line = QString("%1;%2;%3;%4\n").arg(dt.toString("yyyy-MM-ddTHH:mm:ss,zzz")).arg(deltaTime).arg((*m_array)[i], 0, 'f').arg(m_unit);
ts << line;
}
m_dirty = false;
@@ -1178,6 +1186,188 @@ bool DMMGraph::exportDataSLOT()
return false;
}
+
+void DMMGraph::importDataSLOT()
+{
+ if (m_dirty && m_alertUnsaved)
+ {
+ QMessageBox question;
+ question.setWindowTitle(tr("QtDMM: Unsaved data"));
+ question.setText(tr("Unsaved data"
+ "Importing data will overwrite your measured data"
+ "
Do you want to export your unsaved data first?"));
+ question.setIcon(QMessageBox::Question);
+
+ // Standard-Buttons
+ question.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
+ question.setDefaultButton(QMessageBox::Yes);
+ question.setEscapeButton(QMessageBox::Cancel);
+
+ QAbstractButton *yesButton = question.button(QMessageBox::Yes);
+ if (yesButton)
+ yesButton->setText(tr("Export data first"));
+
+ QAbstractButton *noButton = question.button(QMessageBox::No);
+ if (noButton)
+ noButton->setText(tr("Import & overwrite data"));
+
+ switch (question.exec())
+ {
+ case QMessageBox::Yes:
+ exportDataSLOT();
+ return;
+ case QMessageBox::Cancel:
+ return;
+ }
+ }
+ QDir path;
+ QString fn = QFileDialog::getOpenFileName(this, tr("Import data"), m_cfg->getString("QtDMM/LastUsesPath", tr("CSV (*.csv);;All files (*)")));
+
+ int cnt = 0;
+ int sample = 0;
+
+ QDateTime graphEnd;
+
+ if (!fn.isNull())
+ {
+ m_cfg->setString("QtDMM/LastUsesPath", path.absoluteFilePath(fn));
+ // First pass -> figure out size and sample time
+ QFile file(fn);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ Q_EMIT error(tr("Cannot open file."));
+ return;
+ }
+
+ QStringList token;
+ QStringList dateToken;
+ QStringList timeToken;
+ QStringList timeParts;
+
+ QTextStream ts(&file);
+
+ QString line = ts.readLine();
+ if (line.isNull())
+ {
+ Q_EMIT error(tr("Oops! Seems not to be a valid file"));
+ file.close();
+ return;
+ }
+
+ // skip CSV-Header
+ if (line.startsWith("timestamp"))
+ {
+ line = ts.readLine();
+ if (line.isNull())
+ {
+ Q_EMIT error(tr("File contains only header"));
+ file.close();
+ return;
+ }
+ }
+
+ QRegularExpression reLegacy(
+ R"(^(?\d{2})\.(?\d{2})\.(?\d{4})\t(?\d{2}):(?\d{2}):(?\d{2}):(?\d{1,3})\t(?-?\d+(?:\.\d+)?|nan)\t(?.*)$)",
+ QRegularExpression::CaseInsensitiveOption
+ );
+
+ QRegularExpression reCSV(
+ R"(^(?\d{4})-(?\d{2})-(?\d{2})T(?\d{2}):(?\d{2}):(?\d{2})[,\.](?\d{1,3});(?-?\d+(?:\.\d+)?|nan);(?-?\d+(?:\.\d+)?|nan);(?.*)$)",
+ QRegularExpression::CaseInsensitiveOption
+ );
+
+ // detect format
+ bool isLegacy = reLegacy.match(line).hasMatch();
+
+ //(*m_array).clear();
+ QVector values;
+ QRegularExpressionMatch match;
+ do
+ {
+ if (!line.trimmed().isEmpty())
+ {
+ match = isLegacy ? reLegacy.match(line) : reCSV.match(line);
+
+ if (!match.hasMatch())
+ {
+ qInfo() << line;
+ Q_EMIT error(tr("Oops! Seems not to be a valid file"));
+ file.close();
+ return;
+ }
+
+ QDate valueDate(
+ match.captured("year").toInt(),
+ match.captured("month").toInt(),
+ match.captured("day").toInt()
+ );
+
+ QTime valueTime(
+ match.captured("hour").toInt(),
+ match.captured("minute").toInt(),
+ match.captured("second").toInt(),
+ match.captured("ms").toInt()
+ );
+
+ if (values.isEmpty())
+ {
+ setUnit(match.captured("unit"));
+ m_graphStartDateTime = QDateTime(valueDate, valueTime);
+ }
+
+ graphEnd = QDateTime(valueDate, valueTime);
+ sample += m_graphStartDateTime.secsTo(graphEnd);
+ values << (match.captured("value") == "nan" ? 0.0f : match.captured("value").toDouble());
+ }
+
+ line = ts.readLine();
+ }
+ while (!line.isNull());
+ file.close();
+
+ int cnt = values.size();
+ m_sampleTime = (sample / (cnt > 1 ? cnt - 1 : 1))/10;
+ if (m_sampleTime<1) m_sampleTime=1;
+ qInfo() << m_graphStartDateTime.secsTo( graphEnd ) << m_sampleTime;
+ //m_sampleTime = m_graphStartDateTime.secsTo( graphEnd ) / (cnt > 1 ? cnt - 1 : 1);
+
+ int size = m_size * m_sampleTime;
+
+ if (cnt > 1)
+ {
+ Q_EMIT sampleTime(m_sampleTime);
+ m_sampleTime = (sample / (cnt - 1))/10;
+ }
+ if (m_sampleTime<1) m_sampleTime=1;
+ qInfo() << sample << cnt << m_sampleTime << m_graphStartDateTime.secsTo( graphEnd );
+
+ m_scaleMin = 1e40;
+ m_scaleMax = -1e40;
+
+ // TEST
+ setGraphSize(size, cnt * m_sampleTime);
+
+ for(int i=0; isetText(tr("Export data first"));
@@ -1240,8 +1429,13 @@ void DMMGraph::importDataSLOT()
if (!line.isNull())
{
- QRegularExpression re(R"(^\d+\.\d+\.\d+\t\d+:\d+:\d+(?:\.\d+)?\t-?\d*\.\d+\t.*$)");
- if (!re.match(line).hasMatch())
+ QRegularExpression reLegacy(
+ R"(^(?\d{2})\.(?\d{2})\.(?\d{4})\t(?\d{2}):(?\d{2}):(?\d{2}):(?\d{1,3})\t(?-?\d+(?:\.\d+)?)\t.*$)"
+ );
+ QRegularExpression reCSV(
+ R"(^(?\d{4})-(?\d{2})-(?\d{2})T(?\d{2}):(?\d{2}):(?\d{2}),(?\d{1,3});(?-?\d+(?:\.\d+)?);(?-?\d+(?:\.\d+)?);(?.+)$)"
+ );
+ if (!reCSV.match(line).hasMatch())
{
Q_EMIT error(tr("Oops! Seems not to be a valid file"));
@@ -1321,12 +1515,12 @@ void DMMGraph::importDataSLOT()
m_sampleTime = sample / (cnt - 1);
}
- /* if (cnt*m_sampleTime > length)
- {
- if (size > cnt*m_sampleTime) size = cnt*m_sampleTime;
- emit graphSize( size, cnt*m_sampleTime );
- setGraphSize( size, cnt*m_sampleTime );
- }*/
+ // if (cnt*m_sampleTime > length)
+ // {
+ // if (size > cnt*m_sampleTime) size = cnt*m_sampleTime;
+ // emit graphSize( size, cnt*m_sampleTime );
+ // setGraphSize( size, cnt*m_sampleTime );
+ // }
m_scaleMin = 1e40;
m_scaleMax = -1e40;
@@ -1373,6 +1567,10 @@ void DMMGraph::importDataSLOT()
}
}
+*/
+
+
+
void DMMGraph::setThresholds(double falling, double raising)
{
m_fallingThreshold = falling;
diff --git a/src/dmmgraph.h b/src/dmmgraph.h
index ae1878c..426b1da 100644
--- a/src/dmmgraph.h
+++ b/src/dmmgraph.h
@@ -150,7 +150,7 @@ protected Q_SLOTS:
double m_xstep;
double m_hUnitFact;
double m_maxUnit;
- int m_sampleTime;
+ double m_sampleTime;
int m_sampleLength;
bool m_running;
bool m_connected;
diff --git a/src/dmmprefs.cpp b/src/dmmprefs.cpp
index fd659fe..7b2e1b3 100644
--- a/src/dmmprefs.cpp
+++ b/src/dmmprefs.cpp
@@ -45,6 +45,21 @@ DmmPrefs::DmmPrefs(QWidget *parent) : PrefWidget(parent)
" also a number of predefined models.");
m_pixmap = new QPixmap(":/Symbols/dmm.xpm");
+ setupComboBoxModel();
+
+ message2->hide();
+
+ m_path = QDir::currentPath();
+}
+
+
+DmmPrefs::~DmmPrefs()
+{
+ delete m_pixmap;
+}
+
+void DmmPrefs::setupComboBoxModel()
+{
ui_model->clear();
ui_model->insertItem(-1, tr("Manual settings"));
@@ -61,15 +76,27 @@ DmmPrefs::DmmPrefs(QWidget *parent) : PrefWidget(parent)
ui_model->addItem(cfg.name);
}
- message2->hide();
-
- m_path = QDir::currentPath();
-}
+ QCompleter *completer = new QCompleter(ui_model->model(), this);
+ completer->setCaseSensitivity(Qt::CaseInsensitive);
+ completer->setFilterMode(Qt::MatchContains);
+ completer->setCompletionMode(QCompleter::PopupCompletion);
+ ui_model->setCompleter(completer);
-DmmPrefs::~DmmPrefs()
-{
- delete m_pixmap;
+ connect(ui_model->lineEdit(), &QLineEdit::editingFinished, this, [this]()
+ {
+ bool found = false;
+ for (int i = 0; i < ui_model->count(); ++i)
+ {
+ if (ui_model->itemText(i).compare(ui_model->currentText(), Qt::CaseInsensitive) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ ui_model->setCurrentIndex(-1);
+ });
}
QString DmmPrefs::deviceListText() const
diff --git a/src/dmmprefs.h b/src/dmmprefs.h
index c75b93b..1d5d814 100644
--- a/src/dmmprefs.h
+++ b/src/dmmprefs.h
@@ -64,7 +64,7 @@ protected Q_SLOTS:
protected:
QString m_path;
DmmDecoder::DMMInfo m_dmmInfo;
-
-private:
QStringListModel *m_portlist;
+
+ void setupComboBoxModel();
};
diff --git a/src/guiprefs.cpp b/src/guiprefs.cpp
index afc8758..c99ea88 100644
--- a/src/guiprefs.cpp
+++ b/src/guiprefs.cpp
@@ -103,8 +103,8 @@ void GuiPrefs::setToolbarVisibility(bool disp, bool dmm, bool graph, bool file)
void GuiPrefs::applySLOT()
{
- m_cfg->setInt("QtDMM/version", 0);
- m_cfg->setInt("QtDMM/revision", 84);
+ m_cfg->setInt("QtDMM/version", 0); // TODO set version by cmake
+ m_cfg->setInt("QtDMM/revision", 84); // TODO set revision by cmake
m_cfg->setBool("QtDMM/show-tip", showTip());
m_cfg->setBool("Save/window-pos", saveWindowPosition());
m_cfg->setBool("Save/window-size", saveWindowSize());
diff --git a/src/instancesdlg.cpp b/src/instancesdlg.cpp
new file mode 100644
index 0000000..3bce2b6
--- /dev/null
+++ b/src/instancesdlg.cpp
@@ -0,0 +1,144 @@
+#include
+#include
+
+#include "instancesdlg.h"
+
+
+InstancesDlg::InstancesDlg(Settings *settings, QString instance_id, QString config_path, QWidget *parent)
+ : QDialog(parent)
+ , m_instanceId(instance_id.isEmpty() ? "default" : instance_id)
+ , m_configPath(config_path)
+ , m_settings(settings)
+{
+ setupUi(this);
+ ui_instancesList->setStyleSheet("background-color: palette(window);");
+}
+
+void InstancesDlg::setInstancesOnline(QStringList instances)
+{
+ m_instancesOnline = instances;
+ updateInstancesListBox();
+}
+
+void InstancesDlg::updateInstancesListBox()
+{
+ ui_instancesList->clear();
+ m_instancesConfigured = m_settings->getConfigInstances();
+
+ QStringList instances = m_instancesConfigured;
+ for (auto &instanceId : m_instancesOnline)
+ if (!instances.contains(instanceId))
+ instances << instanceId;
+
+ for (auto &instanceId : m_instancesConfigured)
+ {
+ QListWidgetItem *wi_item = new QListWidgetItem(ui_instancesList);
+ wi_item->setSizeHint(QSize(150, 40));
+
+ QPushButton *btn = new QPushButton(instanceId, ui_instancesList);
+ btn->setProperty("instanceId", instanceId);
+ QFont font = btn->font();
+ if (instanceId != m_instanceId)
+ font.setBold(true);
+ font.setPointSize(font.pointSize() + 2);
+ btn->setFont(font);
+
+ if (instanceId == m_instanceId || (m_onDelete && m_instancesOnline.contains(instanceId)))
+ btn->setEnabled(false);
+ else if (m_onDelete)
+ {
+ btn->setCheckable(true);
+ }
+ else
+ {
+ btn->setCheckable(false);
+ }
+
+ connect(btn, SIGNAL(clicked()), this, SLOT(onInstanceButtonClicked()));
+
+ ui_instancesList->addItem(wi_item);
+ ui_instancesList->setItemWidget(wi_item, btn);
+ }
+}
+
+
+void InstancesDlg::onInstanceButtonClicked()
+{
+ if (m_onDelete)
+ return;
+
+ QPushButton *btn = qobject_cast(sender());
+ if (!btn)
+ return;
+
+ QString configId = btn->property("instanceId").toString();
+ QString exePath = QCoreApplication::applicationFilePath();
+
+ if (configId == m_instanceId)
+ return;
+
+ if (m_instancesOnline.contains(configId))
+ Q_EMIT writeState("RAISE_"+configId);
+ else
+ {
+ QStringList args;
+ if (configId != "default")
+ args << "--config-id" << configId;
+ if (!m_configPath.isEmpty())
+ args << "--config-dir" << m_configPath;
+
+ QProcess::startDetached(exePath, args);
+ }
+}
+
+void InstancesDlg::on_ui_instance_add_clicked()
+{
+ bool ok{};
+ QString configId = QInputDialog::getText(this, tr("QtDMM - new instance"), tr("Instance name:"), QLineEdit::Normal, "", &ok).trimmed();
+ if (!ok || configId.isEmpty() || configId == "default")
+ return;
+
+ if (m_instancesOnline.contains(configId) || m_instancesConfigured.contains(configId) )
+ {
+ QMessageBox::warning(this, APP_NAME,tr("Instance already exists."));
+ return;
+ }
+
+ QString exePath = QCoreApplication::applicationFilePath();
+
+ QStringList args;
+ if (configId != "default")
+ args << "--config-id" << configId;
+ if (!m_configPath.isEmpty())
+ args << "--config-dir" << m_configPath;
+
+ QProcess::startDetached(exePath, args);
+ Q_EMIT writeState("UPDATE_INSTANCES_"+QString::number(QDateTime::currentMSecsSinceEpoch()));
+}
+
+
+void InstancesDlg::on_ui_instance_del_clicked()
+{
+ if (m_onDelete && !ui_instance_del->isChecked())
+ {
+ for (int i = 0; i < ui_instancesList->count(); ++i)
+ {
+ QListWidgetItem *item = ui_instancesList->item(i);
+ if (!item)
+ continue;
+
+ auto btn = qobject_cast(ui_instancesList->itemWidget(item));
+ if (!btn || !btn->isChecked())
+ continue;
+
+ QString configId = btn->property("instanceId").toString();
+ m_settings->deleteConfig(configId);
+ }
+ Q_EMIT writeState("UPDATE_INSTANCES_"+QString::number(QDateTime::currentMSecsSinceEpoch()));
+ }
+
+ m_onDelete = ui_instance_del->isChecked();
+ updateInstancesListBox();
+}
+
+
diff --git a/src/instancesdlg.h b/src/instancesdlg.h
new file mode 100644
index 0000000..77274ce
--- /dev/null
+++ b/src/instancesdlg.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+#include "ui_uiinstancesdlg.h"
+#include "settings.h"
+
+class InstancesDlg : public QDialog, private Ui::UIInstancesDlg
+{
+ Q_OBJECT
+public:
+ InstancesDlg(Settings *settings, QString instance_id, QString config_path, QWidget *parent = Q_NULLPTR);
+
+ void setInstancesOnline(QStringList instances);
+ void updateInstancesListBox();
+
+protected:
+ QStringList m_instancesOnline;
+ QStringList m_instancesConfigured;
+ QString m_configPath;
+ QString m_instanceId;
+ Settings *m_settings;
+ bool m_onDelete;
+
+protected Q_SLOTS:
+ void onInstanceButtonClicked();
+ void on_ui_instance_add_clicked();
+ void on_ui_instance_del_clicked();
+
+Q_SIGNALS:
+ void raiseApplicationWindow(const QString &);
+ void writeState(const QString &);
+};
diff --git a/src/mainwid.cpp b/src/mainwid.cpp
index b68f8c0..d8d8736 100644
--- a/src/mainwid.cpp
+++ b/src/mainwid.cpp
@@ -31,8 +31,12 @@
#include "dmm.h"
#include "displaywid.h"
#include "tipdlg.h"
+#include "settings.h"
+#include "instancesdlg.h"
-MainWid::MainWid(QString session_id, QString config_path, QWidget *parent) : QFrame(parent),
+
+
+MainWid::MainWid(QString instance_id, QString config_path, QWidget *parent) : QFrame(parent),
m_display(0),
m_tipDlg(0)
{
@@ -42,14 +46,18 @@ MainWid::MainWid(QString session_id, QString config_path, QWidget *parent) : QF
m_dmm = new DMM(this);
m_external = new QProcess(this);
- m_configDlg = new ConfigDlg(session_id, config_path, this);
+ m_settings = new Settings(instance_id, config_path, this);
+ m_configDlg = new ConfigDlg(m_settings, this);
m_configDlg->hide();
-
m_configDlg->readPrinter(&m_printer);
m_printDlg = new qtdmm::PrintDlg(this);
m_printDlg->hide();
+ m_instancesDlg = new InstancesDlg(m_settings, instance_id, config_path,this);
+
+ connect(m_instancesDlg, SIGNAL(writeState(const QString &)), parent, SLOT(sendStateSLOT(const QString &)));
+ connect(this, SIGNAL(sendState(const QString &)), parent, SLOT(sendStateSLOT(const QString &)));
connect(m_dmm, SIGNAL(value(double, const QString &, const QString &, const QString &, const QString &, bool, bool, int)),
this, SLOT(valueSLOT(double, const QString &, const QString &, const QString &, const QString &, bool, bool, int)));
connect(m_dmm, SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &)));
@@ -74,10 +82,11 @@ MainWid::MainWid(QString session_id, QString config_path, QWidget *parent) : QF
connect(ui_graph, SIGNAL(thresholdChanged(DMMGraph::CursorMode, double)),
m_configDlg, SLOT(thresholdChangedSLOT(DMMGraph::CursorMode, double)));
- ui_graph->setSettings(m_configDlg->getSettings());
+ ui_graph->setSettings(m_settings);
//resetSLOT();
-
+ m_settings->save();
+ Q_EMIT sendState("UPDATE_INSTANCES_"+QString::number(QDateTime::currentMSecsSinceEpoch()));
startTimer(100);
if (m_configDlg->showTip())
@@ -539,3 +548,13 @@ void MainWid::setToolbarVisibility(bool disp, bool dmm, bool graph, bool file)
{
m_configDlg->setToolbarVisibility(disp, dmm, graph, file);
}
+
+void MainWid::instancesSLOT()
+{
+ m_instancesDlg->show();
+}
+
+void MainWid::instancesChangedSlot(QStringList& instances)
+{
+ m_instancesDlg->setInstancesOnline(instances);
+}
diff --git a/src/mainwid.h b/src/mainwid.h
index 0071018..d39986c 100644
--- a/src/mainwid.h
+++ b/src/mainwid.h
@@ -27,6 +27,7 @@
#include
#include "ui_uimainwid.h"
+
#include "printdlg.h"
class DMM;
@@ -34,12 +35,14 @@ class QProcess;
class ConfigDlg;
class DisplayWid;
class TipDlg;
+class Settings;
+class InstancesDlg;
class MainWid : public QFrame, private Ui::UIMainWid
{
Q_OBJECT
public:
- MainWid(QString session_id, QString config_path, QWidget *parent = Q_NULLPTR);
+ MainWid(QString instance_id, QString config_path, QWidget *parent = Q_NULLPTR);
bool closeWin();
QRect winRect() const;
bool saveWindowPosition() const;
@@ -59,6 +62,7 @@ class MainWid : public QFrame, private Ui::UIMainWid
void setConnect(bool);
void toolbarVisibility(bool, bool, bool, bool);
void connectDMM(bool);
+ void sendState(const QString&);
public Q_SLOTS:
void valueSLOT(double, const QString &, const QString &, const QString &, const QString &, bool, bool, int);
@@ -79,6 +83,8 @@ public Q_SLOTS:
void applySLOT();
void rejectSLOT();
void showTipsSLOT();
+ void instancesSLOT();
+ void instancesChangedSlot(QStringList&);
protected:
DMM *m_dmm;
@@ -92,6 +98,8 @@ public Q_SLOTS:
DisplayWid *m_display;
double m_dval;
TipDlg *m_tipDlg;
+ InstancesDlg *m_instancesDlg;
+ Settings *m_settings;
void readConfig();
QRect parentRect() const;
diff --git a/src/mainwin.cpp b/src/mainwin.cpp
index 9c0368c..87503e7 100644
--- a/src/mainwin.cpp
+++ b/src/mainwin.cpp
@@ -29,27 +29,29 @@
#include "mainwid.h"
#include "displaywid.h"
-
MainWin::MainWin(QCommandLineParser &parser, QWidget *parent)
: QMainWindow(parent)
, m_running(false)
, m_menu(Q_NULLPTR)
+ , m_localRecord(true)
{
setupUi(this);
setupIcons();
+ m_config_id = parser.value("config-id");
+
+ m_stateMgr = new SharedStateManager(m_config_id.isEmpty()?"default":m_config_id,this);
QWidget* spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
this->toolBarMenu->addWidget(spacer);
this->toolBarMenu->addAction(this->action_Menu);
- QString config_id = parser.value("config-id");
QString version = APP_VERSION;
int plusIndex = version.indexOf('+');
if (plusIndex != -1)
version = version.left(plusIndex);
- m_wid = new MainWid(config_id, parser.value("config-dir"), this);
+ m_wid = new MainWid(m_config_id, parser.value("config-dir"), this);
setCentralWidget(m_wid);
setConsoleLogging(parser.isSet("debug"));
@@ -59,7 +61,10 @@ MainWin::MainWin(QCommandLineParser &parser, QWidget *parent)
toolBarDisplay->addWidget(m_display);
m_wid->setDisplay(m_display);
- setWindowTitle(QString("%1 %2 %3").arg(APP_NAME).arg(version).arg(config_id));
+ if (m_config_id.isEmpty())
+ setWindowTitle(QString("%1 %2").arg(APP_NAME).arg(version));
+ else
+ setWindowTitle(QString("%1 %2 [%3]").arg(APP_NAME).arg(version).arg(m_config_id));
connect(m_wid, SIGNAL(running(bool)), this, SLOT(runningSLOT(bool)));
@@ -88,19 +93,51 @@ MainWin::MainWin(QCommandLineParser &parser, QWidget *parent)
m_wid->applySLOT();
-
if (!winRect.isEmpty())
{
if (m_wid->saveWindowPosition())
+ {
move(winRect.x(), winRect.y());
+ }
if (m_wid->saveWindowSize())
resize(winRect.width(), winRect.height());
else
resize(640, 480);
}
- QTimer::singleShot(1000, action_Connect, &QAction::trigger);
+
+ connect(m_stateMgr, &SharedStateManager::stateChanged, this, [=](const QString& state){
+ if (state == "RECORD")
+ {
+ QMetaObject::invokeMethod(m_wid, "startSLOT", Qt::DirectConnection);
+ m_localRecord = false;
+ }
+ else if (state == "STOP")
+ {
+ if (!m_localRecord)
+ action_Stop->trigger();
+ }
+ else if (state == "RAISE_"+(m_config_id.isEmpty()?"default":m_config_id))
+ {
+ bringMainWindowToFront();
+ m_stateMgr->writeState("IDLE");
+ }
+ });
+
+ connect(m_stateMgr, &SharedStateManager::instanceIdAlreadyInUse, this, [=](){
+ QMessageBox::critical(this, APP_NAME,tr("Another instance is running."));
+ qApp->quit();
+ });
+
+ if ( m_stateMgr->registerInstance() )
+ QTimer::singleShot(1000, action_Connect, &QAction::trigger);
}
+void MainWin::sendStateSLOT(const QString & state)
+{
+ m_stateMgr->writeState(state);
+}
+
+
void MainWin::setConsoleLogging(bool on)
{
m_wid->setConsoleLogging(on);
@@ -122,8 +159,9 @@ void MainWin::createActions()
connect(action_Connect, SIGNAL(triggered(bool)), m_wid, SLOT(connectSLOT(bool)));
connect(action_Connect, SIGNAL(triggered(bool)), this, SLOT(connectSLOT(bool)));
connect(action_Reset, SIGNAL(triggered()), m_wid, SLOT(resetSLOT()));
- connect(action_Start, SIGNAL(triggered()), m_wid, SLOT(startSLOT()));
+ connect(action_Start, SIGNAL(triggered()), this, SLOT(startSLOT()));
connect(action_Stop, SIGNAL(triggered()), m_wid, SLOT(stopSLOT()));
+ connect(action_Stop, SIGNAL(triggered()), this, SLOT(stopSLOT()));
connect(action_Clear, SIGNAL(triggered()), m_wid, SLOT(clearSLOT()));
connect(action_Print, SIGNAL(triggered()), m_wid, SLOT(printSLOT()));
connect(action_Import, SIGNAL(triggered()), m_wid, SLOT(importSLOT()));
@@ -135,6 +173,7 @@ void MainWin::createActions()
connect(action_Quit, SIGNAL(triggered()), m_wid, SLOT(quitSLOT()));
connect(action_Direct_help, SIGNAL(triggered()), m_wid, SLOT(helpSLOT()));
connect(action_Tip_of_the_day, SIGNAL(triggered()), m_wid, SLOT(showTipsSLOT()));
+ connect(action_Instances, SIGNAL(triggered()), m_wid, SLOT(instancesSLOT()));
connect(toolBarDisplay, SIGNAL(visibilityChanged(bool)), this, SLOT(setToolbarVisibilitySLOT()));
connect(toolBarMenu, SIGNAL(visibilityChanged(bool)), this, SLOT(setToolbarVisibilitySLOT()));
@@ -142,12 +181,56 @@ void MainWin::createActions()
connect(toolBarRecorder, SIGNAL(visibilityChanged(bool)), this, SLOT(setToolbarVisibilitySLOT()));
connect(toolBarDMM, SIGNAL(visibilityChanged(bool)), this, SLOT(setToolbarVisibilitySLOT()));
+ connect(m_stateMgr, SIGNAL(instancesChanged(QStringList&)), m_wid, SLOT(instancesChangedSlot(QStringList&)));
+
connect(new QShortcut(action_Configure->shortcut(), this), SIGNAL(activated()), action_Configure, SLOT(trigger()));
connect(new QShortcut(action_Direct_help->shortcut(), this), SIGNAL(activated()), action_Direct_help, SLOT(trigger()));
connect(new QShortcut(action_About->shortcut(), this), SIGNAL(activated()), action_About, SLOT(trigger()));
connect(new QShortcut(action_Quit->shortcut(), this), SIGNAL(activated()), action_Quit, SLOT(trigger()));
}
+void MainWin::startSLOT()
+{
+ if (m_stateMgr->instances().count()<=1)
+ {
+ QMetaObject::invokeMethod(m_wid, "startSLOT", Qt::DirectConnection);
+ m_localRecord = true;
+ }
+ else
+ {
+ QMessageBox question(
+ QMessageBox::Question,
+ tr("Record DMM data"),
+ tr("Multiple instances of QtDMM have been detected.\n"
+ "Please choose which instance should record."),
+ QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
+ question.button(QMessageBox::Yes)->setText(tr("This instance"));
+ question.button(QMessageBox::No)->setText(tr("All instances"));
+ question.setEscapeButton(QMessageBox::Cancel);
+
+ switch (question.exec())
+ {
+ case QMessageBox::Yes:
+ QMetaObject::invokeMethod(m_wid, "startSLOT", Qt::DirectConnection);
+ m_localRecord = true;
+ return;
+ case QMessageBox::No:
+ m_stateMgr->writeState("RECORD");
+ m_localRecord = false;
+ return;
+ }
+ }
+}
+
+void MainWin::stopSLOT()
+{
+ qInfo() << "stop" << m_localRecord;
+ if (! m_localRecord)
+ m_stateMgr->writeState("STOP");
+ m_localRecord = false;
+
+}
+
void MainWin::runningSLOT(bool on)
{
m_running = on;
@@ -251,3 +334,25 @@ void MainWin::setupIcons()
this->action_Connect->setIcon(checked ? iconConnectOn : iconConnectOff);
});
}
+
+void MainWin::bringMainWindowToFront()
+{
+ QWidget *mainWin = nullptr;
+ const auto topWidgets = QApplication::topLevelWidgets();
+
+ for (QWidget *w : topWidgets)
+ {
+ if (w->inherits("MainWin"))
+ {
+ mainWin = w;
+ break;
+ }
+ }
+
+ if (!mainWin)
+ return;
+
+ mainWin->showNormal();
+ mainWin->raise();
+ mainWin->activateWindow();
+}
diff --git a/src/mainwin.h b/src/mainwin.h
index 8f8fd7e..f5197b0 100644
--- a/src/mainwin.h
+++ b/src/mainwin.h
@@ -28,6 +28,7 @@
#include
#include "ui_uimainwin.h"
+#include "sharedstatemanager.h"
class MainWid;
class DisplayWid;
@@ -43,6 +44,9 @@ class MainWin : public QMainWindow, private Ui::UIMainWin
protected Q_SLOTS:
void runningSLOT(bool);
void connectSLOT(bool);
+ void startSLOT();
+ void stopSLOT();
+ void sendStateSLOT(const QString &);
void on_action_About_triggered();
void on_action_Menu_triggered();
void setConnectSLOT(bool);
@@ -57,10 +61,14 @@ protected Q_SLOTS:
QLabel *m_error;
QLabel *m_info;
QMenu *m_menu;
+ SharedStateManager* m_stateMgr;
+ QString m_config_id;
+ bool m_localRecord;
void setupIcons();
void createToolBars();
void createActions();
void closeEvent(QCloseEvent *)Q_DECL_OVERRIDE;
+ void bringMainWindowToFront();
};
diff --git a/src/resources.qrc b/src/resources.qrc
index a61b464..c7692e6 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -22,6 +22,7 @@
../assets/icons/W.xpm
../assets/icons/W_small.xpm
../assets/icons/ac.xpm
+ ../assets/icons/add.xpm
../assets/icons/all_digits.xpm
../assets/icons/auto.xpm
../assets/icons/bar.xpm
@@ -61,6 +62,7 @@
../assets/icons/icon.xpm
../assets/icons/icon_gray.xpm
../assets/icons/import.xpm
+ ../assets/icons/instances.xpm
../assets/icons/integration.xpm
../assets/icons/integration_old.xpm
../assets/icons/k.xpm
@@ -88,6 +90,7 @@
../assets/icons/print.xpm
../assets/icons/quit.xpm
../assets/icons/recorder.xpm
+ ../assets/icons/remove.xpm
../assets/icons/reset.xpm
../assets/icons/scale.xpm
../assets/icons/sixty_bar.xpm
diff --git a/src/settings.cpp b/src/settings.cpp
index 0a8f283..c1723ac 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -34,25 +34,73 @@ Settings::~Settings()
delete m_tmpConfig;
}
-Settings::Settings(QObject *parent, const QString &session_id, const QString &config_path) : Settings(parent)
+Settings::Settings(const QString &instance_id, const QString &config_path, QObject *parent)
+ : Settings(parent)
{
- if (!session_id.isEmpty())
+ QFileInfo fileInfo(m_qsettings->fileName());
+ m_configPath = fileInfo.absolutePath();
+ m_configBaseFileName = fileInfo.baseName();
+ m_configFileNameSuffix = fileInfo.suffix();
+ m_instanceId = instance_id.isEmpty() ? "default" : instance_id;
+
+ if (!instance_id.isEmpty() || !config_path.isEmpty())
{
- QSettings qsettings(this);
- QFileInfo fileInfo(qsettings.fileName());
- QString fileDir = config_path.isEmpty() ? fileInfo.absolutePath() : config_path;
+ m_configPath = config_path.isEmpty() ? fileInfo.absolutePath() : config_path;
+ m_configBaseFileName = fileInfo.baseName();
+ m_configFileNameSuffix = fileInfo.suffix();
+
+ QString instance_fileName = m_configBaseFileName+(instance_id.isEmpty() ? "" : "_"+instance_id)+"."+m_configFileNameSuffix;
+ instance_fileName.prepend(m_configPath+"/");
- QString session_fileName = fileDir+"/"+fileInfo.baseName()+"_"+session_id+"."+fileInfo.suffix();
delete m_qsettings;
- m_qsettings = new QSettings(session_fileName, QSettings::IniFormat, this);
+ m_qsettings = new QSettings(instance_fileName, QSettings::IniFormat, this);
QFile file(m_qsettings->fileName());
m_fileExists = file.exists();
m_filename = m_qsettings->fileName();
m_fileConverted = false;
- qInfo() << "config file: " << m_qsettings->fileName();
+ //qInfo() << "config file: " << m_qsettings->fileName();
}
}
+QStringList Settings::getConfigInstances()
+{
+ QDir dir(m_configPath);
+ if (!dir.exists())
+ return {};
+ //qInfo() << m_configPath << m_configBaseFileName << m_configFileNameSuffix;
+ QString pattern = QString("%1*%2")
+ .arg(m_configBaseFileName)
+ .arg(m_configFileNameSuffix.isEmpty() ? "" : "." + m_configFileNameSuffix);
+
+ QStringList result;
+ result << m_instanceId;
+ for (const QString &file : dir.entryList({ pattern }, QDir::Files, QDir::Name))
+ {
+ QString base = QFileInfo(file).completeBaseName();
+
+ if (base.startsWith(m_configBaseFileName))
+ base = base.mid(m_configBaseFileName.length());
+
+ if (base.startsWith("_"))
+ base.remove(0, 1);
+
+ result << (base.isEmpty() ? "default" : base);
+ }
+
+ result.removeDuplicates();
+ result.sort(Qt::CaseInsensitive);
+ if (result.contains("default"))
+ {
+ result.removeAll("default");
+ result.prepend("default");
+ }
+
+ //for (auto item : result)
+ // qInfo() << item;
+
+ return result;
+}
+
int Settings::getInt(const QString &name, const int &def) const
{
return m_qsettings->value(name, def).toInt();
@@ -107,3 +155,14 @@ void Settings::clear()
{
m_tmpConfig->clear();
}
+
+
+void Settings::deleteConfig(QString instance_id)
+{
+ if(instance_id.isEmpty())
+ return;
+
+ QFile configFile(m_configPath+"/"+m_configBaseFileName+"_"+instance_id+"."+m_configFileNameSuffix);
+ configFile.remove();
+}
+
diff --git a/src/settings.h b/src/settings.h
index 9c5aea4..651f2cc 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -25,14 +25,16 @@ class Settings : public QObject
Q_OBJECT
public:
explicit Settings(QObject *parent = Q_NULLPTR);
- explicit Settings(QObject *parent, const QString &session_id, const QString &config_path);
+ explicit Settings(const QString &instance_id, const QString &config_path, QObject *parent = Q_NULLPTR);
~Settings();
const bool &fileExists() const { return m_fileExists; }
const bool &fileConverted() const { return m_fileConverted; }
+ const QString &fileName() const { return m_filename; }
- const QString &fileName()const { return m_filename; }
+ void deleteConfig(QString instance_id);
+ QStringList getConfigInstances();
void save();
void clear();
@@ -53,5 +55,9 @@ class Settings : public QObject
bool m_fileConverted;
QString m_filename;
QSettings *m_qsettings;
+ QString m_configPath;
+ QString m_configBaseFileName;
+ QString m_configFileNameSuffix;
+ QString m_instanceId;
QHash *m_tmpConfig;
};
diff --git a/src/sharedstatemanager.cpp b/src/sharedstatemanager.cpp
new file mode 100644
index 0000000..d768faa
--- /dev/null
+++ b/src/sharedstatemanager.cpp
@@ -0,0 +1,175 @@
+#include "sharedstatemanager.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+SharedStateManager::SharedStateManager(const QString &instanceId, QObject *parent)
+ : QObject(parent),
+ m_memory("qtdmm_ipc_memory"),
+ m_lastState(),
+ m_instanceId(instanceId),
+ m_registered(false),
+ m_emit_inUse(false)
+{
+ m_timer.setInterval(250);
+ connect(&m_timer, &QTimer::timeout, this, &SharedStateManager::checkForChanges);
+ m_timer.start();
+}
+
+SharedStateManager::~SharedStateManager()
+{
+ unregisterInstance();
+}
+
+QJsonObject SharedStateManager::initialJson()
+{
+ QJsonObject data;
+ data["instances"] = QJsonArray();
+ data["state"] = "";
+ return data;
+}
+
+
+QJsonObject SharedStateManager::readJsonData()
+{
+ if (!m_memory.isAttached() && !m_memory.attach())
+ return initialJson();
+
+ m_memory.lock();
+
+ QByteArray rawData(static_cast(m_memory.constData()), m_memory.size());
+ int end = rawData.indexOf('\0');
+ if (end != -1)
+ rawData.truncate(end);
+
+ m_memory.unlock();
+ QJsonParseError parseError;
+ QJsonDocument doc = QJsonDocument::fromJson(rawData, &parseError);
+ //qInfo() << QJsonDocument(doc).toJson(QJsonDocument::Compact);
+
+ if (parseError.error != QJsonParseError::NoError || !doc.isObject())
+ {
+ qInfo("parse error");
+ return initialJson();
+ }
+
+ return doc.object();
+}
+
+bool SharedStateManager::writeJsonData(const QJsonObject &obj)
+{
+ if (!m_memory.isAttached() && !m_memory.attach())
+ {
+ if (!m_memory.create(8192))
+ return false;
+ }
+ QJsonDocument doc(obj);
+ QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
+
+ if (jsonData.size() > m_memory.size())
+ {
+ m_memory.detach();
+ if (!m_memory.create(jsonData.size() + 1024))
+ return false;
+ }
+
+ m_memory.lock();
+ char *to = static_cast(m_memory.data());
+ const char* from = jsonData.constData();
+ memset(to, 0, m_memory.size());
+ memcpy(to, from, jsonData.size());
+
+ m_memory.unlock();
+
+ return true;
+}
+
+bool SharedStateManager::registerInstance()
+{
+ if (!m_registered)
+ {
+ QJsonObject data = readJsonData();
+ QJsonArray instances = data["instances"].toArray();
+
+ for (const QJsonValue &val : instances)
+ {
+ if (val.toString() == m_instanceId)
+ {
+ qWarning() << m_instanceId << "id exists";
+ m_emit_inUse = true;
+ return false;
+ }
+ }
+
+ instances.append(m_instanceId);
+ data["instances"] = instances;
+
+ m_registered = writeJsonData(data);
+ if (!m_registered)
+ qInfo() << "reg failed";
+ }
+
+ return m_registered;
+}
+
+void SharedStateManager::checkForChanges()
+{
+ if (m_registered)
+ {
+ QJsonObject data = readJsonData();
+
+ QString currentState = data["state"].toString();
+ QStringList instances;
+ for (const QVariant &v : data["instances"].toArray().toVariantList())
+ instances << v.toString();
+
+ if (instances.count() != m_instances.count())
+ {
+ m_instances = instances;
+ Q_EMIT instancesChanged(m_instances);
+ }
+
+ if (currentState != m_lastState)
+ {
+ //qInfo() << currentState << QJsonDocument(data).toJson(QJsonDocument::Compact);
+ m_lastState = currentState;
+ if (currentState.startsWith("UPDATE_INSTANCES"))
+ Q_EMIT instancesChanged(m_instances);
+ Q_EMIT stateChanged(currentState);
+ }
+ }
+ if (m_emit_inUse)
+ Q_EMIT instanceIdAlreadyInUse();
+
+}
+
+bool SharedStateManager::writeState(const QString &newState)
+{
+ QJsonObject data = readJsonData();
+ data["state"] = newState;
+ return writeJsonData(data);
+}
+
+void SharedStateManager::unregisterInstance()
+{
+ if (m_registered)
+ {
+ QJsonObject data = readJsonData();
+ QJsonArray instances = data["instances"].toArray();
+
+ QJsonArray updated;
+ for (const QJsonValue &val : instances)
+ {
+ if (val.toString() != m_instanceId)
+ updated.append(val);
+ }
+
+ data["instances"] = updated;
+ writeJsonData(data);
+ }
+}
+
diff --git a/src/sharedstatemanager.h b/src/sharedstatemanager.h
new file mode 100644
index 0000000..326cbf1
--- /dev/null
+++ b/src/sharedstatemanager.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class SharedStateManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SharedStateManager(const QString &instanceId, QObject *parent = nullptr);
+ ~SharedStateManager();
+
+ bool registerInstance();
+ void unregisterInstance();
+
+ void checkForChanges();
+ QStringList instances() { return m_instances; };
+
+signals:
+ void stateChanged(const QString &newState);
+ void instanceIdAlreadyInUse();
+ void instancesChanged(QStringList &instances);
+
+public Q_SLOTS:
+ bool writeState(const QString &newState);
+
+private:
+ QJsonObject readJsonData();
+ bool writeJsonData(const QJsonObject &obj);
+ QJsonObject initialJson();
+
+ QSharedMemory m_memory;
+ QTimer m_timer;
+ QString m_lastState;
+ QString m_instanceId;
+ bool m_registered;
+ bool m_emit_inUse;
+ QStringList m_instances;
+};
diff --git a/src/ui/uidmmprefs.ui b/src/ui/uidmmprefs.ui
index cf617c7..8115210 100644
--- a/src/ui/uidmmprefs.ui
+++ b/src/ui/uidmmprefs.ui
@@ -40,9 +40,18 @@
Here you may select your DMM model. If your DMM is not in the list, try to find working settings and mail them to me (<font color=blue><u>qtdmm@mtoussaint.de</u></font>). So I can add them to the list in the next release.
+
+ true
+
+
+ QComboBox::NoInsert
+
+
+ QComboBox::AdjustToContents
+
-
diff --git a/src/ui/uiinstancesdlg.ui b/src/ui/uiinstancesdlg.ui
new file mode 100644
index 0000000..cb8c6a8
--- /dev/null
+++ b/src/ui/uiinstancesdlg.ui
@@ -0,0 +1,147 @@
+
+
+ UIInstancesDlg
+
+
+
+ 0
+ 0
+ 390
+ 397
+
+
+
+ Instance Manager
+
+
+
-
+
+
+ 12
+
+
+ 12
+
+
-
+
+
+ Choose instance to open, or add/remove instances
+
+
+
+
+
+ -
+
+
+ false
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 0
+
+
+
+ -
+
+
-
+
+
+ add new instance
+
+
+ +
+
+
+
+ :/Symbols/add.xpm:/Symbols/add.xpm
+
+
+
+ -
+
+
+ remove instances
+
+
+ -
+
+
+
+ :/Symbols/remove.xpm:/Symbols/remove.xpm
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+
+
+
+
+
+ ui_instances_buttonBox
+ accepted()
+ UIInstancesDlg
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 268
+
+
+
+
+ ui_instances_buttonBox
+ rejected()
+ UIInstancesDlg
+ reject()
+
+
+ 389
+ 254
+
+
+ 286
+ 268
+
+
+
+
+
diff --git a/src/ui/uimainwin.ui b/src/ui/uimainwin.ui
index 149f25d..6a9892f 100644
--- a/src/ui/uimainwin.ui
+++ b/src/ui/uimainwin.ui
@@ -26,6 +26,9 @@
DMM
+
+
+
false
@@ -41,6 +44,8 @@
false
+
+
@@ -48,6 +53,9 @@
Recorder
+
+
+
false
@@ -148,6 +156,9 @@
&Export
+
+ Export graph
+
<html><head/><body><p><span style=" font-weight:600;">Export recorder graph</span></p><p>Here you can export the recorded data as tab separated list. Each line contains the following values (separated by a tab character): date (dd.mm.yyyy) time (hh:mm:ss) value (float) unit.</p></body></html>
@@ -166,6 +177,9 @@
&Import
+
+ Import graph
+
<html><head/><body><p><span style=" font-weight:600;">Import data into recorder</span></p><p>Here you can import previously exported data files. QtDMM tries to do an educated guess if the file format is correct and rejects import of files which to not match.</p></body></html>
@@ -184,6 +198,9 @@
&Print
+
+ Print graph
+
<html><head/><body><p><span style=" font-weight:600;">Print recorder graph</span></p><p>A dialog will open where you can define a title and a comment for your printout. The printer itself can also be configured here. To be able to print you need at least one working postscript printer configured in your system. Printing into a file is also supported.</p></body></html>
@@ -241,6 +258,9 @@
&Connect
+
+ Connect/Disconnect DMM
+
<html><head/><body><p><span style=" font-weight:600;">Connect to the Multimeter</span></p><p>This will establish the serial connection to the dmm. If not connected the serial port is free and can be used by other software.</p></body></html>
@@ -259,6 +279,9 @@
&Reset
+
+ reset min/max
+
<html><head/><body><p><span style=" font-weight:600;">Reset min/max values</span></p><p>The min/max values in the display will be reset. You can activate this option at any time. </p></body></html>
@@ -294,6 +317,9 @@
&Start
+
+ Start recording
+
<html><head/><body><p><span style=" font-weight:600;">Start the recorder</span></p><p>If you are in manual mode this will start the recorder. Press F2 to set the recorder options.</p></body></html>
@@ -315,6 +341,9 @@
S&top
+
+ Stop recording
+
<html><head/><body><p><span style=" font-weight:600;">Stop the recorder</span></p><p>The recorder will be stopped. This is independent from the start mode of the recorder. </p></body></html>
@@ -333,6 +362,9 @@
&Clear
+
+ Clear graph
+
<html><head/><body><p><span style=" font-weight:600;">Clear the recorder graph</span></p><p>If the recorder is already started it will clear the graph and continue recording.</p></body></html>
@@ -412,7 +444,7 @@
-
+
:/Symbols/menu.xpm:/Symbols/menu.xpm
@@ -422,7 +454,22 @@
Menu
- Ctrl+M
+ Ctrl+M
+
+
+
+
+
+ :/Symbols/instances.xpm:/Symbols/instances.xpm
+
+
+ Instances
+
+
+ Instance Manager
+
+
+ Ctrl+N
diff --git a/tests/data/do3122.json_todo b/tests/data/do3122.json_todo
deleted file mode 100644
index 9cacdcb..0000000
--- a/tests/data/do3122.json_todo
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "DO3122Continuous",
- "tests": [
- {
- "hex": "AA 55 52 24 00 xx ",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/dtm0660.json_todo b/tests/data/dtm0660.json_todo
deleted file mode 100644
index 1d5ed5a..0000000
--- a/tests/data/dtm0660.json_todo
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "DTM0660",
- "tests": [
- {
- "hex": "17 27 3D 4F 5D 67 7D 87 9D A0 B0 C0 D4 E0",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/es51922.json b/tests/data/es51922.json
deleted file mode 100644
index e1eeccb..0000000
--- a/tests/data/es51922.json
+++ /dev/null
@@ -1,101 +0,0 @@
-{
- "decoder": "CyrustekES51922",
- "tests": [
- {
- "hex": "30 30 30 30 30 30 3B 30 30 30 3A 30 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 30 3B 30 30 30 38 30 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "MANU",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 30 3B 32 30 30 3A 30 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 30 3D 30 30 30 3A 30 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "000.00",
- "unit": "uA",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 30 30 30 30 30 34 32 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "00.000",
- "unit": "A",
- "range": "MANU",
- "special": "AC",
- "hold": true,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 30 3B 30 30 30 3B 30 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "Hz",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 37 3F 30 30 30 36 30 0D 0A",
- "expected": {
- "dval": 0.000007,
- "val": "00.007",
- "unit": "mA",
- "range": "AUTO",
- "special": "AC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "34 30 31 33 34 34 3B 34 30 30 38 30 0D 0A",
- "expected": {
- "dval": -0.01344,
- "val": "-013.44",
- "unit": "mV",
- "range": "MANU",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/es51962.json b/tests/data/es51962.json
deleted file mode 100644
index f74eba5..0000000
--- a/tests/data/es51962.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "decoder": "CyrustekES51962",
- "tests": [
- {
- "hex": "30 30 30 35 38 3B 30 30 3A 0D 0A",
- "expected": {
- "dval": 0.0058,
- "val": "005.8",
- "unit": "mV",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 32 31 3B 34 30 38 0D 0A",
- "expected": {
- "dval": -0.0021,
- "val": "-002.1",
- "unit": "mV",
- "range": "MANU",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 31 34 30 33 30 30 30 0D 0A",
- "expected": {
- "dval": 14.0,
- "val": "014.0",
- "unit": "Ohm",
- "range": "MANU",
- "special": "",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/es51986.json b/tests/data/es51986.json
deleted file mode 100644
index 3c82703..0000000
--- a/tests/data/es51986.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "decoder": "CyrustekES51986",
- "tests": [
- {
- "hex": "30 30 30 30 30 3B 38 30 3A 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "30 30 30 30 30 3B 3C 30 38 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "-0.000",
- "unit": "V",
- "range": "MANU",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/graph/legacy.txt b/tests/data/graph/legacy.txt
new file mode 100644
index 0000000..350a0be
--- /dev/null
+++ b/tests/data/graph/legacy.txt
@@ -0,0 +1,42 @@
+09.07.2025 13:16:38 0.001200 V
+09.07.2025 13:16:39 -0.000080 V
+09.07.2025 13:16:40 -0.003340 V
+09.07.2025 13:16:41 -0.001470 V
+09.07.2025 13:16:42 -0.002050 V
+09.07.2025 13:16:43 -0.001940 V
+09.07.2025 13:16:44 -0.005850 V
+09.07.2025 13:16:45 -0.371410 V
+09.07.2025 13:16:46 -1.049560 V
+09.07.2025 13:16:47 -1.314830 V
+09.07.2025 13:16:48 -1.314900 V
+09.07.2025 13:16:49 -1.314900 V
+09.07.2025 13:16:50 -1.314900 V
+09.07.2025 13:16:51 -1.314930 V
+09.07.2025 13:16:52 -1.062350 V
+09.07.2025 13:16:53 -0.792770 V
+09.07.2025 13:16:54 -1.079020 V
+09.07.2025 13:16:55 -0.987650 V
+09.07.2025 13:16:56 -0.044250 V
+09.07.2025 13:16:57 0.002180 V
+09.07.2025 13:16:58 -0.002060 V
+09.07.2025 13:16:59 -0.000850 V
+09.07.2025 13:17:00 -0.000080 V
+09.07.2025 13:17:01 0.411220 V
+09.07.2025 13:17:02 1.315010 V
+09.07.2025 13:17:03 1.315130 V
+09.07.2025 13:17:04 1.315170 V
+09.07.2025 13:17:05 1.315130 V
+09.07.2025 13:17:06 1.315170 V
+09.07.2025 13:17:07 1.315100 V
+09.07.2025 13:17:08 0.920540 V
+09.07.2025 13:17:09 -0.000190 V
+09.07.2025 13:17:10 -0.000640 V
+09.07.2025 13:17:11 -0.000380 V
+09.07.2025 13:17:12 -0.020760 V
+09.07.2025 13:17:13 -1.055930 V
+09.07.2025 13:17:14 -0.911510 V
+09.07.2025 13:17:15 0.005540 V
+09.07.2025 13:17:16 0.000000 V
+09.07.2025 13:17:17 0.000000 V
+09.07.2025 13:17:18 -0.000030 V
+09.07.2025 13:17:19 -0.000160 V
diff --git a/tests/data/graph/legacy_nan.txt b/tests/data/graph/legacy_nan.txt
new file mode 100644
index 0000000..23995b6
--- /dev/null
+++ b/tests/data/graph/legacy_nan.txt
@@ -0,0 +1,47 @@
+15.07.2025 10:14:53:775 nan Ohm
+15.07.2025 10:14:54:275 nan Ohm
+15.07.2025 10:14:54:775 nan Ohm
+15.07.2025 10:14:55:275 nan Ohm
+15.07.2025 10:14:55:775 nan Ohm
+15.07.2025 10:14:56:275 nan Ohm
+15.07.2025 10:14:56:775 0.011452 Ohm
+15.07.2025 10:14:57:275 0.037174 Ohm
+15.07.2025 10:14:57:775 0.075652 Ohm
+15.07.2025 10:14:58:275 0.032896 Ohm
+15.07.2025 10:14:58:775 -0.004520 Ohm
+15.07.2025 10:14:59:275 0.044218 Ohm
+15.07.2025 10:14:59:775 -0.030110 Ohm
+15.07.2025 10:15:00:275 -0.064386 Ohm
+15.07.2025 10:15:00:775 -0.007114 Ohm
+15.07.2025 10:15:01:275 -0.020008 Ohm
+15.07.2025 10:15:01:775 -0.022212 Ohm
+15.07.2025 10:15:02:275 -0.022284 Ohm
+15.07.2025 10:15:02:775 -0.021094 Ohm
+15.07.2025 10:15:03:275 -0.019696 Ohm
+15.07.2025 10:15:03:775 -0.020166 Ohm
+15.07.2025 10:15:04:275 -0.020674 Ohm
+15.07.2025 10:15:04:775 nan Ohm
+15.07.2025 10:15:05:275 nan Ohm
+15.07.2025 10:15:05:775 nan Ohm
+15.07.2025 10:15:06:275 nan Ohm
+15.07.2025 10:15:06:775 nan Ohm
+15.07.2025 10:15:07:275 nan Ohm
+15.07.2025 10:15:07:775 nan Ohm
+15.07.2025 10:15:08:275 nan Ohm
+15.07.2025 10:15:08:775 nan Ohm
+15.07.2025 10:15:09:275 nan Ohm
+15.07.2025 10:15:09:775 nan Ohm
+15.07.2025 10:15:10:275 2117800.000000 Ohm
+15.07.2025 10:15:10:775 2543000.000000 Ohm
+15.07.2025 10:15:11:275 2460800.000000 Ohm
+15.07.2025 10:15:11:775 2610000.000000 Ohm
+15.07.2025 10:15:12:275 2716800.000000 Ohm
+15.07.2025 10:15:12:775 2777600.000000 Ohm
+15.07.2025 10:15:13:275 2730800.000000 Ohm
+15.07.2025 10:15:13:775 2675000.000000 Ohm
+15.07.2025 10:15:14:275 2811200.000000 Ohm
+15.07.2025 10:15:14:775 2890800.000000 Ohm
+15.07.2025 10:15:15:275 2966800.000000 Ohm
+15.07.2025 10:15:15:775 5567600.000000 Ohm
+15.07.2025 10:15:16:275 31088200.000000 Ohm
+15.07.2025 10:15:16:775 63226000.000000 Ohm
diff --git a/tests/data/graph/new_dpoint.csv b/tests/data/graph/new_dpoint.csv
new file mode 100644
index 0000000..84480a6
--- /dev/null
+++ b/tests/data/graph/new_dpoint.csv
@@ -0,0 +1,43 @@
+12.07.2025 23:38:02:997 0.007020 V
+12.07.2025 23:38:03:497 0.002960 V
+12.07.2025 23:38:03:997 0.003720 V
+12.07.2025 23:38:04:497 0.015560 V
+12.07.2025 23:38:04:997 -0.005920 V
+12.07.2025 23:38:05:497 -0.008000 V
+12.07.2025 23:38:05:997 0.003000 V
+12.07.2025 23:38:06:497 0.008060 V
+12.07.2025 23:38:06:997 0.003660 V
+12.07.2025 23:38:07:497 0.004400 V
+12.07.2025 23:38:07:997 -0.003740 V
+12.07.2025 23:38:08:497 0.006900 V
+12.07.2025 23:38:08:997 0.004340 V
+12.07.2025 23:38:09:497 0.010820 V
+12.07.2025 23:38:09:997 0.012800 V
+12.07.2025 23:38:10:497 0.001280 V
+12.07.2025 23:38:10:997 0.002960 V
+12.07.2025 23:38:11:497 0.027380 V
+12.07.2025 23:38:11:997 0.034560 V
+12.07.2025 23:38:12:497 0.027940 V
+12.07.2025 23:38:12:997 0.026200 V
+12.07.2025 23:38:13:497 0.027480 V
+12.07.2025 23:38:13:997 0.019960 V
+12.07.2025 23:38:14:497 0.010080 V
+12.07.2025 23:38:14:997 0.015940 V
+12.07.2025 23:38:15:497 0.027260 V
+12.07.2025 23:38:15:997 0.031040 V
+12.07.2025 23:38:16:497 0.032680 V
+12.07.2025 23:38:16:997 0.032040 V
+12.07.2025 23:38:17:497 0.008040 V
+12.07.2025 23:38:17:997 -0.016300 V
+12.07.2025 23:38:18:497 0.048620 V
+12.07.2025 23:38:18:997 0.016080 V
+12.07.2025 23:38:19:497 -0.000800 V
+12.07.2025 23:38:19:997 0.040260 V
+12.07.2025 23:38:20:497 0.005200 V
+12.07.2025 23:38:20:997 -0.002120 V
+12.07.2025 23:38:21:497 0.009780 V
+12.07.2025 23:38:21:997 0.001220 V
+12.07.2025 23:38:22:497 -0.001840 V
+12.07.2025 23:38:22:997 -0.002940 V
+12.07.2025 23:38:23:497 0.002880 V
+12.07.2025 23:38:23:997 0.000880 V
diff --git a/tests/data/graph/new_komma_ms.csv b/tests/data/graph/new_komma_ms.csv
new file mode 100644
index 0000000..f13176d
--- /dev/null
+++ b/tests/data/graph/new_komma_ms.csv
@@ -0,0 +1,28 @@
+timestamp;time (ms);value;unit
+2025-08-11T05:41:54,551;0;0.000000;V
+2025-08-11T05:41:54,751;0.2;0.000000;V
+2025-08-11T05:41:54,951;0.4;0.000000;V
+2025-08-11T05:41:55,151;0.6;0.000000;V
+2025-08-11T05:41:55,351;0.8;0.000000;V
+2025-08-11T05:41:55,551;1;0.000000;V
+2025-08-11T05:41:55,751;1.2;0.000000;V
+2025-08-11T05:41:55,951;1.4;-0.003600;V
+2025-08-11T05:41:56,151;1.6;-0.003600;V
+2025-08-11T05:41:56,351;1.8;-0.001900;V
+2025-08-11T05:41:56,551;2;-0.000200;V
+2025-08-11T05:41:56,751;2.2;-0.000200;V
+2025-08-11T05:41:56,951;2.4;-0.000400;V
+2025-08-11T05:41:57,151;2.6;-0.000400;V
+2025-08-11T05:41:57,351;2.8;-0.000400;V
+2025-08-11T05:41:57,551;3;-0.000400;V
+2025-08-11T05:41:57,751;3.2;-0.000400;V
+2025-08-11T05:41:57,951;3.4;0.000000;V
+2025-08-11T05:41:58,151;3.6;0.000000;V
+2025-08-11T05:41:58,351;3.8;0.000000;V
+2025-08-11T05:41:58,551;4;0.000000;V
+2025-08-11T05:41:58,751;4.2;0.000000;V
+2025-08-11T05:41:58,951;4.4;0.000000;V
+2025-08-11T05:41:59,151;4.6;0.000000;V
+2025-08-11T05:41:59,351;4.8;0.000000;V
+2025-08-11T05:41:59,551;5;0.000000;V
+2025-08-11T05:41:59,751;5.2;0.000000;V
diff --git a/tests/data/graph/new_larger.csv b/tests/data/graph/new_larger.csv
new file mode 100644
index 0000000..9907ae2
--- /dev/null
+++ b/tests/data/graph/new_larger.csv
@@ -0,0 +1,272 @@
+timestamp;time (s);value;unit
+2025-08-11T10:25:22,289;0;0.032700;V
+2025-08-11T10:25:22,489;0.2;0.036000;V
+2025-08-11T10:25:22,689;0.4;0.036000;V
+2025-08-11T10:25:22,889;0.6;0.032400;V
+2025-08-11T10:25:23,089;0.8;0.028800;V
+2025-08-11T10:25:23,289;1;0.028800;V
+2025-08-11T10:25:23,489;1.2;0.024700;V
+2025-08-11T10:25:23,689;1.4;0.024700;V
+2025-08-11T10:25:23,889;1.6;0.027950;V
+2025-08-11T10:25:24,089;1.8;0.031200;V
+2025-08-11T10:25:24,289;2;0.031200;V
+2025-08-11T10:25:24,489;2.2;0.032100;V
+2025-08-11T10:25:24,689;2.4;0.032100;V
+2025-08-11T10:25:24,889;2.6;0.034250;V
+2025-08-11T10:25:25,089;2.8;0.036400;V
+2025-08-11T10:25:25,289;3;0.036400;V
+2025-08-11T10:25:25,489;3.2;0.033600;V
+2025-08-11T10:25:25,689;3.4;0.033600;V
+2025-08-11T10:25:25,889;3.6;0.045300;V
+2025-08-11T10:25:26,089;3.8;0.057000;V
+2025-08-11T10:25:26,289;4;0.057000;V
+2025-08-11T10:25:26,489;4.2;0.053900;V
+2025-08-11T10:25:26,689;4.4;0.053900;V
+2025-08-11T10:25:26,889;4.6;0.049350;V
+2025-08-11T10:25:27,089;4.8;0.044800;V
+2025-08-11T10:25:27,289;5;0.044800;V
+2025-08-11T10:25:27,489;5.2;0.043000;V
+2025-08-11T10:25:27,689;5.4;0.043000;V
+2025-08-11T10:25:27,889;5.6;0.038850;V
+2025-08-11T10:25:28,089;5.8;0.034700;V
+2025-08-11T10:25:28,289;6;0.034700;V
+2025-08-11T10:25:28,489;6.2;0.029500;V
+2025-08-11T10:25:28,689;6.4;0.029500;V
+2025-08-11T10:25:28,889;6.6;0.026150;V
+2025-08-11T10:25:29,089;6.8;0.022800;V
+2025-08-11T10:25:29,289;7;0.022800;V
+2025-08-11T10:25:29,489;7.2;0.025200;V
+2025-08-11T10:25:29,689;7.4;0.025200;V
+2025-08-11T10:25:29,889;7.6;0.024200;V
+2025-08-11T10:25:30,089;7.8;0.023200;V
+2025-08-11T10:25:30,289;8;0.023200;V
+2025-08-11T10:25:30,489;8.2;0.035500;V
+2025-08-11T10:25:30,689;8.4;0.035500;V
+2025-08-11T10:25:30,889;8.6;0.036850;V
+2025-08-11T10:25:31,089;8.8;0.038200;V
+2025-08-11T10:25:31,289;9;0.038200;V
+2025-08-11T10:25:31,489;9.2;0.036000;V
+2025-08-11T10:25:31,689;9.4;0.036000;V
+2025-08-11T10:25:31,889;9.6;0.036000;V
+2025-08-11T10:25:32,089;9.8;0.036000;V
+2025-08-11T10:25:32,289;10;0.036000;V
+2025-08-11T10:25:32,489;10.2;0.032800;V
+2025-08-11T10:25:32,689;10.4;0.032800;V
+2025-08-11T10:25:32,889;10.6;0.034050;V
+2025-08-11T10:25:33,089;10.8;0.035300;V
+2025-08-11T10:25:33,289;11;0.035300;V
+2025-08-11T10:25:33,489;11.2;0.033100;V
+2025-08-11T10:25:33,689;11.4;0.033100;V
+2025-08-11T10:25:33,889;11.6;0.030500;V
+2025-08-11T10:25:34,089;11.8;0.027900;V
+2025-08-11T10:25:34,289;12;0.027900;V
+2025-08-11T10:25:34,489;12.2;0.053300;V
+2025-08-11T10:25:34,689;12.4;0.053300;V
+2025-08-11T10:25:34,889;12.6;0.041200;V
+2025-08-11T10:25:35,089;12.8;0.029100;V
+2025-08-11T10:25:35,289;13;0.029100;V
+2025-08-11T10:25:35,489;13.2;0.023000;V
+2025-08-11T10:25:35,689;13.4;0.023000;V
+2025-08-11T10:25:35,889;13.6;0.020800;V
+2025-08-11T10:25:36,089;13.8;0.018600;V
+2025-08-11T10:25:36,289;14;0.018600;V
+2025-08-11T10:25:36,489;14.2;0.007700;V
+2025-08-11T10:25:36,689;14.4;0.007700;V
+2025-08-11T10:25:36,889;14.6;0.006100;V
+2025-08-11T10:25:37,089;14.8;0.004500;V
+2025-08-11T10:25:37,289;15;0.004500;V
+2025-08-11T10:25:37,489;15.2;0.080400;V
+2025-08-11T10:25:37,689;15.4;0.080400;V
+2025-08-11T10:25:37,889;15.6;0.118350;V
+2025-08-11T10:25:38,089;15.8;0.156300;V
+2025-08-11T10:25:38,289;16;0.156300;V
+2025-08-11T10:25:38,489;16.2;-0.011100;V
+2025-08-11T10:25:38,689;16.4;-0.011100;V
+2025-08-11T10:25:38,889;16.6;0.001150;V
+2025-08-11T10:25:39,089;16.8;0.013400;V
+2025-08-11T10:25:39,289;17;0.013400;V
+2025-08-11T10:25:39,489;17.2;-0.051650;V
+2025-08-11T10:25:39,689;17.4;-0.116700;V
+2025-08-11T10:25:39,889;17.6;-0.116700;V
+2025-08-11T10:25:40,089;17.8;-0.004700;V
+2025-08-11T10:25:40,289;18;-0.004700;V
+2025-08-11T10:25:40,489;18.2;-0.022100;V
+2025-08-11T10:25:40,689;18.4;-0.039500;V
+2025-08-11T10:25:40,889;18.6;-0.039500;V
+2025-08-11T10:25:41,089;18.8;0.017900;V
+2025-08-11T10:25:41,289;19;0.017900;V
+2025-08-11T10:25:41,489;19.2;0.008900;V
+2025-08-11T10:25:41,689;19.4;-0.000100;V
+2025-08-11T10:25:41,889;19.6;-0.000100;V
+2025-08-11T10:25:42,089;19.8;-0.042000;V
+2025-08-11T10:25:42,289;20;-0.042000;V
+2025-08-11T10:25:42,489;20.2;-0.005550;V
+2025-08-11T10:25:42,689;20.4;0.030900;V
+2025-08-11T10:25:42,889;20.6;0.030900;V
+2025-08-11T10:25:43,089;20.8;0.006100;V
+2025-08-11T10:25:43,289;21;0.006100;V
+2025-08-11T10:25:43,489;21.2;-0.006100;V
+2025-08-11T10:25:43,689;21.4;-0.018300;V
+2025-08-11T10:25:43,889;21.6;-0.018300;V
+2025-08-11T10:25:44,089;21.8;0.000700;V
+2025-08-11T10:25:44,289;22;0.000700;V
+2025-08-11T10:25:44,489;22.2;0.009400;V
+2025-08-11T10:25:44,689;22.4;0.018100;V
+2025-08-11T10:25:44,889;22.6;0.018100;V
+2025-08-11T10:25:45,089;22.8;0.032300;V
+2025-08-11T10:25:45,289;23;0.032300;V
+2025-08-11T10:25:45,489;23.2;0.032600;V
+2025-08-11T10:25:45,689;23.4;0.032900;V
+2025-08-11T10:25:45,889;23.6;0.032900;V
+2025-08-11T10:25:46,089;23.8;0.029600;V
+2025-08-11T10:25:46,289;24;0.029600;V
+2025-08-11T10:25:46,489;24.2;0.006350;V
+2025-08-11T10:25:46,689;24.4;-0.016900;V
+2025-08-11T10:25:46,889;24.6;-0.016900;V
+2025-08-11T10:25:47,089;24.8;-0.001000;V
+2025-08-11T10:25:47,289;25;-0.001000;V
+2025-08-11T10:25:47,489;25.2;0.004100;V
+2025-08-11T10:25:47,689;25.4;0.009200;V
+2025-08-11T10:25:47,889;25.6;0.009200;V
+2025-08-11T10:25:48,089;25.8;0.008100;V
+2025-08-11T10:25:48,289;26;0.008100;V
+2025-08-11T10:25:48,489;26.2;0.002950;V
+2025-08-11T10:25:48,689;26.4;-0.002200;V
+2025-08-11T10:25:48,889;26.6;-0.002200;V
+2025-08-11T10:25:49,089;26.8;0.015600;V
+2025-08-11T10:25:49,289;27;0.015600;V
+2025-08-11T10:25:49,489;27.2;0.007800;V
+2025-08-11T10:25:49,689;27.4;0.000000;V
+2025-08-11T10:25:49,889;27.6;0.000000;V
+2025-08-11T10:25:50,089;27.8;0.014200;V
+2025-08-11T10:25:50,289;28;0.014200;V
+2025-08-11T10:25:50,489;28.2;0.040000;V
+2025-08-11T10:25:50,689;28.4;0.065800;V
+2025-08-11T10:25:50,889;28.6;0.065800;V
+2025-08-11T10:25:51,089;28.8;0.015300;V
+2025-08-11T10:25:51,289;29;0.015300;V
+2025-08-11T10:25:51,489;29.2;0.018650;V
+2025-08-11T10:25:51,689;29.4;0.022000;V
+2025-08-11T10:25:51,889;29.6;0.022000;V
+2025-08-11T10:25:52,089;29.8;0.007600;V
+2025-08-11T10:25:52,289;30;0.007600;V
+2025-08-11T10:25:52,489;30.2;0.000800;V
+2025-08-11T10:25:52,689;30.4;-0.006000;V
+2025-08-11T10:25:52,889;30.6;-0.006000;V
+2025-08-11T10:25:53,089;30.8;0.002400;V
+2025-08-11T10:25:53,289;31;0.002400;V
+2025-08-11T10:25:53,489;31.2;0.002700;V
+2025-08-11T10:25:53,689;31.4;0.003000;V
+2025-08-11T10:25:53,889;31.6;0.003000;V
+2025-08-11T10:25:54,089;31.8;0.549700;V
+2025-08-11T10:25:54,289;32;0.549700;V
+2025-08-11T10:25:54,489;32.2;0.292850;V
+2025-08-11T10:25:54,689;32.4;0.036000;V
+2025-08-11T10:25:54,889;32.6;0.036000;V
+2025-08-11T10:25:55,089;32.8;0.000000;V
+2025-08-11T10:25:55,289;33;0.000000;V
+2025-08-11T10:25:55,489;33.2;-0.006500;V
+2025-08-11T10:25:55,689;33.4;-0.013000;V
+2025-08-11T10:25:55,889;33.6;-0.013000;V
+2025-08-11T10:25:56,089;33.8;0.000000;V
+2025-08-11T10:25:56,289;34;0.000000;V
+2025-08-11T10:25:56,489;34.2;-0.003500;V
+2025-08-11T10:25:56,689;34.4;-0.007000;V
+2025-08-11T10:25:56,889;34.6;-0.007000;V
+2025-08-11T10:25:57,089;34.8;-0.007000;V
+2025-08-11T10:25:57,289;35;-0.007000;V
+2025-08-11T10:25:57,489;35.2;-0.004000;V
+2025-08-11T10:25:57,689;35.4;-0.001000;V
+2025-08-11T10:25:57,889;35.6;-0.001000;V
+2025-08-11T10:25:58,089;35.8;0.322000;V
+2025-08-11T10:25:58,289;36;0.322000;V
+2025-08-11T10:25:58,489;36.2;0.075000;V
+2025-08-11T10:25:58,689;36.4;-0.172000;V
+2025-08-11T10:25:58,889;36.6;-0.172000;V
+2025-08-11T10:25:59,089;36.8;0.123000;V
+2025-08-11T10:25:59,289;37;0.123000;V
+2025-08-11T10:25:59,489;37.2;-0.551500;V
+2025-08-11T10:25:59,689;37.4;-1.226000;V
+2025-08-11T10:25:59,889;37.6;-1.226000;V
+2025-08-11T10:26:00,089;37.8;-0.059000;V
+2025-08-11T10:26:00,289;38;-0.059000;V
+2025-08-11T10:26:00,489;38.2;0.345500;V
+2025-08-11T10:26:00,689;38.4;0.750000;V
+2025-08-11T10:26:00,889;38.6;0.750000;V
+2025-08-11T10:26:01,089;38.8;1.205000;V
+2025-08-11T10:26:01,289;39;1.205000;V
+2025-08-11T10:26:01,489;39.2;1.241500;V
+2025-08-11T10:26:01,689;39.4;1.278000;V
+2025-08-11T10:26:01,889;39.6;1.278000;V
+2025-08-11T10:26:02,089;39.8;0.704000;V
+2025-08-11T10:26:02,289;40;0.704000;V
+2025-08-11T10:26:02,489;40.2;0.718500;V
+2025-08-11T10:26:02,689;40.4;0.733000;V
+2025-08-11T10:26:02,889;40.6;0.733000;V
+2025-08-11T10:26:03,089;40.8;0.153000;V
+2025-08-11T10:26:03,289;41;0.153000;V
+2025-08-11T10:26:03,489;41.2;0.198500;V
+2025-08-11T10:26:03,689;41.4;0.244000;V
+2025-08-11T10:26:03,889;41.6;0.244000;V
+2025-08-11T10:26:04,089;41.8;-0.013000;V
+2025-08-11T10:26:04,289;42;-0.013000;V
+2025-08-11T10:26:04,489;42.2;-0.344500;V
+2025-08-11T10:26:04,689;42.4;-0.676000;V
+2025-08-11T10:26:04,889;42.6;-0.676000;V
+2025-08-11T10:26:05,089;42.8;-0.820000;V
+2025-08-11T10:26:05,289;43;-0.820000;V
+2025-08-11T10:26:05,489;43.2;-0.059500;V
+2025-08-11T10:26:05,689;43.4;0.701000;V
+2025-08-11T10:26:05,889;43.6;0.701000;V
+2025-08-11T10:26:06,089;43.8;1.168000;V
+2025-08-11T10:26:06,289;44;1.168000;V
+2025-08-11T10:26:06,489;44.2;0.625500;V
+2025-08-11T10:26:06,689;44.4;0.083000;V
+2025-08-11T10:26:06,889;44.6;0.083000;V
+2025-08-11T10:26:07,089;44.8;-0.237000;V
+2025-08-11T10:26:07,289;45;-0.237000;V
+2025-08-11T10:26:07,489;45.2;-0.232000;V
+2025-08-11T10:26:07,689;45.4;-0.227000;V
+2025-08-11T10:26:07,889;45.6;-0.227000;V
+2025-08-11T10:26:08,089;45.8;0.432000;V
+2025-08-11T10:26:08,289;46;0.432000;V
+2025-08-11T10:26:08,489;46.2;0.348500;V
+2025-08-11T10:26:08,689;46.4;0.265000;V
+2025-08-11T10:26:08,889;46.6;0.265000;V
+2025-08-11T10:26:09,089;46.8;-0.043000;V
+2025-08-11T10:26:09,289;47;-0.043000;V
+2025-08-11T10:26:09,489;47.2;-0.021500;V
+2025-08-11T10:26:09,689;47.4;0.000000;V
+2025-08-11T10:26:09,889;47.6;0.000000;V
+2025-08-11T10:26:10,089;47.8;1.144200;V
+2025-08-11T10:26:10,289;48;1.144200;V
+2025-08-11T10:26:10,489;48.2;0.712500;V
+2025-08-11T10:26:10,689;48.4;0.280800;V
+2025-08-11T10:26:10,889;48.6;0.280800;V
+2025-08-11T10:26:11,089;48.8;0.835900;V
+2025-08-11T10:26:11,289;49;0.835900;V
+2025-08-11T10:26:11,489;49.2;0.046150;V
+2025-08-11T10:26:11,689;49.4;-0.743600;V
+2025-08-11T10:26:11,889;49.6;-0.743600;V
+2025-08-11T10:26:12,089;49.8;0.002900;V
+2025-08-11T10:26:12,289;50;0.002900;V
+2025-08-11T10:26:12,489;50.2;0.003250;V
+2025-08-11T10:26:12,689;50.4;0.003600;V
+2025-08-11T10:26:12,889;50.6;0.003600;V
+2025-08-11T10:26:13,089;50.8;-0.049300;V
+2025-08-11T10:26:13,289;51;-0.049300;V
+2025-08-11T10:26:13,489;51.2;-0.024250;V
+2025-08-11T10:26:13,689;51.4;0.000800;V
+2025-08-11T10:26:13,889;51.6;0.000800;V
+2025-08-11T10:26:14,089;51.8;-0.009800;V
+2025-08-11T10:26:14,289;52;-0.009800;V
+2025-08-11T10:26:14,489;52.2;-0.016300;V
+2025-08-11T10:26:14,689;52.4;-0.022800;V
+2025-08-11T10:26:14,889;52.6;-0.022800;V
+2025-08-11T10:26:15,089;52.8;-0.003100;V
+2025-08-11T10:26:15,289;53;-0.003100;V
+2025-08-11T10:26:15,489;53.2;-0.006600;V
+2025-08-11T10:26:15,689;53.4;-0.010100;V
+2025-08-11T10:26:15,889;53.6;-0.010100;V
+2025-08-11T10:26:16,089;53.8;0.001600;V
+2025-08-11T10:26:16,289;54;0.001600;V
diff --git a/tests/data/m9803r.json b/tests/data/m9803r.json
deleted file mode 100644
index cbec126..0000000
--- a/tests/data/m9803r.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "M9803RContinuous",
- "tests": [
- {
- "hex": "00 04 03 02 01 00 00 00 04 00 0D 0A",
- "expected": {
- "dval": 123.4,
- "val": "123.4",
- "unit": "mV",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/qm1537.json b/tests/data/qm1537.json
deleted file mode 100644
index a0cda73..0000000
--- a/tests/data/qm1537.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "QM1537Continuous",
- "tests": [
- {
- "hex": "2B 33 34 38 32 20 34 31 00 40 80 22 0D 0A",
- "expected": {
- "dval": 348.2,
- "val": "348.2",
- "unit": "mV",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/rs22812.json b/tests/data/rs22812.json
deleted file mode 100644
index 55701b0..0000000
--- a/tests/data/rs22812.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "RS22812Continuous",
- "tests": [
- {
- "hex": "00 03 00 D7 D7 DF 50 0B 24",
- "expected": {
- "dval": -0.001,
- "val": " -1.000",
- "unit": "mV",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/vc820.json b/tests/data/vc820.json
deleted file mode 100644
index 7d72a17..0000000
--- a/tests/data/vc820.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "decoder": "VC820Continuous",
- "tests": [
- {
- "hex": "17 2F 3D 47 5D 67 7D 8A 97 A0 B8 C0 D4 E8",
- "expected": {
- "dval": -0.4,
- "val": " -000.4",
- "unit": "mV",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- },
- {
- "hex": "15 25 3B 40 55 67 7D 8F 9E A0 B0 C0 D1 E1",
- "expected": {
- "dval": 210.6,
- "val": " 210.6",
- "unit": "C",
- "range": "MANU",
- "special": "TE",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/vc870.json b/tests/data/vc870.json
deleted file mode 100644
index 6bb6fc9..0000000
--- a/tests/data/vc870.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "decoder": "VC870Continuous",
- "tests": [
- {
- "hex": "30 30 30 34 30 30 30 30 34 30 30 30 30 00 00 30 30 30 30 30 31 0D 0A",
- "expected": {
- "dval": 4.0,
- "val": " 4.0000",
- "unit": "V",
- "special": "DC",
- "dval2": 4.0,
- "val2": " 4.0000",
- "unit2": "V",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}
diff --git a/tests/data/vc940.json b/tests/data/vc940.json
deleted file mode 100644
index e4c9372..0000000
--- a/tests/data/vc940.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "decoder": "VC940Continuous",
- "tests": [
- {
- "hex": "30 30 30 30 30 31 31 30 31 0D 0A",
- "expected": {
- "dval": 0.0,
- "val": "0.0000",
- "unit": "V",
- "range": "AUTO",
- "special": "DC",
- "hold": false,
- "showBar": true
- }
- }
- ]
-}