From b211ebc0c1b66d5a970c325a940d1c2167f0f0dc Mon Sep 17 00:00:00 2001 From: Sani7 Date: Sun, 29 Oct 2023 16:33:52 +0100 Subject: [PATCH] Before git init --- .gitignore | 2 - Qt/CMakeLists.txt | 107 +++++++++++ Qt/IterableEnum.hpp | 120 ++++++++++++ Qt/better_radiobutton.cpp | 38 ++++ Qt/better_radiobutton.h | 25 +++ Qt/helpers.cpp | 102 ++++++++++ Qt/helpers.h | 26 +++ Qt/main.cpp | 11 ++ Qt/mainwindow.cpp | 169 +++++++++++++++++ Qt/mainwindow.h | 47 +++++ Qt/mainwindow.ui | 43 +++++ Qt/networkerror.cpp | 19 ++ Qt/networkerror.h | 22 +++ Qt/networkerror.ui | 69 +++++++ Qt/qwt | 1 + Qt/sim-can_debugger.cpp | 13 ++ Qt/sim-can_debugger.h | 22 +++ Qt/sim-can_debugger.ui | 31 +++ Qt/simulatorbase.cpp | 388 ++++++++++++++++++++++++++++++++++++++ Qt/simulatorbase.h | 60 ++++++ 20 files changed, 1313 insertions(+), 2 deletions(-) create mode 100644 Qt/CMakeLists.txt create mode 100644 Qt/IterableEnum.hpp create mode 100644 Qt/better_radiobutton.cpp create mode 100644 Qt/better_radiobutton.h create mode 100644 Qt/helpers.cpp create mode 100644 Qt/helpers.h create mode 100644 Qt/main.cpp create mode 100644 Qt/mainwindow.cpp create mode 100644 Qt/mainwindow.h create mode 100644 Qt/mainwindow.ui create mode 100644 Qt/networkerror.cpp create mode 100644 Qt/networkerror.h create mode 100644 Qt/networkerror.ui create mode 160000 Qt/qwt create mode 100644 Qt/sim-can_debugger.cpp create mode 100644 Qt/sim-can_debugger.h create mode 100644 Qt/sim-can_debugger.ui create mode 100644 Qt/simulatorbase.cpp create mode 100644 Qt/simulatorbase.h diff --git a/.gitignore b/.gitignore index a5d136a..b3663e3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,3 @@ .vscode/ build/ - -log-sim_us.txt diff --git a/Qt/CMakeLists.txt b/Qt/CMakeLists.txt new file mode 100644 index 0000000..58b2353 --- /dev/null +++ b/Qt/CMakeLists.txt @@ -0,0 +1,107 @@ +cmake_minimum_required(VERSION 3.5) + +project(Simulator-CVS_I10 VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +include(FetchContent) + +FetchContent_Declare(magic_enum URL https://github.com/Neargye/magic_enum/releases/download/v0.9.6/magic_enum-v0.9.6.tar.gz DOWNLOAD_EXTRACT_TIMESTAMP TRUE) +FetchContent_MakeAvailable(magic_enum) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Svg WebSockets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Svg WebSockets) + +add_subdirectory(qwt) + +set(PROJECT_SOURCES + sim-cvs_i10.h + sim-cvs_i10.cpp + sim-cvs_i10_helper.cpp + sim-cvs_i10.ui + + sim-can_debugger.h + sim-can_debugger.cpp + sim-can_debugger.ui + + sim-scope_mux_tester.h + sim-scope_mux_tester.cpp + sim-scope_mux_tester.ui + + simulatorbase.h + simulatorbase.cpp + helpers.h + helpers.cpp + IterableEnum.hpp + better_radiobutton.h + better_radiobutton.cpp + + networkerror.h + networkerror.cpp + networkerror.ui + + mainwindow.h + mainwindow.cpp + mainwindow.ui + main.cpp +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(Simulator-CVS_I10 + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET Simulator-CVS_I10 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(Simulator-CVS_I10 SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(Simulator-CVS_I10 + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_include_directories(Simulator-CVS_I10 + PUBLIC + ${PROJECT_BINARY_DIR}/ +) + +target_link_libraries(Simulator-CVS_I10 PRIVATE qwt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WebSockets) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Simulator-CVS_I10) +endif() +set_target_properties(Simulator-CVS_I10 PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS Simulator-CVS_I10 + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(Simulator-CVS_I10) +endif() diff --git a/Qt/IterableEnum.hpp b/Qt/IterableEnum.hpp new file mode 100644 index 0000000..963277c --- /dev/null +++ b/Qt/IterableEnum.hpp @@ -0,0 +1,120 @@ +/* + A way to iterate over enumerations as C++ template. + +Copyright (c) 2023 Sander Speetjens +Licensed under the terms of the MIT license (given below). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#pragma once + +#define ITERABLE_ENUM_BEGIN(name) enum class name : int { +#define ITERABLE_ENUM_END ,end, begin = 0 } + +template +class Iterable +{ +public: + Iterable(T value = T::begin) : mValue(value) {} + Iterable(const Iterable& other) : mValue(other.mValue) {} + + Iterable& operator++() + { + assert(mValue >= T::begin && mValue < T::end); + mValue = static_cast(static_cast(mValue) + 1); + return *this; + } + + Iterable operator++(int) + { + Iterable prev = *this; + mValue = static_cast(static_cast(mValue) + 1); + return prev; + } + + Iterable& operator--() + { + assert(mValue >= T::end && mValue < T::begin); + mValue = static_cast(static_cast(mValue) - 1); + return *this; + } + + Iterable operator--(int) + { + Iterable prev = *this; + mValue = static_cast(static_cast(mValue) - 1); + return prev; + } + + Iterable operator+(int i) + { + return static_cast(static_cast(mValue) + i); + } + + Iterable operator+(T i) + { + return static_cast(static_cast(mValue) + static_cast(i)); + } + + Iterable operator-(int i) + { + return static_cast(static_cast(mValue) - i); + } + + Iterable operator-(T i) + { + return static_cast(static_cast(mValue) - static_cast(i)); + } + + + bool operator >(const T& other) const + { + return static_cast(mValue) > static_cast(other); + } + + bool operator >=(const T& other) const + { + return static_cast(mValue) >= static_cast(other); + } + + bool operator <(const T& other) const + { + return static_cast(mValue) < static_cast(other); + } + + bool operator <=(const T& other) const + { + return static_cast(mValue) <= static_cast(other); + } + + bool operator ==(const T& other) const + { + return static_cast(mValue) == static_cast(other); + } + + bool valid() const { return mValue != T::end; } + + operator T() const { return mValue; } + operator T&() { return mValue; } + T val() const { return mValue; } + +private: + T mValue; +}; diff --git a/Qt/better_radiobutton.cpp b/Qt/better_radiobutton.cpp new file mode 100644 index 0000000..d6339f0 --- /dev/null +++ b/Qt/better_radiobutton.cpp @@ -0,0 +1,38 @@ +#include "better_radiobutton.h" + +Better_Radiobutton::Better_Radiobutton(QWidget* parent) : +QWidget{parent}, +m_radiobuttons{} +{ +} + +Better_Radiobutton::Better_Radiobutton(std::vector names, QWidget* parent) : + QWidget{parent}, + m_v_layout{new QVBoxLayout(this)}, + m_radiobuttons{}, + m_checked{""} +{ + for (const auto& name : names) { + add_radiobutton(name); + } +} + +void Better_Radiobutton::add_radiobutton(QString name) +{ + auto radiobutton = new QRadioButton(name, this); + m_radiobuttons.push_back(radiobutton); + m_v_layout->addWidget(radiobutton); + + connect(radiobutton, &QRadioButton::toggled, this, [this, name, radiobutton] { + emit toggled(name); + if (radiobutton->isChecked()) { + m_checked = name; + emit checked(name); + } + }); +} + +QString Better_Radiobutton::isChecked() const +{ + return m_checked; +} diff --git a/Qt/better_radiobutton.h b/Qt/better_radiobutton.h new file mode 100644 index 0000000..90b3076 --- /dev/null +++ b/Qt/better_radiobutton.h @@ -0,0 +1,25 @@ +#ifndef BETTER_RADIOBUTTON_H +#define BETTER_RADIOBUTTON_H + +#include +#include + +class Better_Radiobutton : public QWidget { + Q_OBJECT + public: + explicit Better_Radiobutton(QWidget* parent = nullptr); + explicit Better_Radiobutton(std::vectornames, QWidget* partent = nullptr); + + void add_radiobutton(QString name); + QString isChecked() const; + + signals: + void toggled(QString name); + void checked(QString name); + private: + QVBoxLayout* m_v_layout; + std::vector m_radiobuttons; + QString m_checked; +}; + +#endif // BETTER_RADIOBUTTON_H diff --git a/Qt/helpers.cpp b/Qt/helpers.cpp new file mode 100644 index 0000000..c1b0eb9 --- /dev/null +++ b/Qt/helpers.cpp @@ -0,0 +1,102 @@ +#include "helpers.h" + +void create_dial_needle(QwtDial* dial) +{ + QwtDialSimpleNeedle* needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, Qt::yellow, + QColor( Qt::gray ).lighter( 130 ) ); + needle->setWidth(2); + dial->setNeedle( needle ); +} + +void set_thermo_color(QwtSlider* slider, QColor color, QColor bg) +{ + QString style = QString("#%1 { background-color: %2; color: %3;}").arg(slider->objectName(), bg.name(), color.name()); + + slider->setStyleSheet(style); +} + +void set_button_color(QPushButton* button, QColor color, QColor bg) +{ + QString style = QString("#%1 { background-color: %2; color: %3;}").arg(button->objectName(), bg.name(), color.name()); + + button->setStyleSheet(style); +} + +void set_button_color(QPushButton* button, QString color, QString bg) +{ + QString style = QString("#%1 { background-color: %2; color: %3;}").arg(button->objectName(), bg, color); + + button->setStyleSheet(style); +} + +QColor button_bg_color(QPushButton* button) +{ + std::string input = button->styleSheet().toStdString(); + QColor background_color; + + // Find the position of "background-color:" + size_t bg_pos = input.find("background-color:"); + if (bg_pos != std::string::npos) { + // Find the end position of the background color value + size_t bg_value_start = bg_pos + 17; // Length of "background-color:" is 17 + size_t bg_value_end = input.find(";", bg_value_start); + if (bg_value_end != std::string::npos) { + background_color = QColor(input.substr(bg_value_start, bg_value_end - bg_value_start).c_str()); + } + } + + return background_color; +} + +QColor button_color(QPushButton* button) +{ + std::string input = button->styleSheet().toStdString(); + QColor color; + + // Find the position of "color:" + size_t color_pos = input.find(" color:"); + if (color_pos != std::string::npos) { + // Find the end position of the text color value + size_t color_value_start = color_pos + 7; // Length of "color:" is 7 + size_t color_value_end = input.find(";", color_value_start); + if (color_value_end != std::string::npos) { + color = QColor(input.substr(color_value_start, color_value_end - color_value_start).c_str()); + } + } + return color; +} + +void reset_button_color(QPushButton* button) +{ + button->setStyleSheet(""); +} + +bool is_read_only(QWidget* widget) +{ + return widget->testAttribute(Qt::WA_TransparentForMouseEvents) || (widget->focusPolicy() == Qt::NoFocus); +} + +void set_read_only(QWidget* widget, bool read_only) +{ + widget->setAttribute(Qt::WA_TransparentForMouseEvents, read_only); + widget->setFocusPolicy(read_only ? Qt::NoFocus : Qt::StrongFocus); +} + +QString network_reply_to_fix(QNetworkReply::NetworkError error) +{ + switch (error) { + case QNetworkReply::NetworkError::ConnectionRefusedError: + return "Make sure the server is started ($ systemctl status unocan_server)"; + break; + case QNetworkReply::NetworkError::HostNotFoundError: + return "Make sure the simulator is turned on"; + break; + case QNetworkReply::NetworkError::InternalServerError: + return "Server had an oopsie, restart the client and if it fails again restart the server"; + break; + default: + return ""; + break; + } +} diff --git a/Qt/helpers.h b/Qt/helpers.h new file mode 100644 index 0000000..9daf2ba --- /dev/null +++ b/Qt/helpers.h @@ -0,0 +1,26 @@ +#ifndef HELPERS_H +#define HELPERS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void create_dial_needle(QwtDial* dial); + +void set_thermo_color(QwtSlider* slider, QColor color, QColor bg); +void set_button_color(QPushButton* button, QColor color, QColor bg); +void set_button_color(QPushButton* button, QString color, QString bg); +QColor button_bg_color(QPushButton* button); +QColor button_color(QPushButton* button); +void reset_button_color(QPushButton* button); +void set_read_only(QWidget* widget, bool readOnly = true); +bool is_read_only(QWidget* widget); +QString network_reply_to_fix(QNetworkReply::NetworkError error); + +#endif // HELPERS_H diff --git a/Qt/main.cpp b/Qt/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/Qt/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp new file mode 100644 index 0000000..a1e3e55 --- /dev/null +++ b/Qt/mainwindow.cpp @@ -0,0 +1,169 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "sim-scope_mux_tester.h" +#include "sim-can_debugger.h" +#include "sim-cvs_i10.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + api(new OpenAPI::OAICANApiApi()), + error_dialog(new NetworkError(this)), + sims{{"CANDebugger", new Can_Debugger(api, this)}, + {"Scope_Mux_Tester", new Scope_Mux_Tester(api, this)}, + {"CVS_I10", new CVS_I10(api, this)}} +{ + ui->setupUi(this); + this->setWindowTitle("Simulator Selector"); + connect(this, &MainWindow::quit, this, &QCoreApplication::quit, Qt::QueuedConnection); + + api->setNewServerForAllOperations( + //QUrl("http://localhost:1880"), + QUrl("https://cvs-i10.local/api"), + "No description provided", + QMap()); + api->apiCanapiSimsGet(); + + //Default sim + defaultSim("CVS_I10"); + + connect(ui->pushButton, &QPushButton::clicked, this, [=]{open_sim(ui->comboBox->currentText());}); +} + +MainWindow::~MainWindow() +{ + delete ui; + delete api; + delete error_dialog; +} + +void MainWindow::setup_cb(void) +{ + connect(api, &OpenAPI::OAICANApiApi::apiCanapiSimsGetSignal, this, [=](QList summary){Parse_Sims(summary);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiSimsGetSignalError, this, [=](QList summary, QNetworkReply::NetworkError error_type, const QString &error_str){Parse_Sims(summary, error_type, error_str);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimPutSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimPutSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){ + apiCanapiUielements_bool_cb(false, error_type, error_str);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimGetSignal, this, [this](OpenAPI::OAISimulatorInfo summary){apiCanapiCurrentsimGet_cb(summary);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimGetSignalError, this, [this](OpenAPI::OAISimulatorInfo summary, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo(), error_type, error_str);}); +} + +void MainWindow::showEvent( QShowEvent* event ) +{ + QWidget::showEvent( event ); + setup_cb(); +} + +void MainWindow::hideEvent(QHideEvent* event) +{ + QWidget::hideEvent(event); + disconnect(api, nullptr, nullptr, nullptr); +} + +void MainWindow::defaultSim(QString name) +{ + this->selected_sim_name = name; + this->default_sim = true; +} + +void MainWindow::Parse_Sims(QList sims, QNetworkReply::NetworkError error_type, const QString error_str) +{ + if (error_type) { + QD << "( " << error_type << "," << error_str << ")"; + error_dialog->set_error(error_str + "\n" + network_reply_to_fix(error_type)); + error_dialog->open(); + return; + } if (sims.empty()) { + QD << " got called with invalid data and no error!"; + error_dialog->set_error(QString(__FUNCTION__) + " got called with invalid data and no error!"); + error_dialog->open(); + return; + } + + for (size_t i = 0; i < sims.length(); i++) + { + ui->comboBox->addItem(sims[i]); + } + + QThread::msleep(30); + + if (this->default_sim) + { + ui->comboBox->setCurrentText(this->selected_sim_name); + open_sim(this->selected_sim_name); + } +} + +void MainWindow::open_sim(QString sim_name) +{ + try + { + selected_sim = sims.at(sim_name); + this->selected_sim_name = sim_name; + } + catch (const std::out_of_range& ex) + { + QD << "out_of_range::what(): " << ex.what(); + return; + } + + api->apiCanapiCurrentsimGet(); +} + +void MainWindow::open_sim_window(void) +{ + this->hide(); + selected_sim->show(); +} + +void MainWindow::apiCanapiUielements_bool_cb(bool output, QNetworkReply::NetworkError error_type, QString error_str) +{ + if (error_type) { + QD << "( " << Qt::hex << output << "," << error_type << "," << error_str << ")"; + error_dialog->set_error(error_str + "\n" + network_reply_to_fix(error_type)); + error_dialog->open(); + return; + } if (!output) { + QD << " got called with invalid data and no error!"; + error_dialog->set_error(QString(__FUNCTION__) + " got called with invalid data and no error!"); + error_dialog->open(); + return; + } + + open_sim_window(); +} + +void MainWindow::apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo summary, QNetworkReply::NetworkError error_type, QString error_str) +{ + if (error_type) { + QD << "( " << summary.asJson() << "," << error_type << "," << error_str << ")"; + error_dialog->set_error(error_str + "\n" + network_reply_to_fix(error_type)); + error_dialog->open(); + return; + } if (!summary.isValid()) { + QD << " got called with invalid data and no error!"; + error_dialog->set_error(QString(__FUNCTION__) + " got called with invalid data and no error!"); + error_dialog->open(); + return; + } + + if (!summary.is_name_Set()) + { + QD << "Name not set"; + api->apiCanapiCurrentsimPut(selected_sim_name); + return; + } + + QD << "Current sim:" << summary.getName(); + + if (summary.getName() == selected_sim_name) + { + QD << "Name set and right"; + open_sim_window(); + return; + } + + QD << "Name set and wrong"; + api->apiCanapiCurrentsimPut(selected_sim_name); +} diff --git a/Qt/mainwindow.h b/Qt/mainwindow.h new file mode 100644 index 0000000..fe5e26b --- /dev/null +++ b/Qt/mainwindow.h @@ -0,0 +1,47 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "OAICANApiApi.h" +#include "networkerror.h" +#include "simulatorbase.h" +#include "helpers.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + void defaultSim(QString name); + void Parse_Sims(QList sims, QNetworkReply::NetworkError error_type = QNetworkReply::NetworkError::NoError, QString error_str = QString()); + void apiCanapiUielements_bool_cb(bool ret, QNetworkReply::NetworkError error_type = QNetworkReply::NetworkError::NoError, QString error_str = QString()); + void apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo summary, QNetworkReply::NetworkError error_type = QNetworkReply::NetworkError::NoError, QString error_str = QString()); + void open_sim_window(void); + void open_sim(QString sim_name); + void setup_cb(void); + + signals: + void quit(void); + private: + void showEvent( QShowEvent* event ); + void hideEvent(QHideEvent* event); + + bool default_sim = false; + QString selected_sim_name; + QMainWindow* selected_sim; + Ui::MainWindow *ui; + OpenAPI::OAICANApiApi *api; + NetworkError* error_dialog; + + std::map sims; +}; + +#endif // MAINWINDOW_H diff --git a/Qt/mainwindow.ui b/Qt/mainwindow.ui new file mode 100644 index 0000000..c2a5724 --- /dev/null +++ b/Qt/mainwindow.ui @@ -0,0 +1,43 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + + + + + Open + + + + + + + + + + + test + + + + + + diff --git a/Qt/networkerror.cpp b/Qt/networkerror.cpp new file mode 100644 index 0000000..eee256a --- /dev/null +++ b/Qt/networkerror.cpp @@ -0,0 +1,19 @@ +#include "networkerror.h" +#include "ui_networkerror.h" + +NetworkError::NetworkError(QWidget* parent) : QDialog(parent), ui(new Ui::NetworkError) { + ui->setupUi(this); + connect(ui->exit, &QPushButton::clicked, this, &QCoreApplication::quit, Qt::QueuedConnection); + connect(ui->close, &QPushButton::clicked, this, [=]{this->close();}); + + this->setWindowTitle("Error"); +} + +void NetworkError::set_error(QString error) +{ + ui->Error->setText(error); +} + +NetworkError::~NetworkError() { + delete ui; +} diff --git a/Qt/networkerror.h b/Qt/networkerror.h new file mode 100644 index 0000000..08edfb5 --- /dev/null +++ b/Qt/networkerror.h @@ -0,0 +1,22 @@ +#ifndef NETWORKERROR_H +#define NETWORKERROR_H + +#include + +namespace Ui { +class NetworkError; +} + +class NetworkError : public QDialog { + Q_OBJECT + + public: + explicit NetworkError(QWidget* parent = nullptr); + void set_error(QString error); + ~NetworkError(); + + private: + Ui::NetworkError* ui; +}; + +#endif // NETWORKERROR_H diff --git a/Qt/networkerror.ui b/Qt/networkerror.ui new file mode 100644 index 0000000..a114f4c --- /dev/null +++ b/Qt/networkerror.ui @@ -0,0 +1,69 @@ + + + NetworkError + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + 16 + true + + + + Oops, an error occured + + + Qt::AlignCenter + + + + + + + Error... + + + Qt::AlignCenter + + + + + + + + + Close + + + + + + + Exit Application + + + + + + + + + + + + diff --git a/Qt/qwt b/Qt/qwt new file mode 160000 index 0000000..aab5a99 --- /dev/null +++ b/Qt/qwt @@ -0,0 +1 @@ +Subproject commit aab5a9927cc2fc2b865ef64a8e3d8c1f7f86fd04 diff --git a/Qt/sim-can_debugger.cpp b/Qt/sim-can_debugger.cpp new file mode 100644 index 0000000..9e1946c --- /dev/null +++ b/Qt/sim-can_debugger.cpp @@ -0,0 +1,13 @@ +#include "sim-can_debugger.h" +#include "ui_sim-can_debugger.h" + +Can_Debugger::Can_Debugger(OpenAPI::OAICANApiApi* api, QWidget *parent) : + SimulatorBase(api, parent), + ui(new Ui::Can_Debugger) { + ui->setupUi(this); + this->setWindowTitle("CAN Debugger"); +} + +Can_Debugger::~Can_Debugger() { + delete ui; +} diff --git a/Qt/sim-can_debugger.h b/Qt/sim-can_debugger.h new file mode 100644 index 0000000..9aa28a4 --- /dev/null +++ b/Qt/sim-can_debugger.h @@ -0,0 +1,22 @@ +#ifndef SIM_CAN_DEBUGGER_H +#define SIM_CAN_DEBUGGER_H + +#include +#include "simulatorbase.h" + +namespace Ui { +class Can_Debugger; +} + +class Can_Debugger : public SimulatorBase { + Q_OBJECT + + public: + explicit Can_Debugger(OpenAPI::OAICANApiApi* api, QWidget *parent = nullptr); + ~Can_Debugger(); + + private: + Ui::Can_Debugger* ui; +}; + +#endif // SIM_CAN_DEBUGGER_H diff --git a/Qt/sim-can_debugger.ui b/Qt/sim-can_debugger.ui new file mode 100644 index 0000000..34f1596 --- /dev/null +++ b/Qt/sim-can_debugger.ui @@ -0,0 +1,31 @@ + + + Can_Debugger + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 0 + 0 + 800 + 23 + + + + + + + + diff --git a/Qt/simulatorbase.cpp b/Qt/simulatorbase.cpp new file mode 100644 index 0000000..0a11eef --- /dev/null +++ b/Qt/simulatorbase.cpp @@ -0,0 +1,388 @@ +#include "simulatorbase.h" + +SimulatorBase::SimulatorBase(OpenAPI::OAICANApiApi* api, QWidget* parent) : + QMainWindow{parent}, + error_dialog(new NetworkError(this)) +{ + this->api = api; + timer_update = new QTimer(this); + timer_update->connect(timer_update, &QTimer::timeout, this, [=]{sim_update();}); + refresh_rate = 100; +} + +void SimulatorBase::sim_update(void) +{ + api->apiCanapiCurrentsimGet(); +} + +void SimulatorBase::showEvent( QShowEvent* event ) +{ + QWidget::showEvent( event ); + QD << "Starting refresh timer"; + setup_cb(); + this->timer_update->start(refresh_rate); + closed = false; +} + +void SimulatorBase::closeEvent(QCloseEvent* event) +{ + if (!closed) + { + QD << "Disconnecting callbacks"; + QWidget::closeEvent(event); + this->timer_update->stop(); + disconnect(api, nullptr, nullptr, nullptr); + this->parentWidget()->show(); + } + closed = true; +} + +void SimulatorBase::setup_cb(void) +{ + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsPushbuttonPostSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsPushbuttonPostSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiUielements_bool_cb(false, false, error_type, error_str);}); + + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsPushbuttonarrayPostSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsPushbuttonarrayPostSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiUielements_bool_cb(false, false, error_type, error_str);}); + + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsTextboxPostSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsTextboxPostSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiUielements_bool_cb(false, false, error_type, error_str);}); + + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsSliderPostSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsSliderPostSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiUielements_bool_cb(false, false, error_type, error_str);}); + + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsTogglearrayPostSignal, this, [this](bool output){apiCanapiUielements_bool_cb(output);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiUielementsTogglearrayPostSignalError, this, [this](bool output, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiUielements_bool_cb(false, false, error_type, error_str);}); + + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimGetSignal, this, [this](OpenAPI::OAISimulatorInfo summary){apiCanapiCurrentsimGet_cb(summary);}); + connect(api, &OpenAPI::OAICANApiApi::apiCanapiCurrentsimGetSignalError, this, [this](OpenAPI::OAISimulatorInfo summary, QNetworkReply::NetworkError error_type, QString error_str){apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo(), error_type, error_str);}); +} + +void SimulatorBase::apiCanapiUielements_bool_cb(bool output, bool currentsim, QNetworkReply::NetworkError error_type, QString error_str) +{ + if (error_type) { + QD << "( " << Qt::hex << output << "," << error_type << "," << error_str << ")"; + error_dialog->set_error(error_str + "\n" + network_reply_to_fix(error_type)); + error_dialog->open(); + return; + } if (!output) { + QD << " got called with invalid data and no error!"; + error_dialog->set_error(QString(__FUNCTION__) + " got called with invalid data and no error!"); + error_dialog->open(); + return; + } +} + +void SimulatorBase::apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo summary, QNetworkReply::NetworkError error_type, QString error_str) +{ + if (error_type) { + QD << "( " << summary.asJson() << "," << error_type << "," << error_str << ")"; + error_dialog->set_error(error_str + "\n" + network_reply_to_fix(error_type)); + error_dialog->open(); + return; + } if (!summary.isValid()) { + QD << " got called with invalid data and no error!"; + error_dialog->set_error(QString(__FUNCTION__) + " got called with invalid data and no error!"); + error_dialog->open(); + return; + } + OAISimulatorInfo_parser(summary); +} + +void SimulatorBase::OAISimulatorInfo_parser(OpenAPI::OAISimulatorInfo input) +{ + const OpenAPI::OAIClientUIItem* uiItem; + + for (size_t i = 0; i < input.getUiItems().count(); i++) + { + uiItem = &(input.getUiItems().at(i)); + + if (uiItem->getType() == "UIButtonArray") + { + process_ui_button_array(uiItem); + continue; + } + if (uiItem->getType() == "UILedArray") + { + process_ui_led_array(uiItem); + continue; + } + if (uiItem->getType() == "UISlider") + { + process_ui_slider(uiItem); + continue; + } + if (uiItem->getType() == "UITextbox") + { + process_ui_textbox(uiItem); + continue; + } + if (uiItem->getType() == "UILabel") + { + process_ui_label(uiItem); + continue; + } + if (uiItem->getType() == "UICombobox") + { + process_ui_combobox(uiItem); + continue; + } + if (uiItem->getType() == "UIRadioButton") + { + process_ui_radiobutton(uiItem); + continue; + } + } +} + +void SimulatorBase::process_ui_button_array(const OpenAPI::OAIClientUIItem* uiItem) +{ + QJsonObject state = uiItem->getState().asJsonObject(); + QJsonArray state_array = state["buttonArray"].toArray(); + for (size_t i = 0; i < state_array.count(); i++) + { + process_ui_button_array_element(state_array[i].toObject()); + } +} + +void SimulatorBase::process_ui_led_array(const OpenAPI::OAIClientUIItem* uiItem) +{ + QJsonObject state = uiItem->getState().asJsonObject(); + QJsonArray state_array = state["ledArray"].toArray(); + for (size_t i = 0; i < state_array.count(); i++) + { + process_ui_led_array_element(state_array[i].toObject()); + } +} + +void SimulatorBase::process_ui_label(const OpenAPI::OAIClientUIItem* uiItem) +{ + QLabel* label = id_to_label(uiItem->getName()); + if (label == nullptr) + { + QD << "id_to_label returned null on " << uiItem->getName(); + return; + } + + QString value = uiItem->getState().asJsonObject()["textString"].toString(); + + if (label->text() != value) + { + label->setText(value); + } +} + +void SimulatorBase::process_ui_slider(const OpenAPI::OAIClientUIItem* uiItem) +{ + if (uiItem->getName().startsWith('t')) + { + QwtSlider* slider = id_to_slider(uiItem->getName()); + QLabel* label = id_to_slider_label(uiItem->getName()); + if (slider == nullptr) + { + QD << "id_to_slider returned null on " << uiItem->getName(); + return; + } + + double value = uiItem->getState().asJsonObject()["currentValue"].toDouble(); + bool enable = uiItem->getState().asJsonObject()["enable"].toBool(); + bool visible = uiItem->getState().asJsonObject()["visible"].toBool(); + + if (slider->value() != value) + { + if (label != nullptr) + { + label->setText(format_slider_value(uiItem->getName(), value)); + } + slider->setValue(value); + } + + if (slider->isReadOnly() != !enable) + { + slider->setReadOnly(!enable); + } + + if (slider->isVisible() != visible) + { + slider->setVisible(visible); + } + //TODO: Add color for scale + return; + } + + if (uiItem->getName().startsWith('d')) + { + QwtDial* dial = id_to_dial(uiItem->getName()); + QLabel* label = id_to_dial_label(uiItem->getName()); + if (dial == nullptr) + { + QD << "id_to_dial returned null on " << uiItem->getName(); + return; + } + + double value = uiItem->getState().asJsonObject()["currentValue"].toDouble(); + bool visible = uiItem->getState().asJsonObject()["visible"].toBool(); + + if (dial->value() != value) + { + if (label != nullptr) + { + label->setText(format_dial_value(uiItem->getName(), value)); + } + dial->setValue(value); + } + + if (dial->isVisible() != visible) + { + dial->setVisible(visible); + } + return; + } +} + +void SimulatorBase::process_ui_textbox(const OpenAPI::OAIClientUIItem* uiItem) +{ + QD << "Not implemented"; + emit quit(); +} + +void SimulatorBase::process_ui_combobox(const OpenAPI::OAIClientUIItem* uiItem) +{ + QComboBox* combobox = id_to_combobox(uiItem->getName()); + if (combobox == nullptr) + { + QD << "id_to_combobox returned null on " << uiItem->getName(); + return; + } + + QJsonObject state = uiItem->getState().asJsonObject(); + QString selected = state["selected"].toString(); + QJsonArray toggleArray = state["toggleArray"].toArray(); + + + for (size_t i = 0; i < toggleArray.count(); i++) + { + QString item = toggleArray[i].toObject()["id"].toString(); + if (combobox->findText(item) == -1) + combobox->addItem(item); + } + + if (combobox->currentText() != selected) + { + combobox->setCurrentText(selected); + } +} + +void SimulatorBase::process_ui_radiobutton(const OpenAPI::OAIClientUIItem* uiItem) +{ + QD << "Not implemented"; + emit quit(); +} + +void SimulatorBase::process_ui_button_array_element(QJsonObject jsonObject) +{ + //TODO: fix bgcolor and color + QPushButton* button = id_to_button(jsonObject["id"].toString()); + + if (button == nullptr) + { + QD << "id_to_button returned null on " << jsonObject["id"].toString(); + return; + } + + QColor bg_color = QColor(jsonObject["color"].toString()); + QString text = jsonObject["title"].toString(); + bool enabled = jsonObject["enable"].toBool(); + + if (button->text() != text) + { + button->setText(text); + } + + if (button->isEnabled() != enabled) + { + button->setEnabled(enabled); + } + + if (bg_color == QColor(0x83, 0x91, 0x92)) + { + reset_button_color(button); + return; + } + + if (bg_color == QColor(0xFF, 0x00, 0x00)) + { + set_button_color(button, QString("black"), QString("darkred")); + return; + } + + if (bg_color == QColor(0x00, 0xFF, 0x00)) + { + set_button_color(button, QString("white"), QString("darkgreen")); + return; + } + + if (bg_color == QColor(0xFF8800)) + { + set_button_color(button, QString("darkred"), QString("darkorange")); + return; + } + + if (bg_color != button_bg_color(button)) + { + set_button_color(button, QColor(), bg_color); + } +} + +void SimulatorBase::process_ui_led_array_element(QJsonObject jsonObject) +{ + QPushButton* led = id_to_led(jsonObject["id"].toString()); + if (led == nullptr) + { + QD << "id_to_led returned null on " << jsonObject["id"].toString(); + return; + } + + QColor bg_color = QColor(jsonObject["state"].toString()); + QColor color = QColor(~bg_color.red(), ~bg_color.green(), ~bg_color.blue()); + QString text = jsonObject["title"].toString(); + + if (false == is_read_only(led)) + { + set_read_only(led); + } + + if (text != led->text()) + { + led->setText(text); + } + + if (bg_color == QColor(0x83, 0x91, 0x92)) + { + reset_button_color(led); + return; + } + + if (bg_color == QColor(0xFF, 0x00, 0x00)) + { + set_button_color(led, QString("black"), QString("darkred")); + return; + } + + if (bg_color == QColor(0x00, 0xFF, 0x00)) + { + set_button_color(led, QString("white"), QString("darkgreen")); + return; + } + + if (bg_color == QColor(0xFF8800)) + { + set_button_color(led, QString("darkred"), QString("darkorange")); + return; + } + + if (bg_color != button_bg_color(led)) + { + set_button_color(led, color, bg_color); + } +} diff --git a/Qt/simulatorbase.h b/Qt/simulatorbase.h new file mode 100644 index 0000000..ecbc8b9 --- /dev/null +++ b/Qt/simulatorbase.h @@ -0,0 +1,60 @@ +#ifndef SIMULATORBASE_H +#define SIMULATORBASE_H + +#include +#include +#include "OAICANApiApi.h" +#include "networkerror.h" +#include "helpers.h" +#include +#include "IterableEnum.hpp" + +#define QD qDebug() << "Debug:" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":" + +class SimulatorBase : public QMainWindow { + Q_OBJECT + public: + explicit SimulatorBase(OpenAPI::OAICANApiApi* api, QWidget* parent = nullptr); + void setup_cb(void); + void sim_update(void); + virtual QLabel* id_to_label(QString name) {return nullptr;} + virtual QPushButton* id_to_led(QString name) {return nullptr;} + virtual QPushButton* id_to_button(QString name) {return nullptr;} + virtual QwtSlider* id_to_slider(QString name) {return nullptr;} + virtual QLabel* id_to_slider_label(QString name) {return nullptr;} + virtual QString format_slider_value(QString name, double val) {return QString();} + virtual QwtDial* id_to_dial(QString name) {return nullptr;} + virtual QLabel* id_to_dial_label(QString name) {return nullptr;} + virtual QString format_dial_value(QString name, double val) {return QString();} + virtual QComboBox* id_to_combobox(QString name) {return nullptr;} + virtual QTextEdit* id_to_textbox(QString name) {return nullptr;} + + signals: + void quit(); + protected: + void apiCanapiUielements_bool_cb(bool ret, bool currentsim = false, QNetworkReply::NetworkError error_type = QNetworkReply::NetworkError::NoError, QString error_str = QString()); + void apiCanapiCurrentsimGet_cb(OpenAPI::OAISimulatorInfo output, QNetworkReply::NetworkError error_type = QNetworkReply::NetworkError::NoError, QString error_str = QString()); + + void OAISimulatorInfo_parser(OpenAPI::OAISimulatorInfo input); + void process_ui_button_array(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_led_array(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_label(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_slider(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_textbox(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_combobox(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_radiobutton(const OpenAPI::OAIClientUIItem* uiItem); + void process_ui_button_array_element(QJsonObject jsonObject); + void process_ui_led_array_element(QJsonObject jsonObject); + + void showEvent(QShowEvent* event); + void closeEvent(QCloseEvent* event); + + OpenAPI::OAICANApiApi* api; + + QTimer* timer_update; + uint32_t refresh_rate; + NetworkError* error_dialog; + bool closed = false; +}; + +#endif // SIMULATORBASE_H