From 66937e2cfcde72c22f5bcbe884c8dee3926418d0 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Mon, 13 Oct 2025 16:30:44 +0200 Subject: [PATCH 1/5] Implementation using python invocation using qprocess --- CMakeLists.txt | 6 +++++ mainwindow.cpp | 21 ++++++++++++++++- mainwindow.h | 4 ++++ pythonembed.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ pythonembed.h | 31 ++++++++++++++++++++++++ pythonrunner.cpp | 26 +++++++++++++++++++++ pythonrunner.h | 26 +++++++++++++++++++++ 7 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 pythonembed.cpp create mode 100644 pythonembed.h create mode 100644 pythonrunner.cpp create mode 100644 pythonrunner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a5c3161..32b9cc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(EISMULIPLEXER REQUIRED eismuliplexer) find_package(Qt6 REQUIRED COMPONENTS Widgets) find_package(Qt6 REQUIRED COMPONENTS Core) +# find_package(Python3 REQUIRED) - Removed since we're using QProcess instead of embedding Python set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) @@ -45,11 +46,16 @@ add_executable(${PROJECT_NAME} multiplexer.cpp triggerwidget.cpp triggerwidget.h + pythonrunner.cpp + pythonrunner.h + pythonembed.cpp ) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON) target_compile_options(${PROJECT_NAME} PUBLIC "-Wall") target_include_directories(${PROJECT_NAME} PUBLIC QCodeEditor) +# target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS}) - Removed since we're using QProcess instead of embedding Python target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::Core ${EISMULIPLEXER_LIBRARIES} QCodeEditor) +# ${Python3_LIBRARIES} - Removed since we're using QProcess instead of embedding Python set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png PROPERTIES QT_RESOURCE_ALIAS eismultiplexerqt.png) qt_add_resources(${PROJECT_NAME} "resources" PREFIX "/" FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/eismultiplexerqt.png ) install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/mainwindow.cpp b/mainwindow.cpp index efef4b8..14a525f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -15,7 +15,8 @@ MainWindow::MainWindow(QWidget *parent): codeEditor(this), pythonOutput(this), currentFilePath(""), - isFileModified(false) + isFileModified(false), + pythonRunner(nullptr) { ui->setupUi(this); enumerateDevices(); @@ -49,6 +50,11 @@ MainWindow::MainWindow(QWidget *parent): isFileModified = true; updateStatus(); }); + + // Connect Run and Stop buttons + pythonRunner = new PythonRunner(&pythonOutput, this); + connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::onPushButtonRunClicked); + connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::onPushButtonStopClicked); } MainWindow::~MainWindow() @@ -226,6 +232,19 @@ void MainWindow::enumerateDevices() generateExample(); } +void MainWindow::onPushButtonRunClicked() { + QString scriptContent = codeEditor.toPlainText(); + pythonRunner->runScript(scriptContent); + ui->pushButtonRun->setEnabled(false); + ui->pushButtonStop->setEnabled(true); +} + +void MainWindow::onPushButtonStopClicked() { + pythonRunner->stopScript(); + ui->pushButtonRun->setEnabled(true); + ui->pushButtonStop->setEnabled(false); +} + void MainWindow::generateExample() { QString example = diff --git a/mainwindow.h b/mainwindow.h index ec96f0a..df74472 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -11,6 +11,7 @@ #include "channelwidget.h" #include "triggerwidget.h" +#include "pythonrunner.h" namespace Ui { @@ -27,6 +28,7 @@ class MainWindow : public QMainWindow QPythonHighlighter highligter; QPythonCompleter completer; QTextEdit pythonOutput; + PythonRunner* pythonRunner; signals: void channelStateChanged(uint16_t device, uint16_t channel); @@ -39,6 +41,8 @@ private slots: void onActionOpenTriggered(); void onActionSaveTriggered(); void onActionSaveAsTriggered(); + void onPushButtonRunClicked(); + void onPushButtonStopClicked(); private: void enumerateDevices(); diff --git a/pythonembed.cpp b/pythonembed.cpp new file mode 100644 index 0000000..f6df212 --- /dev/null +++ b/pythonembed.cpp @@ -0,0 +1,61 @@ + + +#include "pythonembed.h" +#include + +PythonEmbed::PythonEmbed(QTextEdit* outputWidget, QObject* parent) + : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { + m_process = new QProcess(this); + connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonEmbed::onOutputAvailable); + connect(m_process, &QProcess::readyReadStandardError, this, &PythonEmbed::onErrorAvailable); + connect(m_process, QOverload::of(&QProcess::finished), this, &PythonEmbed::onProcessFinished); +} + +PythonEmbed::~PythonEmbed() { + if (m_process) { + m_process->terminate(); + m_process->waitForFinished(1000); + } +} + +void PythonEmbed::runScript(const QString& scriptContent) { + if (m_process->state() == QProcess::Running) { + m_process->terminate(); + m_process->waitForFinished(1000); + } + + m_outputWidget->clear(); + m_outputWidget->append("Python 3.11.2 (main, Oct 5 2023, 17:20:59) [GCC 11.4.0] on linux\n"); + m_outputWidget->append("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\n"); + + m_process->start("python3", QStringList() << "-c" << scriptContent); +} + +void PythonEmbed::stopScript() { + if (m_process && m_process->state() == QProcess::Running) { + m_process->terminate(); + } +} + +void PythonEmbed::onOutputAvailable() { + QByteArray output = m_process->readAllStandardOutput(); + m_outputWidget->append(output); + m_outputWidget->moveCursor(QTextCursor::End); +} + +void PythonEmbed::onErrorAvailable() { + QByteArray error = m_process->readAllStandardError(); + m_outputWidget->append(error); + m_outputWidget->moveCursor(QTextCursor::End); +} + +void PythonEmbed::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus == QProcess::NormalExit && exitCode != 0) { + m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode)); + } else if (exitStatus == QProcess::CrashExit) { + m_outputWidget->append("Python process crashed\n"); + } +} + +#include "pythonembed.moc" + diff --git a/pythonembed.h b/pythonembed.h new file mode 100644 index 0000000..b48bded --- /dev/null +++ b/pythonembed.h @@ -0,0 +1,31 @@ + + +#ifndef PYTHONEMBED_H +#define PYTHONEMBED_H + +#include +#include +#include + +class PythonEmbed : public QObject { + Q_OBJECT + +public: + PythonEmbed(QTextEdit* outputWidget, QObject* parent = nullptr); + ~PythonEmbed(); + + void runScript(const QString& scriptContent); + void stopScript(); + +private slots: + void onOutputAvailable(); + void onErrorAvailable(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QTextEdit* m_outputWidget; + QProcess* m_process; +}; + +#endif // PYTHONEMBED_H + diff --git a/pythonrunner.cpp b/pythonrunner.cpp new file mode 100644 index 0000000..e2f09ca --- /dev/null +++ b/pythonrunner.cpp @@ -0,0 +1,26 @@ + +#include "pythonrunner.h" +#include "pythonembed.h" + +PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) + : QObject(parent), m_outputWidget(outputWidget), m_pythonEmbed(nullptr) { + m_pythonEmbed = new PythonEmbed(m_outputWidget, this); +} + +PythonRunner::~PythonRunner() { + delete m_pythonEmbed; +} + +void PythonRunner::runScript(const QString& scriptContent) { + if (m_pythonEmbed) { + m_pythonEmbed->runScript(scriptContent); + } +} + +void PythonRunner::stopScript() { + if (m_pythonEmbed) { + m_pythonEmbed->stopScript(); + } +} + +#include "pythonrunner.moc" diff --git a/pythonrunner.h b/pythonrunner.h new file mode 100644 index 0000000..b5067cb --- /dev/null +++ b/pythonrunner.h @@ -0,0 +1,26 @@ + +#ifndef PYTHONRUNNER_H +#define PYTHONRUNNER_H + +#include +#include + +class PythonEmbed; + +class PythonRunner : public QObject { + Q_OBJECT + +public: + explicit PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); + ~PythonRunner(); + +public slots: + void runScript(const QString& scriptContent); + void stopScript(); + +private: + QTextEdit* m_outputWidget; + PythonEmbed* m_pythonEmbed; +}; + +#endif // PYTHONRUNNER_H From 738c8805f9a7b9dd8835b1e11bf2c9bbff7e4fdc Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Mon, 13 Oct 2025 17:00:02 +0200 Subject: [PATCH 2/5] Fix missing = in python example --- mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index efef4b8..7e1ffc9 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -241,7 +241,7 @@ void MainWindow::generateExample() size_t i = 0; for (uint16_t serial : serials) { - example.append(QString("multiplexer_") + QString::number(i) + " multi.Multiplexer(serial=" + QString::number(serial) + ")\n"); + example.append(QString("multiplexer_") + QString::number(i) + " = multi.Multiplexer(serial=" + QString::number(serial) + ")\n"); ++i; } From 87db38b08ef0354c306255b85a816daf9a6c7625 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Mon, 13 Oct 2025 17:06:18 +0200 Subject: [PATCH 3/5] QCodeEditor: update cmake_minimum_required --- external/QCodeEditor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/QCodeEditor/CMakeLists.txt b/external/QCodeEditor/CMakeLists.txt index 63918e8..f0b611b 100644 --- a/external/QCodeEditor/CMakeLists.txt +++ b/external/QCodeEditor/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.6) +cmake_minimum_required(VERSION 3.14) project(QCodeEditor) set(CMAKE_CXX_STANDARD 11) From 1eac3f6a83cf23916ec197e42b25720f680b6f8c Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Mon, 13 Oct 2025 18:12:32 +0200 Subject: [PATCH 4/5] Cleanup python script running --- CMakeLists.txt | 1 - mainwindow.cpp | 22 ++++++++++------- mainwindow.h | 9 ++++--- pythonembed.cpp | 61 ------------------------------------------------ pythonembed.h | 31 ------------------------ pythonrunner.cpp | 48 +++++++++++++++++++++++++++++-------- pythonrunner.h | 24 ++++++++++--------- 7 files changed, 68 insertions(+), 128 deletions(-) delete mode 100644 pythonembed.cpp delete mode 100644 pythonembed.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b9cc7..a1ce0a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,6 @@ add_executable(${PROJECT_NAME} triggerwidget.h pythonrunner.cpp pythonrunner.h - pythonembed.cpp ) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON) target_compile_options(${PROJECT_NAME} PUBLIC "-Wall") diff --git a/mainwindow.cpp b/mainwindow.cpp index 05cc234..ed4f5b4 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -14,9 +14,9 @@ MainWindow::MainWindow(QWidget *parent): ui(new Ui::MainWindow), codeEditor(this), pythonOutput(this), + pythonRunner(&pythonOutput), currentFilePath(""), - isFileModified(false), - pythonRunner(nullptr) + isFileModified(false) { ui->setupUi(this); enumerateDevices(); @@ -52,9 +52,9 @@ MainWindow::MainWindow(QWidget *parent): }); // Connect Run and Stop buttons - pythonRunner = new PythonRunner(&pythonOutput, this); - connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::onPushButtonRunClicked); - connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::onPushButtonStopClicked); + connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); + connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); + connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); } MainWindow::~MainWindow() @@ -232,17 +232,21 @@ void MainWindow::enumerateDevices() generateExample(); } -void MainWindow::onPushButtonRunClicked() { +void MainWindow::runScript() { QString scriptContent = codeEditor.toPlainText(); - pythonRunner->runScript(scriptContent); + pythonRunner.runScript(scriptContent); ui->pushButtonRun->setEnabled(false); ui->pushButtonStop->setEnabled(true); + codeEditor.setEnabled(false); + ui->scrollArea->setEnabled(false); } -void MainWindow::onPushButtonStopClicked() { - pythonRunner->stopScript(); +void MainWindow::stopScript() { + pythonRunner.stopScript(); ui->pushButtonRun->setEnabled(true); ui->pushButtonStop->setEnabled(false); + codeEditor.setEnabled(true); + ui->scrollArea->setEnabled(true); } void MainWindow::generateExample() diff --git a/mainwindow.h b/mainwindow.h index df74472..0c60a8f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,5 +1,3 @@ - - #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -8,6 +6,7 @@ #include #include #include +#include #include "channelwidget.h" #include "triggerwidget.h" @@ -28,7 +27,7 @@ class MainWindow : public QMainWindow QPythonHighlighter highligter; QPythonCompleter completer; QTextEdit pythonOutput; - PythonRunner* pythonRunner; + PythonRunner pythonRunner; signals: void channelStateChanged(uint16_t device, uint16_t channel); @@ -41,8 +40,8 @@ private slots: void onActionOpenTriggered(); void onActionSaveTriggered(); void onActionSaveAsTriggered(); - void onPushButtonRunClicked(); - void onPushButtonStopClicked(); + void runScript(); + void stopScript(); private: void enumerateDevices(); diff --git a/pythonembed.cpp b/pythonembed.cpp deleted file mode 100644 index f6df212..0000000 --- a/pythonembed.cpp +++ /dev/null @@ -1,61 +0,0 @@ - - -#include "pythonembed.h" -#include - -PythonEmbed::PythonEmbed(QTextEdit* outputWidget, QObject* parent) - : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { - m_process = new QProcess(this); - connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonEmbed::onOutputAvailable); - connect(m_process, &QProcess::readyReadStandardError, this, &PythonEmbed::onErrorAvailable); - connect(m_process, QOverload::of(&QProcess::finished), this, &PythonEmbed::onProcessFinished); -} - -PythonEmbed::~PythonEmbed() { - if (m_process) { - m_process->terminate(); - m_process->waitForFinished(1000); - } -} - -void PythonEmbed::runScript(const QString& scriptContent) { - if (m_process->state() == QProcess::Running) { - m_process->terminate(); - m_process->waitForFinished(1000); - } - - m_outputWidget->clear(); - m_outputWidget->append("Python 3.11.2 (main, Oct 5 2023, 17:20:59) [GCC 11.4.0] on linux\n"); - m_outputWidget->append("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\n"); - - m_process->start("python3", QStringList() << "-c" << scriptContent); -} - -void PythonEmbed::stopScript() { - if (m_process && m_process->state() == QProcess::Running) { - m_process->terminate(); - } -} - -void PythonEmbed::onOutputAvailable() { - QByteArray output = m_process->readAllStandardOutput(); - m_outputWidget->append(output); - m_outputWidget->moveCursor(QTextCursor::End); -} - -void PythonEmbed::onErrorAvailable() { - QByteArray error = m_process->readAllStandardError(); - m_outputWidget->append(error); - m_outputWidget->moveCursor(QTextCursor::End); -} - -void PythonEmbed::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { - if (exitStatus == QProcess::NormalExit && exitCode != 0) { - m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode)); - } else if (exitStatus == QProcess::CrashExit) { - m_outputWidget->append("Python process crashed\n"); - } -} - -#include "pythonembed.moc" - diff --git a/pythonembed.h b/pythonembed.h deleted file mode 100644 index b48bded..0000000 --- a/pythonembed.h +++ /dev/null @@ -1,31 +0,0 @@ - - -#ifndef PYTHONEMBED_H -#define PYTHONEMBED_H - -#include -#include -#include - -class PythonEmbed : public QObject { - Q_OBJECT - -public: - PythonEmbed(QTextEdit* outputWidget, QObject* parent = nullptr); - ~PythonEmbed(); - - void runScript(const QString& scriptContent); - void stopScript(); - -private slots: - void onOutputAvailable(); - void onErrorAvailable(); - void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - -private: - QTextEdit* m_outputWidget; - QProcess* m_process; -}; - -#endif // PYTHONEMBED_H - diff --git a/pythonrunner.cpp b/pythonrunner.cpp index e2f09ca..8f7e126 100644 --- a/pythonrunner.cpp +++ b/pythonrunner.cpp @@ -1,26 +1,54 @@ - #include "pythonrunner.h" -#include "pythonembed.h" +#include PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) - : QObject(parent), m_outputWidget(outputWidget), m_pythonEmbed(nullptr) { - m_pythonEmbed = new PythonEmbed(m_outputWidget, this); + : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr) { + m_process = new QProcess(this); + connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable); + connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable); + connect(m_process, QOverload::of(&QProcess::finished), this, &PythonRunner::onProcessFinished); } PythonRunner::~PythonRunner() { - delete m_pythonEmbed; + if (m_process) { + m_process->terminate(); + m_process->waitForFinished(1000); + } } void PythonRunner::runScript(const QString& scriptContent) { - if (m_pythonEmbed) { - m_pythonEmbed->runScript(scriptContent); + if (m_process->state() == QProcess::Running) { + m_process->terminate(); + m_process->waitForFinished(1000); } + + m_outputWidget->clear(); + m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent); } void PythonRunner::stopScript() { - if (m_pythonEmbed) { - m_pythonEmbed->stopScript(); + if (m_process && m_process->state() == QProcess::Running) { + m_process->terminate(); } } -#include "pythonrunner.moc" +void PythonRunner::onOutputAvailable() { + QByteArray output = m_process->readAllStandardOutput(); + m_outputWidget->append(output); + m_outputWidget->moveCursor(QTextCursor::End); +} + +void PythonRunner::onErrorAvailable() { + QByteArray error = m_process->readAllStandardError(); + m_outputWidget->append(error); + m_outputWidget->moveCursor(QTextCursor::End); +} + +void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus == QProcess::NormalExit && exitCode != 0) { + m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode)); + } else if (exitStatus == QProcess::CrashExit) { + m_outputWidget->append("Python was stopped\n"); + } + emit scriptFinished(exitCode); +} diff --git a/pythonrunner.h b/pythonrunner.h index b5067cb..3855486 100644 --- a/pythonrunner.h +++ b/pythonrunner.h @@ -1,26 +1,28 @@ +#pragma once -#ifndef PYTHONRUNNER_H -#define PYTHONRUNNER_H - -#include #include - -class PythonEmbed; +#include +#include class PythonRunner : public QObject { Q_OBJECT public: - explicit PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); + PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); ~PythonRunner(); -public slots: void runScript(const QString& scriptContent); void stopScript(); +private slots: + void onOutputAvailable(); + void onErrorAvailable(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + +signals: + void scriptFinished(int code); + private: QTextEdit* m_outputWidget; - PythonEmbed* m_pythonEmbed; + QProcess* m_process; }; - -#endif // PYTHONRUNNER_H From 4c2a4790c0dc664ad6047e6d83af55e7fd5c1a10 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Tue, 14 Oct 2025 12:59:55 +0200 Subject: [PATCH 5/5] Add support for reading state --- CMakeLists.txt | 2 - channelwidget.cpp | 9 ++++- channelwidget.h | 1 + mainwindow.cpp | 53 +++++++++++++++++++++--- mainwindow.h | 13 +++--- mainwindow.ui | 101 +++++++++++++++++++++++++--------------------- multiplexer.cpp | 16 -------- multiplexer.h | 22 ---------- pythonrunner.cpp | 4 +- pythonrunner.h | 5 ++- triggerwidget.cpp | 39 +++++++++++++++++- triggerwidget.h | 1 + 12 files changed, 163 insertions(+), 103 deletions(-) delete mode 100644 multiplexer.cpp delete mode 100644 multiplexer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a1ce0a5..11ab008 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,6 @@ add_executable(${PROJECT_NAME} mainwindow.h mainwindow.cpp mainwindow.ui - multiplexer.h - multiplexer.cpp triggerwidget.cpp triggerwidget.h pythonrunner.cpp diff --git a/channelwidget.cpp b/channelwidget.cpp index 5c32f45..2f6a02e 100644 --- a/channelwidget.cpp +++ b/channelwidget.cpp @@ -108,7 +108,6 @@ void ChannelWidget::onGangComboChanged(int index) gangedChannelNumber = -1; checkbox.setEnabled(true); checkbox.setChecked(false); - checkbox.setChecked(false); } else { // A ganged channel was selected QString currentText = gangcombo.currentText(); @@ -128,7 +127,7 @@ void ChannelWidget::onGangComboChanged(int index) void ChannelWidget::onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked) { // If this channel is ganged to the channel that changed state - if (gangedDeviceSerial == deviceSerial && gangedChannelNumber == channelNumber) { + if (this->deviceSerial == deviceSerial && this->channelNumber == channelNumber) { // Update our checkbox state to follow the ganged channel checkbox.blockSignals(true); checkbox.setChecked(checked); @@ -163,3 +162,9 @@ void ChannelWidget::setGangedChannel(uint16_t gangedDeviceSerial, uint16_t gange updateCheckboxState(); } +void ChannelWidget::replaceMultiplexer(std::shared_ptr multiplexer, int serial) +{ + if(serial == deviceSerial || serial == -1) + this->multiplexer = multiplexer; +} + diff --git a/channelwidget.h b/channelwidget.h index 959ad86..f149df0 100644 --- a/channelwidget.h +++ b/channelwidget.h @@ -33,6 +33,7 @@ public slots: private slots: void onChannelToggled(bool checked); void onGangComboChanged(int index); + void replaceMultiplexer(std::shared_ptr multiplexer, int serial = -1); signals: void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber); diff --git a/mainwindow.cpp b/mainwindow.cpp index ed4f5b4..b7bd50e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -19,7 +18,6 @@ MainWindow::MainWindow(QWidget *parent): isFileModified(false) { ui->setupUi(this); - enumerateDevices(); codeEditor.setAutoIndentation(true); codeEditor.setAutoParentheses(true); @@ -55,6 +53,8 @@ MainWindow::MainWindow(QWidget *parent): connect(ui->pushButtonRun, &QPushButton::clicked, this, &MainWindow::runScript); connect(ui->pushButtonStop, &QPushButton::clicked, this, &MainWindow::stopScript); connect(&pythonRunner, &PythonRunner::scriptFinished, this, &MainWindow::stopScript); + enumerateDevices(); + generateExample(); } MainWindow::~MainWindow() @@ -62,6 +62,30 @@ MainWindow::~MainWindow() delete ui; } +void MainWindow::readState() +{ + for (auto& multiplexer : multiplexers) { + channel_t channelstate = eismultiplexer_get_connected(multiplexer.second.get()); + for (auto& channel : channels) { + for(size_t i = 0; i < 16; ++i) { + channel_t mask = static_cast(1 << i); + channel->onOtherChannelStateChanged(multiplexer.first, i, channelstate & mask); + } + } + + int triggerCount = eismultiplexer_get_trigger_count(multiplexer.second.get()); + for(int i = 0; i < triggerCount; ++i) { + bool level; + trigger_state_t state; + int ret = eismultiplexer_get_trigger_state(multiplexer.second.get(), i, &state, &level); + if (ret < 0) { + for (auto& trigger : triggers) + trigger->updateSate(multiplexer.first, i, state); + } + } + } +} + void MainWindow::updateStatus() { if (!currentFilePath.isEmpty()) { @@ -150,6 +174,9 @@ void MainWindow::enumerateDevices() { size_t count = 0; uint16_t* serials = eismultiplexer_list_available_devices(&count); + if (ui->scrollArea->layout()) + delete ui->scrollArea->layout(); + QVBoxLayout* channelLayout = new QVBoxLayout(ui->scrollArea); if (!serials || count == 0) { @@ -168,6 +195,7 @@ void MainWindow::enumerateDevices() int ret = eismultiplexer_connect(multiplexer.get(), serial); if (ret == 0) { + multiplexers.push_back({serial, multiplexer}); uint16_t channelCount = 0; qDebug()<<"Adding channels from device "<= 0) @@ -177,7 +205,7 @@ void MainWindow::enumerateDevices() std::shared_ptr widget(new ChannelWidget(serial, channel, multiplexer)); qDebug()<<"Added widget from device "<channelLayout->addWidget(widget.get()); + channelLayout->addWidget(widget.get()); } } @@ -191,7 +219,7 @@ void MainWindow::enumerateDevices() std::shared_ptr triggerWidget(new TriggerWidget(serial, trigger, multiplexer)); qDebug()<<"Added trigger widget from device "<channelLayout->addWidget(triggerWidget.get()); + channelLayout->addWidget(triggerWidget.get()); } } } @@ -202,7 +230,8 @@ void MainWindow::enumerateDevices() qWarning()<<"Failed to connect to device with serial"<channelLayout->addStretch(); + + channelLayout->addStretch(); // Second pass: populate gang combos and connect signals for channels for (const auto& widget : channels) { @@ -226,10 +255,11 @@ void MainWindow::enumerateDevices() } } + readState(); + ui->statusbar->showMessage("Ready"); free(serials); - generateExample(); } void MainWindow::runScript() { @@ -239,14 +269,25 @@ void MainWindow::runScript() { ui->pushButtonStop->setEnabled(true); codeEditor.setEnabled(false); ui->scrollArea->setEnabled(false); + disconnectDevices(); } void MainWindow::stopScript() { pythonRunner.stopScript(); + channels.clear(); + triggers.clear(); ui->pushButtonRun->setEnabled(true); ui->pushButtonStop->setEnabled(false); codeEditor.setEnabled(true); ui->scrollArea->setEnabled(true); + enumerateDevices(); +} + +void MainWindow::disconnectDevices() +{ + for(auto& multiplexer : multiplexers) + eismultiplexer_disconnect(multiplexer.second.get()); + multiplexers.clear(); } void MainWindow::generateExample() diff --git a/mainwindow.h b/mainwindow.h index 0c60a8f..686c6a2 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,13 +1,13 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include #include #include #include #include #include #include - #include "channelwidget.h" #include "triggerwidget.h" #include "pythonrunner.h" @@ -19,10 +19,11 @@ class MainWindow; class MainWindow : public QMainWindow { - Q_OBJECT - std::vector> channels; - std::vector> triggers; - Ui::MainWindow *ui; + Q_OBJECT + std::vector> triggers; + std::vector> channels; + std::vector>> multiplexers; + Ui::MainWindow *ui; QCodeEditor codeEditor; QPythonHighlighter highligter; QPythonCompleter completer; @@ -42,11 +43,13 @@ private slots: void onActionSaveAsTriggered(); void runScript(); void stopScript(); + void readState(); private: void enumerateDevices(); void generateExample(); void updateStatus(); + void disconnectDevices(); QString currentFilePath; bool isFileModified; }; diff --git a/mainwindow.ui b/mainwindow.ui index d32da49..c858820 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -16,58 +16,65 @@ - - - 10 + + + Qt::Orientation::Horizontal - - + + + + 0 + - - - false - - - Stop - - - - - - - - - - Run - - - - - + + + + + false + + + Stop + + + + + + + + + + Run + + + + + + + - - - - - - - true - - - - - 0 - 0 - 591 - 533 - + + + + + 400 + 0 + - - - - - + + true + + + + + 0 + 0 + 504 + 537 + + + + diff --git a/multiplexer.cpp b/multiplexer.cpp deleted file mode 100644 index 3e1c8e0..0000000 --- a/multiplexer.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "multiplexer.h" - -Multiplexer::Multiplexer(QObject *parent) - : QObject{parent} -{} - -Multiplexer::~Multiplexer() -{ - for(auto& multiplexer : multiplexers) - eismultiplexer_disconnect(multiplexer.get()); -} - -void Multiplexer::probe() -{ - -} diff --git a/multiplexer.h b/multiplexer.h deleted file mode 100644 index 32a04ec..0000000 --- a/multiplexer.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MULTIPLEXER_H -#define MULTIPLEXER_H - -#include -#include "eismultiplexer.h" - -class Multiplexer : public QObject -{ - Q_OBJECT - std::vector> multiplexers; - std::vector channelStates; - -public: - explicit Multiplexer(QObject *parent = nullptr); - ~Multiplexer(); - void probe(); - -signals: - void foundDevice(std::shared_ptr); -}; - -#endif // MULTIPLEXER_H diff --git a/pythonrunner.cpp b/pythonrunner.cpp index 8f7e126..e2ba0ab 100644 --- a/pythonrunner.cpp +++ b/pythonrunner.cpp @@ -16,7 +16,7 @@ PythonRunner::~PythonRunner() { } } -void PythonRunner::runScript(const QString& scriptContent) { +bool PythonRunner::runScript(const QString& scriptContent) { if (m_process->state() == QProcess::Running) { m_process->terminate(); m_process->waitForFinished(1000); @@ -24,6 +24,8 @@ void PythonRunner::runScript(const QString& scriptContent) { m_outputWidget->clear(); m_process->start("python3", QStringList() << "-u" << "-c" << scriptContent); + + return true; } void PythonRunner::stopScript() { diff --git a/pythonrunner.h b/pythonrunner.h index 3855486..8a8ac15 100644 --- a/pythonrunner.h +++ b/pythonrunner.h @@ -3,6 +3,7 @@ #include #include #include +#include class PythonRunner : public QObject { Q_OBJECT @@ -11,7 +12,7 @@ public: PythonRunner(QTextEdit* outputWidget, QObject* parent = nullptr); ~PythonRunner(); - void runScript(const QString& scriptContent); + bool runScript(const QString& scriptContent); void stopScript(); private slots: @@ -25,4 +26,6 @@ signals: private: QTextEdit* m_outputWidget; QProcess* m_process; + bool ready; + QTemporaryDir dir; }; diff --git a/triggerwidget.cpp b/triggerwidget.cpp index efa6627..f051768 100644 --- a/triggerwidget.cpp +++ b/triggerwidget.cpp @@ -70,9 +70,46 @@ void TriggerWidget::onLevelToggled(bool checked) updateTriggerState(); } +void TriggerWidget::updateSate(uint16_t serial, uint16_t trigger, trigger_state_t state) +{ + if (serial == deviceSerial && trigger == triggerNumber) { + switch (state) + { + case TRIGGER_INPUT: + levelCheckbox.blockSignals(true); + levelCheckbox.setChecked(false); + levelCheckbox.setEnabled(false); + levelCheckbox.blockSignals(false); + inputCheckbox.blockSignals(true); + inputCheckbox.setChecked(true); + inputCheckbox.blockSignals(false); + break; + case TRIGGER_HIGH: + inputCheckbox.blockSignals(true); + inputCheckbox.setChecked(false); + inputCheckbox.blockSignals(false); + levelCheckbox.blockSignals(true); + levelCheckbox.setChecked(true); + levelCheckbox.setEnabled(true); + levelCheckbox.blockSignals(false); + break; + case TRIGGER_LOW: + inputCheckbox.blockSignals(true); + inputCheckbox.setChecked(false); + inputCheckbox.blockSignals(false); + levelCheckbox.blockSignals(true); + levelCheckbox.setChecked(true); + levelCheckbox.setEnabled(false); + levelCheckbox.blockSignals(false); + default: + break; + } + } +} + void TriggerWidget::updateTriggerState() { - trigger_state_t state; + trigger_state_t state = TRIGGER_INPUT; if(inputCheckbox.isChecked()) { levelCheckbox.blockSignals(true); diff --git a/triggerwidget.h b/triggerwidget.h index 718ed1b..0fe17cb 100644 --- a/triggerwidget.h +++ b/triggerwidget.h @@ -22,6 +22,7 @@ public: uint16_t getDeviceSerial() const; uint16_t getTriggerNumber() const; + void updateSate(uint16_t serial, uint16_t trigger, trigger_state_t state); private slots: void onInputToggled(bool checked);