Updated submodule client/qwt
This commit is contained in:
2026-02-02 14:29:44 +01:00
parent 39ee4ff9a2
commit d798d463f1
32 changed files with 0 additions and 2 deletions
+127
View File
@@ -0,0 +1,127 @@
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)
FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
FetchContent_MakeAvailable(json)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets Svg WebSockets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Svg WebSockets)
add_subdirectory(qwt)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
set(PROJECT_SOURCES
simulators/sim-cvs_i10.h
simulators/sim-cvs_i10.cpp
simulators/sim-cvs_i10.ui
simulators/sim-can_debugger.h
simulators/sim-can_debugger.cpp
simulators/sim-can_debugger.ui
simulators/sim-scope_mux_tester.h
simulators/sim-scope_mux_tester.cpp
simulators/sim-scope_mux_tester.ui
simulators/sim-test.h
simulators/sim-test.cpp
simulators/sim-test.ui
simulator_base.h
simulator_base.cpp
helpers.h
helpers.cpp
IterableEnum.hpp
hex_spinbox.h
hex_spinbox.cpp
better_radiobutton.h
better_radiobutton.cpp
led.h
led.cpp
can_transceive.h
can_transceive.cpp
can_transceive.ui
networkerror.h
networkerror.cpp
networkerror.ui
web_socket_wrapper.h
web_socket_wrapper.cpp
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
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/simulators
${PROJECT_BINARY_DIR}/
)
target_link_libraries(Simulator-CVS_I10 PRIVATE qwt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WebSockets magic_enum::magic_enum nlohmann_json::nlohmann_json)
# 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()
+120
View File
@@ -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 T>
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<T>(static_cast<int>(mValue) + 1);
return *this;
}
Iterable operator++(int)
{
Iterable prev = *this;
mValue = static_cast<T>(static_cast<int>(mValue) + 1);
return prev;
}
Iterable& operator--()
{
assert(mValue >= T::end && mValue < T::begin);
mValue = static_cast<T>(static_cast<int>(mValue) - 1);
return *this;
}
Iterable operator--(int)
{
Iterable prev = *this;
mValue = static_cast<T>(static_cast<int>(mValue) - 1);
return prev;
}
Iterable operator+(int i)
{
return static_cast<T>(static_cast<int>(mValue) + i);
}
Iterable operator+(T i)
{
return static_cast<T>(static_cast<int>(mValue) + static_cast<int>(i));
}
Iterable operator-(int i)
{
return static_cast<T>(static_cast<int>(mValue) - i);
}
Iterable operator-(T i)
{
return static_cast<T>(static_cast<int>(mValue) - static_cast<int>(i));
}
bool operator >(const T& other) const
{
return static_cast<int>(mValue) > static_cast<int>(other);
}
bool operator >=(const T& other) const
{
return static_cast<int>(mValue) >= static_cast<int>(other);
}
bool operator <(const T& other) const
{
return static_cast<int>(mValue) < static_cast<int>(other);
}
bool operator <=(const T& other) const
{
return static_cast<int>(mValue) <= static_cast<int>(other);
}
bool operator ==(const T& other) const
{
return static_cast<int>(mValue) == static_cast<int>(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;
};
+38
View File
@@ -0,0 +1,38 @@
#include "better_radiobutton.h"
Better_Radiobutton::Better_Radiobutton(QWidget* parent) :
QWidget{parent},
m_radiobuttons{}
{
}
Better_Radiobutton::Better_Radiobutton(std::vector<QString> 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;
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef BETTER_RADIOBUTTON_H
#define BETTER_RADIOBUTTON_H
#include <QVBoxLayout>
#include <QRadioButton>
class Better_Radiobutton : public QWidget {
Q_OBJECT
public:
explicit Better_Radiobutton(QWidget* parent = nullptr);
explicit Better_Radiobutton(std::vector<QString>names, 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<QRadioButton*> m_radiobuttons;
QString m_checked;
};
#endif // BETTER_RADIOBUTTON_H
+73
View File
@@ -0,0 +1,73 @@
#include "can_transceive.h"
#include "ui_can_transceive.h"
Can_Transceive::Can_Transceive(QWidget* parent) : QWidget(parent), ui(new Ui::Can_Transceive) {
ui->setupUi(this);
can_send_hexspinboxes = {
ui->s_can_send_data0,
ui->s_can_send_data1,
ui->s_can_send_data2,
ui->s_can_send_data3,
ui->s_can_send_data4,
ui->s_can_send_data5,
ui->s_can_send_data6,
ui->s_can_send_data7,
};
enable_can_send_data();
connect(ui->c_can_send_dlc, &QComboBox::currentIndexChanged, this, [=, this]{enable_can_send_data();});
connect(ui->b_can_send_send, &QPushButton::clicked, this, [=, this]{emit send_can_message(ui->s_can_send_sid->value(), ui->c_can_send_dlc->currentIndex(), std::array<uint8_t, 8>({(uint8_t)(can_send_hexspinboxes[0]->value()), (uint8_t)(can_send_hexspinboxes[1]->value()),(uint8_t)(can_send_hexspinboxes[2]->value()),(uint8_t)(can_send_hexspinboxes[3]->value()),(uint8_t)(can_send_hexspinboxes[4]->value()),(uint8_t)(can_send_hexspinboxes[5]->value()),(uint8_t)(can_send_hexspinboxes[6]->value()),(uint8_t)(can_send_hexspinboxes[7]->value())}));});
connect(ui->b_clear, &QPushButton::clicked, this, [=, this]{emit can_clear();});
}
Can_Transceive::~Can_Transceive() {
delete ui;
}
void Can_Transceive::enable_can_send_data()
{
uint8_t selected = atoi(ui->c_can_send_dlc->currentText().toLocal8Bit());
for (size_t i = 0; i < can_send_hexspinboxes.size(); i++)
{
can_send_hexspinboxes[i]->setEnabled(i < selected);
if (i >= selected)
{
can_send_hexspinboxes[i]->setValue(0);
}
}
}
void Can_Transceive::clear()
{
ui->recvd_list->setRowCount(0);
ui->send_list->setRowCount(0);
}
void Can_Transceive::add_send_item(uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload)
{
size_t rc = ui->send_list->rowCount();
ui->send_list->setRowCount(rc + 1);
ui->send_list->setItem(rc, 0, new QTableWidgetItem(QString().setNum(id, 16)));
ui->send_list->setItem(rc, 1, new QTableWidgetItem(QString("%0").arg(dlc)));
for (size_t i = 0; i < 8; i++)
{
ui->send_list->setItem(rc, 2 + i, new QTableWidgetItem(QString().setNum(payload[i], 16)));
}
ui->send_list->resizeColumnsToContents();
}
void Can_Transceive::add_receive_item(uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload)
{
size_t rc = ui->recvd_list->rowCount();
ui->recvd_list->setRowCount(rc + 1);
ui->recvd_list->setItem(rc, 0, new QTableWidgetItem(QString().setNum(id, 16)));
ui->recvd_list->setItem(rc, 1, new QTableWidgetItem(QString("%0").arg(dlc)));
for (size_t i = 0; i < 8; i++)
{
ui->recvd_list->setItem(rc, 2 + i, new QTableWidgetItem(QString().setNum(payload[i], 16)));
}
ui->recvd_list->resizeColumnsToContents();
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef CAN_TRANSCEIVE_H
#define CAN_TRANSCEIVE_H
#include <QWidget>
#include "hex_spinbox.h"
namespace Ui {
class Can_Transceive;
}
class Can_Transceive : public QWidget {
Q_OBJECT
public:
explicit Can_Transceive(QWidget* parent = nullptr);
~Can_Transceive();
void enable_can_send_data();
void clear();
void add_send_item(uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload);
void add_receive_item(uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload);
signals:
void send_can_message(uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload);
void can_clear();
private:
Ui::Can_Transceive* ui;
std::vector<HexSpinBox*> can_send_hexspinboxes;
};
#endif // CAN_TRANSCEIVE_H
+359
View File
@@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Can_Transceive</class>
<widget class="QWidget" name="Can_Transceive">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>713</width>
<height>562</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Received CAN messages</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTableWidget" name="recvd_list">
<column>
<property name="text">
<string>SID</string>
</property>
</column>
<column>
<property name="text">
<string>DLC</string>
</property>
</column>
<column>
<property name="text">
<string>Data 0</string>
</property>
</column>
<column>
<property name="text">
<string>Data 1</string>
</property>
</column>
<column>
<property name="text">
<string>Data 2</string>
</property>
</column>
<column>
<property name="text">
<string>Data 3</string>
</property>
</column>
<column>
<property name="text">
<string>Data 4</string>
</property>
</column>
<column>
<property name="text">
<string>Data 5</string>
</property>
</column>
<column>
<property name="text">
<string>Data 6</string>
</property>
</column>
<column>
<property name="text">
<string>Data 7</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_8">
<property name="title">
<string>Send CAN message</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="1" column="1">
<widget class="QComboBox" name="c_can_send_dlc">
<item>
<property name="text">
<string>0</string>
</property>
</item>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="1" column="10">
<widget class="QPushButton" name="b_can_send_send">
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="HexSpinBox" name="s_can_send_data1">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="HexSpinBox" name="s_can_send_sid">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="HexSpinBox" name="s_can_send_data0">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>SID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>DLC</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Data 0</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Data 1</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Data 2</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Data 3</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Data 4</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Data 5</string>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Data 6</string>
</property>
</widget>
</item>
<item row="0" column="9">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Data 7</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="HexSpinBox" name="s_can_send_data2">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="HexSpinBox" name="s_can_send_data3">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="HexSpinBox" name="s_can_send_data4">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="HexSpinBox" name="s_can_send_data5">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="HexSpinBox" name="s_can_send_data6">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="1" column="9">
<widget class="HexSpinBox" name="s_can_send_data7">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Send CAN messages</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QTableWidget" name="send_list">
<column>
<property name="text">
<string>SID</string>
</property>
</column>
<column>
<property name="text">
<string>DLC</string>
</property>
</column>
<column>
<property name="text">
<string>Data 0</string>
</property>
</column>
<column>
<property name="text">
<string>Data 1</string>
</property>
</column>
<column>
<property name="text">
<string>Data 2</string>
</property>
</column>
<column>
<property name="text">
<string>Data 3</string>
</property>
</column>
<column>
<property name="text">
<string>Data 4</string>
</property>
</column>
<column>
<property name="text">
<string>Data 5</string>
</property>
</column>
<column>
<property name="text">
<string>Data 6</string>
</property>
</column>
<column>
<property name="text">
<string>Data 7</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="b_clear">
<property name="text">
<string>clear buffers</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HexSpinBox</class>
<extends>QSpinBox</extends>
<header>hex_spinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+84
View File
@@ -0,0 +1,84 @@
#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_widget_color(QWidget* widget, QColor color, QColor bg)
{
QString style = QString("#%1 { background-color: %2; color: %3;}").arg(widget->objectName(), bg.name(), color.name());
widget->setStyleSheet(style);
}
void set_widget_color(QWidget* widget, QString color, QString bg)
{
QString style = QString("#%1 { background-color: %2; color: %3;}").arg(widget->objectName(), bg, color);
widget->setStyleSheet(style);
}
QColor widget_bg_color(QWidget* widget)
{
std::string input = widget->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 widget_color(QWidget* widget)
{
std::string input = widget->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_widget_color(QWidget* widget)
{
widget->setStyleSheet("");
}
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;
}
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef HELPERS_H
#define HELPERS_H
#include <QtWidgets>
#include <qwt_color_map.h>
#include <qwt_dial.h>
#include <qwt_dial_needle.h>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_slider.h>
#include <qwt_series_data.h>
#include <qwt_thermo.h>
#include <QNetworkReply>
void create_dial_needle(QwtDial* dial);
void set_widget_color(QWidget* widget, QColor color, QColor bg);
void set_widget_color(QWidget* widget, QString color, QString bg);
QColor widget_bg_color(QWidget* widget);
QColor widget_color(QWidget* widget);
void reset_widget_color(QWidget* widget);
QString network_reply_to_fix(QNetworkReply::NetworkError error);
#endif // HELPERS_H
+44
View File
@@ -0,0 +1,44 @@
#include "hex_spinbox.h"
HexSpinBox::HexSpinBox(QWidget *parent,
bool showPrefix,
const QString &format) :
QSpinBox(parent),
format(format)
{
// Validates hex strings up to 8 chars with or w/out leading "0x" prefix.
// For arbitrary prefix/suffix, the regex could be built dynamically
// in validate(), or override setPrefix()/setSuffix() methods.
const QRegularExpression rx("(?:0[xX])?[0-9A-Fa-f]{1,8}");
validator = new QRegularExpressionValidator(rx, this);
setShowPrefix(showPrefix);
}
void HexSpinBox::setShowPrefix(bool show)
{
if (show)
setPrefix(QStringLiteral("0x"));
else
setPrefix(QString());
}
void HexSpinBox::setFormat(const QString &text)
{
format = text;
this->lineEdit()->setText(textFromValue(value()));
}
QValidator::State HexSpinBox::validate(QString &text, int &pos) const
{
return validator->validate(text, pos);
}
int HexSpinBox::valueFromText(const QString &text) const
{
return text.toInt(0, 16);
}
QString HexSpinBox::textFromValue(int value) const
{
return QString().asprintf(qPrintable(format), value);
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef _HEXSPINBOX_H_
#define _HEXSPINBOX_H_
#include <QSpinBox>
#include <QLineEdit>
#include <QRegularExpressionValidator>
// NOTE: Since QSpinBox uses int as the storage type, the effective editing range
// is +/- 0x7FFF FFFF, so it can't handle a full unsigned int.
// QDoubleSpinBox would be a more suitable base class if a wider range is needed.
class HexSpinBox : public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = nullptr,
bool showPrefix = false,
const QString &format = QStringLiteral("%X"));
public slots:
void setShowPrefix(bool show);
void setFormat(const QString &text);
protected:
QValidator::State validate(QString &text, int &pos) const override;
int valueFromText(const QString &text) const override;
QString textFromValue(int value) const override;
private:
QRegularExpressionValidator *validator;
QString format;
};
#endif // _HEXSPINBOX_H_
+33
View File
@@ -0,0 +1,33 @@
#include "led.h"
Led::Led(QWidget* parrent)
: QPushButton(parrent)
{
set_read_only(this, true);
}
Led::Led(const QString &text, QWidget *parent)
{
set_read_only(this, true);
}
Led::Led(const QIcon& icon, const QString &text, QWidget *parent)
{
set_read_only(this, true);
}
Led::~Led()
{
}
bool Led::is_read_only(QWidget* widget)
{
return widget->testAttribute(Qt::WA_TransparentForMouseEvents) || (widget->focusPolicy() == Qt::NoFocus);
}
void Led::set_read_only(QWidget* widget, bool read_only)
{
widget->setAttribute(Qt::WA_TransparentForMouseEvents, read_only);
widget->setFocusPolicy(read_only ? Qt::NoFocus : Qt::StrongFocus);
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef LED_H
#define LED_H
#include "QPushButton"
class Led : public QPushButton {
Q_OBJECT
public:
explicit Led(QWidget *parent = nullptr);
explicit Led(const QString &text, QWidget *parent = nullptr);
Led(const QIcon& icon, const QString &text, QWidget *parent = nullptr);
~Led();
private:
bool is_read_only(QWidget* widget);
void set_read_only(QWidget* widget, bool read_only);
};
#endif // LED_H
+31
View File
@@ -0,0 +1,31 @@
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QCommandLineParser parser;
parser.setApplicationDescription("sim-us client");
parser.addHelpOption();
QCommandLineOption hostOption(QStringList() << "s"
<< "server",
QCoreApplication::translate("main", "sim-us server host [default: localhost]."),
QCoreApplication::translate("main", "host"), QLatin1String("localhost"));
parser.addOption(hostOption);
QCommandLineOption portOption(QStringList() << "p"
<< "port",
QCoreApplication::translate("main", "Port for echoserver [default: 9002]."),
QCoreApplication::translate("main", "port"), QLatin1String("9002"));
parser.addOption(portOption);
parser.process(a);
QString host = parser.value(hostOption);
uint16_t port = parser.value(portOption).toUShort();
MainWindow w(host, port);
w.show();
return a.exec();
}
+140
View File
@@ -0,0 +1,140 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "sim-scope_mux_tester.h"
#include "sim-can_debugger.h"
#include "sim-cvs_i10.h"
#include "sim-test.h"
MainWindow::MainWindow(QString host, uint16_t port, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_web_socket(new Web_socket_wrapper(QUrl("ws://" + host + ":" + QString::number(port)), this)),
m_error_dialog(new NetworkError(this)),
m_sims{{"Can_Debugger", new Can_Debugger(m_web_socket, this)},
{"Scope_Mux_Tester", new Scope_Mux_Tester(m_web_socket, this)},
{"CVS_I10", new CVS_I10(m_web_socket, this)},
{"Test_Sim", new Test_Sim(m_web_socket, this)}}
{
ui->setupUi(this);
this->setWindowTitle("Simulator Selector");
m_error_dialog->set_error("Connection timed out\nCheck if the server is running");
ui->connection->setText("Connected to ws://" + host + ":" + QString::number(port));
//Default sim
//defaultSim("CVS_I10");
connect(ui->pushButton, &QPushButton::clicked, this, [=, this]{open_sim(ui->comboBox->currentText());});
connect(this, &MainWindow::quit, this, &QCoreApplication::quit, Qt::QueuedConnection);
}
MainWindow::~MainWindow()
{
delete ui;
delete m_web_socket;
delete m_error_dialog;
}
void MainWindow::setup_cb(void)
{
connect(m_web_socket, &Web_socket_wrapper::on_command_cb, this, [=, this](json& j){on_cmd_cb(j);});
connect(m_web_socket, &Web_socket_wrapper::on_connected, this, [=, this]{m_web_socket->send_command(Web_socket_wrapper::Command::get_simulators);});
connect(m_web_socket, &Web_socket_wrapper::on_closed, this, [=, this]{m_error_dialog->open(); ui->connection->setText("Could not connect to the server");});
}
void MainWindow::showEvent( QShowEvent* event )
{
QWidget::showEvent( event );
setup_cb();
}
void MainWindow::hideEvent(QHideEvent* event)
{
QWidget::hideEvent(event);
disconnect(m_web_socket, nullptr, nullptr, nullptr);
}
void MainWindow::defaultSim(QString name)
{
this->m_selected_sim_name = name;
this->m_default_sim = true;
}
void MainWindow::pase_sim_names(json& sims)
{
for (json& item : sims)
{
ui->comboBox->addItem(QString::fromStdString(item));
}
QThread::msleep(30);
if (this->m_default_sim)
{
ui->comboBox->setCurrentText(this->m_selected_sim_name);
open_sim(this->m_selected_sim_name);
}
}
void MainWindow::open_sim(QString sim_name)
{
try
{
m_selected_sim = m_sims.at(sim_name);
this->m_selected_sim_name = sim_name;
}
catch (const std::out_of_range& ex)
{
QD << "out_of_range::what(): " << ex.what();
return;
}
m_web_socket->send_command(Web_socket_wrapper::Command::get_active_simulator_name);
}
void MainWindow::open_sim_window(void)
{
this->hide();
m_selected_sim->show();
}
void MainWindow::check_active_sim(QString name)
{
if (name == "")
{
QD << "Name not set";
m_web_socket->send_command(Web_socket_wrapper::Command::switch_simulator, m_selected_sim_name);
return;
}
QD << "Current sim:" << name;
if (name == m_selected_sim_name)
{
QD << "Name set and the same";
open_sim_window();
return;
}
QD << "Name set and wrong";
m_web_socket->send_command(Web_socket_wrapper::Command::switch_simulator, m_selected_sim_name);
}
void MainWindow::on_cmd_cb(json& j)
{
auto response = magic_enum::enum_cast<Web_socket_wrapper::Command>(std::string(j.at("type"))).value_or(Web_socket_wrapper::Command::end);
switch (response)
{
case Web_socket_wrapper::Command::get_simulators:
pase_sim_names(j.at("simulators"));
break;
case Web_socket_wrapper::Command::get_active_simulator_name:
check_active_sim(QString::fromStdString(j.at("name")));
break;
case Web_socket_wrapper::Command::switch_simulator:
open_sim_window();
break;
default:
break;
}
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <map>
#include <QMainWindow>
#include "web_socket_wrapper.h"
#include "networkerror.h"
#include "simulator_base.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QString host, uint16_t port, QWidget *parent = nullptr);
~MainWindow();
void defaultSim(QString name);
void pase_sim_names(json& sims);
void open_sim_window(void);
void check_active_sim(QString name);
void open_sim(QString sim_name);
void setup_cb(void);
signals:
void quit(void);
private:
void showEvent(QShowEvent* event);
void hideEvent(QHideEvent* event);
void on_cmd_cb(json& j);
bool m_default_sim = false;
QString m_selected_sim_name;
QMainWindow* m_selected_sim;
Ui::MainWindow* ui;
Web_socket_wrapper *m_web_socket;
NetworkError* m_error_dialog;
std::map<QString, SimulatorBase*> m_sims;
};
#endif // MAINWINDOW_H
+82
View File
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="connection">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actiontest">
<property name="text">
<string>test</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+19
View File
@@ -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]{this->close();});
this->setWindowTitle("Error");
}
void NetworkError::set_error(QString error)
{
ui->Error->setText(error);
}
NetworkError::~NetworkError() {
delete ui;
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef NETWORKERROR_H
#define NETWORKERROR_H
#include <QDialog>
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
+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NetworkError</class>
<widget class="QDialog" name="NetworkError">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="title">
<property name="font">
<font>
<pointsize>16</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Oops, an error occured</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="Error">
<property name="text">
<string>Error...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exit">
<property name="text">
<string>Exit Application</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+531
View File
@@ -0,0 +1,531 @@
#include "simulator_base.h"
#include "can_transceive.h"
SimulatorBase::SimulatorBase(Web_socket_wrapper* web_socket, QWidget* parent) :
QMainWindow{parent},
m_error_dialog(new NetworkError(this)),
m_timer_update(new QTimer()),
m_web_socket(web_socket)
{
m_error_dialog->set_error("Connection timed out\nCheck if the server is running");
m_ui_lookup.reserve(40);
}
QWidget* SimulatorBase::id_to_ui(size_t id)
{
return m_ui_lookup.at(id);
}
void SimulatorBase::showEvent( QShowEvent* event )
{
QWidget::showEvent( event );
QD << "Connecting callbacks";
setup_cb();
m_web_socket->send_command(Web_socket_wrapper::Command::get_UI_elements);
this->m_timer_update->start(m_refresh_rate);
m_open = true;
}
void SimulatorBase::closeEvent(QCloseEvent* event)
{
if (m_open == true)
{
QD << "Disconnecting callbacks";
QWidget::closeEvent(event);
m_timer_update->stop();
disconnect(m_web_socket, nullptr, nullptr, nullptr);
m_open = false;
this->parentWidget()->show();
}
}
void SimulatorBase::setup_cb(void)
{
connect(m_web_socket, &Web_socket_wrapper::on_command_cb, this, [=, this](json& j){on_cmd_cb(j);});
connect(m_web_socket, &Web_socket_wrapper::on_event_cb, this, [=, this](json& j){on_event_cb(j);});
connect(m_web_socket, &Web_socket_wrapper::on_closed, this, [=, this]{m_error_dialog->open();});
}
void SimulatorBase::on_cmd_cb(json& j)
{
auto response = magic_enum::enum_cast<Web_socket_wrapper::Command>(std::string(j.at("type"))).value_or(Web_socket_wrapper::Command::end);
switch (response)
{
case Web_socket_wrapper::Command::get_UI_elements:
case Web_socket_wrapper::Command::get_UI_element:
UI_item_parser(j);
break;
default:
break;
}
}
void SimulatorBase::on_event_cb(json& j)
{
auto response = magic_enum::enum_cast<Web_socket_wrapper::Event>(std::string(j.at("type"))).value_or(Web_socket_wrapper::Event::end);
switch (response)
{
case Web_socket_wrapper::Event::ui_changed:
UI_item_parser(j);
break;
default:
break;
}
}
void SimulatorBase::push_ui_item(QWidget* item)
{
m_ui_lookup.emplace_back(item);
setup_ui_item(item, m_ui_lookup.size() - 1);
}
void SimulatorBase::UI_item_parser(json& input)
{
for (auto& uiItem : input["UI_items"])
{
if (uiItem["type"] == "UI_button")
{
process_ui_button(uiItem);
continue;
}
if (uiItem["type"] == "UI_led")
{
process_ui_led(uiItem);
continue;
}
if (uiItem["type"] == "UI_slider")
{
process_ui_slider(uiItem);
continue;
}
if (uiItem["type"] == "UI_dial")
{
process_ui_dial(uiItem);
continue;
}
if (uiItem["type"] == "UI_thermo")
{
process_ui_thermo(uiItem);
continue;
}
if (uiItem["type"] == "UI_textbox")
{
process_ui_textbox(uiItem);
continue;
}
if (uiItem["type"] == "UI_label")
{
process_ui_label(uiItem);
continue;
}
if (uiItem["type"] == "UI_combobox")
{
process_ui_combobox(uiItem);
continue;
}
if (uiItem["type"] == "UIRadioButton")
{
process_ui_radiobutton(uiItem);
continue;
}
if (uiItem["type"] == "UI_can")
{
process_ui_can(uiItem);
continue;
}
}
}
void SimulatorBase::process_ui_label(json& uiItem)
{
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto label = qobject_cast<QLabel*>(widget);
if (label == nullptr)
{
QD << "widget is not of type QLabel";
return;
}
QString value = QString::fromStdString(uiItem["text"]);
if (label->text() != value)
{
label->setText(value);
}
}
void SimulatorBase::process_ui_slider(json& uiItem)
{
QLabel* label = id_to_label(uiItem["id"]);
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto slider = qobject_cast<QwtSlider*>(widget);
if (slider == nullptr)
{
QD << "widget is not of type QwtSlider";
return;
}
double value = uiItem["value"];
bool enable = uiItem["enabled"];
bool visible = uiItem["visible"];
QString unit = QString::fromStdString(uiItem.at("unit"));
if (slider->value() != value)
{
if (label != nullptr)
{
label->setText(QString("%0 %1").arg(value).arg(unit));
}
slider->setValue(value);
}
if (slider->isReadOnly() != !enable)
{
slider->setReadOnly(!enable);
}
if (slider->isVisible() != visible)
{
slider->setVisible(visible);
}
//TODO: Add color for scale
return;
}
void SimulatorBase::process_ui_dial(json& uiItem)
{
QLabel* label = id_to_label(uiItem["id"]);
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto dial = qobject_cast<QwtDial*>(widget);
if (dial == nullptr)
{
QD << "widget is not of type QwtDial";
return;
}
double value = uiItem["value"];
bool visible = uiItem["visible"];
QString unit = QString::fromStdString(uiItem.at("unit"));
if (dial->value() != value)
{
if (label != nullptr)
{
label->setText(QString("%0 %1").arg(value).arg(unit));
}
dial->setValue(value);
}
if (dial->isVisible() != visible)
{
dial->setVisible(visible);
}
return;
}
void SimulatorBase::process_ui_thermo(json& uiItem)
{
QLabel* label = id_to_label(uiItem["id"]);
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto thermo = qobject_cast<QwtThermo*>(widget);
if (thermo == nullptr)
{
QD << "widget is not of type QwtThermo";
return;
}
double value = uiItem["value"];
bool visible = uiItem["visible"];
QString unit = QString::fromStdString(uiItem.at("unit"));
if (thermo->value() != value)
{
if (label != nullptr)
{
label->setText(QString("%0 %1").arg(value).arg(unit));
}
thermo->setValue(value);
}
if (thermo->isVisible() != visible)
{
thermo->setVisible(visible);
}
return;
}
void SimulatorBase::process_ui_textbox(json& uiItem)
{
QD << "Not implemented";
emit quit();
}
void SimulatorBase::process_ui_combobox(json& uiItem)
{
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto combobox = qobject_cast<QComboBox*>(widget);
if (combobox == nullptr)
{
QD << "widget is not of type QComboBox";
return;
}
QString selected = QString::fromStdString(uiItem["selected"]);
for (json& item : uiItem["options"])
{
if (combobox->findText(QString::fromStdString(item)) == -1)
combobox->addItem(QString::fromStdString(item));
}
if (combobox->currentText() != selected)
{
combobox->setCurrentText(selected);
}
}
void SimulatorBase::process_ui_radiobutton(json& uiItem)
{
QD << "Not implemented";
emit quit();
}
void SimulatorBase::process_ui_button(json& uiItem)
{
//TODO: fix bgcolor and color
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto button = qobject_cast<QPushButton*>(widget);
if (button == nullptr)
{
QD << "widget is not of type QPushButton";
return;
}
QColor bg_color = QColor(QString::fromStdString(uiItem["bg_color"]));
QColor fg_color = QColor(QString::fromStdString(uiItem["fg_color"]));
QString text = QString::fromStdString(uiItem["text"]);
bool enabled = uiItem["enabled"];
if (button->text() != text)
{
button->setText(text);
}
if (button->isEnabled() != enabled)
{
button->setEnabled(enabled);
}
if (bg_color == QColor(0x83, 0x91, 0x92))
{
reset_widget_color(button);
return;
}
if (bg_color == QColor(0xFF, 0x00, 0x00))
{
set_widget_color(button, QString("black"), QString("darkred"));
return;
}
if (bg_color == QColor(0x00, 0xFF, 0x00))
{
set_widget_color(button, QString("white"), QString("darkgreen"));
return;
}
if (bg_color == QColor(0xFF8800))
{
set_widget_color(button, QString("darkred"), QString("darkorange"));
return;
}
if (bg_color != widget_bg_color(button) || fg_color != widget_color(button))
{
set_widget_color(button, fg_color, bg_color);
}
}
void SimulatorBase::process_ui_led(json& uiItem)
{
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto led = qobject_cast<Led*>(widget);
if (led == nullptr)
{
QD << "widget is not of type led";
return;
}
QColor bg_color = QColor(QString::fromStdString(uiItem["led_color"]));
QColor color = QColor(~bg_color.red(), ~bg_color.green(), ~bg_color.blue());
QString text = QString::fromStdString(uiItem["text"]);
if (text != led->text())
{
led->setText(text);
}
if (bg_color == QColor(0x83, 0x91, 0x92))
{
reset_widget_color(led);
return;
}
if (bg_color == QColor(0xFF, 0x00, 0x00))
{
set_widget_color(led, QString("black"), QString("darkred"));
return;
}
if (bg_color == QColor(0x00, 0xFF, 0x00))
{
set_widget_color(led, QString("white"), QString("darkgreen"));
return;
}
if (bg_color == QColor(0xFF8800))
{
set_widget_color(led, QString("darkred"), QString("darkorange"));
return;
}
if (bg_color != widget_bg_color(led))
{
set_widget_color(led, color, bg_color);
}
}
void SimulatorBase::process_ui_can(json& uiItem)
{
QWidget* widget = id_to_ui(uiItem["id"]);
if (widget == nullptr)
{
QD << "id_to_ui returned null on " << QString::number((size_t)uiItem["id"]);
return;
}
auto can_ui = qobject_cast<Can_Transceive*>(widget);
if (can_ui == nullptr)
{
QD << "widget is not of type led";
return;
}
can_ui->clear();
for (auto& send_item : uiItem["send_msgs"])
{
can_ui->add_send_item(send_item["id"], send_item["dlc"], send_item["payload"]);
}
for (auto& recvd_item : uiItem["rcvd_msgs"])
{
can_ui->add_receive_item(recvd_item["id"], recvd_item["dlc"], recvd_item["payload"]);
}
}
void SimulatorBase::setup_ui_item(QWidget* item, size_t index)
{
setup_button(item, index);
setup_combobox(item, index);
setup_dial(item, index);
setup_slider(item, index);
setup_can_ui(item, index);
}
void SimulatorBase::setup_button(QWidget* item, size_t index)
{
QPushButton* button = qobject_cast<QPushButton*>(item);
if (button == nullptr)
return;
connect(button, &QPushButton::clicked, this, [=, this]{m_web_socket->send_event(Web_socket_wrapper::Event::clicked, index);});
}
void SimulatorBase::setup_combobox(QWidget* item, size_t index)
{
QComboBox* combobox = qobject_cast<QComboBox*>(item);
if (combobox == nullptr)
return;
connect(combobox, &QComboBox::currentIndexChanged, this, [=, this]{
m_web_socket->send_event(Web_socket_wrapper::Event::selected, index,
combobox->currentText());});
}
void SimulatorBase::setup_dial(QWidget* item, size_t index)
{
QwtDial* dial = qobject_cast<QwtDial*>(item);
if (dial == nullptr)
return;
create_dial_needle(dial);
}
void SimulatorBase::setup_slider(QWidget* item, size_t index)
{
QwtSlider* slider = qobject_cast<QwtSlider*>(item);
if (slider == nullptr)
return;
connect(slider, &QwtSlider::sliderMoved, this, [=, this]{
m_web_socket->send_event(Web_socket_wrapper::Event::value_changed, index,
slider->value());});
}
void SimulatorBase::setup_can_ui(QWidget* item, size_t index)
{
Can_Transceive* can = qobject_cast<Can_Transceive*>(item);
if (can == nullptr)
return;
connect(can, &Can_Transceive::send_can_message, this, [=, this](uint32_t id, uint8_t dlc, std::array<uint8_t, 8> payload){
m_web_socket->send_event(Web_socket_wrapper::Event::can_send, index, id, dlc, payload);
});
connect(can, &Can_Transceive::can_clear, this, [=, this]{
m_web_socket->send_event(Web_socket_wrapper::Event::can_clear, index);
});
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef SIMULATOR_BASE_H
#define SIMULATOR_BASE_H
#include <QMainWindow>
#include <QWidget>
#include "web_socket_wrapper.h"
#include "networkerror.h"
#include "helpers.h"
#include "led.h"
#include <magic_enum.hpp>
#include "IterableEnum.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#define QD qDebug() << "Debug:" << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":"
class SimulatorBase : public QMainWindow {
Q_OBJECT
public:
explicit SimulatorBase(Web_socket_wrapper* api, QWidget* parent = nullptr);
void setup_cb(void);
void sim_update(void);
QWidget* id_to_ui(size_t id);
virtual QLabel* id_to_label(size_t name) {return nullptr;}
signals:
void quit();
protected:
void on_event_cb(json& j);
void on_cmd_cb(json& j);
void push_ui_item(QWidget* item);
void UI_item_parser(json& input);
virtual void process_ui_label(json& uiItem);
virtual void process_ui_slider(json& uiItem);
virtual void process_ui_dial(json& uiItem);
virtual void process_ui_thermo(json& uiItem);
virtual void process_ui_textbox(json& uiItem);
virtual void process_ui_combobox(json& uiItem);
virtual void process_ui_radiobutton(json& uiItem);
virtual void process_ui_button(json& uiItem);
virtual void process_ui_led(json& uiItem);
virtual void process_ui_can(json& uiItem);
void setup_ui_item(QWidget* item, size_t index);
void setup_button(QWidget* item, size_t index);
void setup_combobox(QWidget* item, size_t index);
void setup_dial(QWidget* item, size_t index);
void setup_slider(QWidget* item, size_t index);
void setup_can_ui(QWidget* item, size_t index);
void showEvent(QShowEvent* event);
void closeEvent(QCloseEvent* event);
private:
Web_socket_wrapper* m_web_socket;
QTimer* m_timer_update;
uint32_t m_refresh_rate;
NetworkError* m_error_dialog;
bool m_open = false;
std::vector<QWidget*>m_ui_lookup;
};
#endif // SIMULATOR_BASE_H
+15
View File
@@ -0,0 +1,15 @@
#include "sim-can_debugger.h"
#include "ui_sim-can_debugger.h"
Can_Debugger::Can_Debugger(Web_socket_wrapper* web_socket, QWidget *parent) :
SimulatorBase(web_socket, parent),
ui(new Ui::Can_Debugger) {
ui->setupUi(this);
this->setWindowTitle("CAN Debugger");
this->push_ui_item(ui->can_transceive);
}
Can_Debugger::~Can_Debugger() {
delete ui;
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef SIM_CAN_DEBUGGER_H
#define SIM_CAN_DEBUGGER_H
#include <QMainWindow>
#include "simulator_base.h"
namespace Ui {
class Can_Debugger;
}
class Can_Debugger : public SimulatorBase {
Q_OBJECT
public:
explicit Can_Debugger(Web_socket_wrapper* web_socket, QWidget *parent = nullptr);
~Can_Debugger();
private:
Ui::Can_Debugger* ui;
};
#endif // SIM_CAN_DEBUGGER_H
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Can_Debugger</class>
<widget class="QMainWindow" name="Can_Debugger">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1430</width>
<height>1127</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="Can_Transceive" name="can_transceive" native="true"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1430</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>Can_Transceive</class>
<extends>QWidget</extends>
<header>can_transceive.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+36
View File
@@ -0,0 +1,36 @@
#include "sim-test.h"
#include "ui_sim-test.h"
Test_Sim::Test_Sim(Web_socket_wrapper* web_socket, QWidget* parent) :
SimulatorBase(web_socket, parent),
ui(new Ui::Test_Sim)
{
ui->setupUi(this);
this->setWindowTitle("Test Sim");
this->setWindowState(Qt::WindowMaximized);
push_ui_item(ui->button);
push_ui_item(ui->combobox);
push_ui_item(ui->label);
push_ui_item(ui->slider);
push_ui_item(ui->dial);
push_ui_item(ui->thermo);
push_ui_item(ui->led);
}
Test_Sim::~Test_Sim() {
delete ui;
}
QLabel* Test_Sim::id_to_label(size_t id)
{
QWidget* widget = id_to_ui(id);
if (widget == ui->slider)
return ui->slider_label;
if (widget == ui->thermo)
return ui->thermo_label;
if (widget == ui->dial)
return ui->dial_label;
return nullptr;
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef SIM_TEST_H
#define SIM_TEST_H
#include <QMainWindow>
#include "simulator_base.h"
#include "web_socket_wrapper.h"
namespace Ui {
class Test_Sim;
}
class Test_Sim : public SimulatorBase {
Q_OBJECT
public:
explicit Test_Sim(Web_socket_wrapper* web_socket, QWidget* parent = nullptr);
~Test_Sim();
QLabel* id_to_label(size_t name) override;
private:
Ui::Test_Sim* ui;
};
#endif // SIM_TEST_H
+135
View File
@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Test_Sim</class>
<widget class="QMainWindow" name="Test_Sim">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="3">
<widget class="QPushButton" name="button">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="Led" name="led">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="slider_label">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QwtSlider" name="slider"/>
</item>
</layout>
</item>
<item row="4" column="3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="dial_label">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QwtDial" name="dial">
<property name="lineWidth">
<number>4</number>
</property>
<property name="minScaleArc">
<double>20.000000000000000</double>
</property>
<property name="maxScaleArc">
<double>340.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="thermo_label">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QwtThermo" name="thermo"/>
</item>
</layout>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="combobox"/>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>QwtDial</class>
<extends>QWidget</extends>
<header>qwt_dial.h</header>
</customwidget>
<customwidget>
<class>QwtSlider</class>
<extends>QWidget</extends>
<header>qwt_slider.h</header>
</customwidget>
<customwidget>
<class>QwtThermo</class>
<extends>QWidget</extends>
<header>qwt_thermo.h</header>
</customwidget>
<customwidget>
<class>Led</class>
<extends>QPushButton</extends>
<header>led.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+191
View File
@@ -0,0 +1,191 @@
#include "web_socket_wrapper.h"
#include "magic_enum.hpp"
Web_socket_wrapper::Web_socket_wrapper(const QUrl &url, QObject *parent) :
QObject(parent),
m_web_socket(new QWebSocket("", QWebSocketProtocol::VersionLatest, this)),
m_ping_timer(new QTimer(this)),
m_pong_timer(new QTimer(this))
{
connect(m_web_socket, &QWebSocket::connected, this, &Web_socket_wrapper::m_on_connected);
connect(m_web_socket, &QWebSocket::disconnected, this, &Web_socket_wrapper::on_closed);
connect(m_web_socket, &QWebSocket::pong, this, &Web_socket_wrapper::m_on_pong);
connect(m_ping_timer, &QTimer::timeout, this, [=, this]{
m_web_socket->ping();
m_pong_timer->start(500);
});
connect(m_pong_timer, &QTimer::timeout, this, &Web_socket_wrapper::m_on_pong_timeout);
m_ping_timer->setSingleShot(true);
m_pong_timer->setSingleShot(true);
m_web_socket->open(url);
}
Web_socket_wrapper::~Web_socket_wrapper()
{
close();
}
void Web_socket_wrapper::send_command(Command command, size_t id)
{
json j;
switch (command)
{
case Command::get_UI_element:
j["command"]["type"] = magic_enum::enum_name(command).data();
j["command"]["id"] = id;
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
case Command::get_UI_elements:
j["command"]["type"] = magic_enum::enum_name(command).data();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
case Command::get_active_simulator_name:
j["command"]["type"] = magic_enum::enum_name(command).data();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
case Command::get_simulators:
j["command"]["type"] = magic_enum::enum_name(command).data();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::send_command(Command command, QString id)
{
json j;
switch (command)
{
case Command::switch_simulator:
j["command"]["type"] = magic_enum::enum_name(command).data();
j["command"]["name"] = id.toStdString();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::send_event(Event event, size_t id)
{
json j;
switch (event)
{
case Event::clicked:
case Event::can_clear:
j["event"]["type"] = magic_enum::enum_name(event).data();
j["event"]["id"] = id;
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::send_event(Event event, size_t id, double val)
{
json j;
switch (event)
{
case Event::value_changed:
j["event"]["type"] = magic_enum::enum_name(event).data();
j["event"]["id"] = id;
j["event"]["value"] = val;
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::send_event(Event event, size_t id, QString val)
{
json j;
switch (event)
{
case Event::text_changed:
j["event"]["type"] = magic_enum::enum_name(event).data();
j["event"]["id"] = id;
j["event"]["text"] = val.toStdString();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
case Event::selected:
j["event"]["type"] = magic_enum::enum_name(event).data();
j["event"]["id"] = id;
j["event"]["selected"] = val.toStdString();
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::send_event(Event event, size_t id, uint32_t sid, uint8_t dlc, std::array<uint8_t, 8> payload)
{
json j;
switch (event)
{
case Event::can_send:
j["event"]["type"] = magic_enum::enum_name(event).data();
j["event"]["id"] = id;
j["event"]["sid"] = sid;
j["event"]["dlc"] = dlc;
j["event"]["payload"] = payload;
m_web_socket->sendTextMessage(QString(j.dump().data()));
break;
default:
break;
}
}
void Web_socket_wrapper::close()
{
if (m_connected)
m_web_socket->close();
}
void Web_socket_wrapper::m_on_connected()
{
m_connected = true;
connect(m_web_socket, &QWebSocket::textMessageReceived,
this, &Web_socket_wrapper::m_on_received);
m_ping_timer->start(1000);
emit on_connected();
}
void Web_socket_wrapper::m_on_received(QString message)
{
json j = json::parse(message.toStdString());
if (j.contains("response"))
{
emit on_command_cb(j.at("response"));
return;
}
if (j.contains("event"))
{
emit on_event_cb(j.at("event"));
return;
}
}
void Web_socket_wrapper::m_on_pong(quint64 elapsedTime)
{
if (elapsedTime > 500)
{
m_web_socket->close();
emit on_closed();
}
m_pong_timer->stop();
m_ping_timer->start(1000);
}
void Web_socket_wrapper::m_on_pong_timeout()
{
qDebug() << "Pong timeout";
m_web_socket->close();
emit on_closed();
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef WEB_SOCKET_WRAPPER_H
#define WEB_SOCKET_WRAPPER_H
#include <QtWebSockets/QWebSocket>
#include <QTimer>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
class Web_socket_wrapper : public QObject
{
Q_OBJECT
public:
enum class Command
{
get_UI_element,
get_UI_elements,
switch_simulator,
get_active_simulator_name,
get_simulators,
end,
begin = 0
};
enum class Event
{
clicked,
value_changed,
text_changed,
selected,
can_send,
can_clear,
ui_changed,
end,
begin = 0
};
public:
explicit Web_socket_wrapper(const QUrl &url, QObject *parent = nullptr);
~Web_socket_wrapper();
void send_command(Command command, size_t id = 0);
void send_command(Command command, QString id);
void send_event(Event event, size_t id);
void send_event(Event event, size_t id, double val);
void send_event(Event event, size_t id, QString val);
void send_event(Event event, size_t id, uint32_t sid, uint8_t dlc, std::array<uint8_t, 8> payload);
void close();
Q_SIGNALS:
void on_connected();
void on_closed();
void on_command_cb(json& data);
void on_event_cb(json& data);
private Q_SLOTS:
void m_on_connected();
void m_on_received(QString message);
void m_on_pong(quint64 elapsedTime);
void m_on_pong_timeout();
private:
bool m_connected = false;
QWebSocket* m_web_socket;
QTimer* m_ping_timer;
QTimer* m_pong_timer;
};
#endif // WEB_SOCKET_WRAPPER_H