Added logs and check with trad RM we are working ok
This commit is contained in:
@@ -1,14 +1,36 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(WasmWebSocketClient LANGUAGES CXX)
|
project(wsapp VERSION 1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets WebSockets)
|
find_package(Qt6 REQUIRED COMPONENTS Core Widgets WebSockets)
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_add_executable(wsapp
|
||||||
|
main.cpp
|
||||||
|
GamesPanel.cpp GamesPanel.h
|
||||||
|
LogPanel.cpp LogPanel.h
|
||||||
|
PowerPanel.cpp PowerPanel.h
|
||||||
|
SettingsTree.cpp SettingsTree.h
|
||||||
|
VersionsPanel.cpp VersionsPanel.h
|
||||||
|
WebSocketController.cpp WebSocketController.h
|
||||||
|
)
|
||||||
|
|
||||||
qt_add_executable(WasmWebSocketClient main.cpp)
|
target_link_libraries(wsapp PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Widgets
|
||||||
|
Qt6::WebSockets
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(WasmWebSocketClient
|
if(EMSCRIPTEN)
|
||||||
PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::WebSockets)
|
set_target_properties(wsapp PROPERTIES
|
||||||
|
WIN32_EXECUTABLE TRUE
|
||||||
|
QT_WASM_INITIAL_MEMORY "50MB"
|
||||||
|
QT_WASM_MAX_MEMORY "1GB"
|
||||||
|
)
|
||||||
|
target_link_options(wsapp PRIVATE
|
||||||
|
"SHELL:-s ASYNCIFY=1"
|
||||||
|
"SHELL:-s ASYNCIFY_STACK_SIZE=65536"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|||||||
112
LogPanel.cpp
Normal file
112
LogPanel.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include "LogPanel.h"
|
||||||
|
#include "WebSocketController.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
static const QStringList k_logNames = {
|
||||||
|
"Wireless", "DataBase", "GenericControllers", "ControlPanelRxBusy",
|
||||||
|
"ErrorChecker", "Main", "PowerControl", "Radar", "LogSerial",
|
||||||
|
"GameEventHandler", "GameScript", "userList", "SecondarySystems",
|
||||||
|
"Sounds", "Games", "SecureSocketComms", "CurrentLimit",
|
||||||
|
"TimerDisplay", "Wall:Controlmessages", "PowerOffTimer", "LogMaster",
|
||||||
|
"Wall:Sensingsystem", "Globals", "GameTimer", "Nfc", "ControlPanelTx",
|
||||||
|
"Targetsystem", "GameBase", "LogSyncMessages", "MasterController",
|
||||||
|
"Wall:Buttons", "ControlPanelRx", "PowerStateChange", "SecureSocket",
|
||||||
|
"Authentication", "UplinkComms", "Resources", "GameCounter",
|
||||||
|
"StripInterface", "Uplink", "Wall:Datarequests", "Wirelesswall",
|
||||||
|
"BeamBreakers", "WebSocketServer", "Wall:Targetdrawing",
|
||||||
|
"LoopTestMessages", "Gps", "LoopMessages", "NfcRemote",
|
||||||
|
"LogLoopErrors"
|
||||||
|
};
|
||||||
|
|
||||||
|
LogPanel::LogPanel(WebSocketController *ctrl, QWidget *parent)
|
||||||
|
: QWidget(parent), m_ctrl(ctrl)
|
||||||
|
{
|
||||||
|
auto *outer = new QVBoxLayout(this);
|
||||||
|
outer->setContentsMargins(6, 6, 6, 6);
|
||||||
|
outer->setSpacing(6);
|
||||||
|
|
||||||
|
auto *toolbar = new QHBoxLayout();
|
||||||
|
auto *refreshBtn = new QPushButton("Refresh", this);
|
||||||
|
auto *allOnBtn = new QPushButton("All On", this);
|
||||||
|
auto *allOffBtn = new QPushButton("All Off", this);
|
||||||
|
toolbar->addWidget(refreshBtn);
|
||||||
|
toolbar->addWidget(allOnBtn);
|
||||||
|
toolbar->addWidget(allOffBtn);
|
||||||
|
toolbar->addStretch(1);
|
||||||
|
outer->addLayout(toolbar);
|
||||||
|
|
||||||
|
auto *scroll = new QScrollArea(this);
|
||||||
|
scroll->setWidgetResizable(true);
|
||||||
|
auto *inner = new QWidget(scroll);
|
||||||
|
auto *grid = new QGridLayout(inner);
|
||||||
|
grid->setSpacing(4);
|
||||||
|
grid->setContentsMargins(4, 4, 4, 4);
|
||||||
|
|
||||||
|
const int cols = 4;
|
||||||
|
for (int i = 0; i < k_logNames.size(); ++i) {
|
||||||
|
const QString &name = k_logNames[i];
|
||||||
|
auto *cb = new QCheckBox(name, inner);
|
||||||
|
m_checkboxes[name] = cb;
|
||||||
|
grid->addWidget(cb, i / cols, i % cols);
|
||||||
|
|
||||||
|
connect(cb, &QCheckBox::toggled, this, [this, name](bool checked) {
|
||||||
|
onCheckboxToggled(name, checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll->setWidget(inner);
|
||||||
|
outer->addWidget(scroll, 1);
|
||||||
|
|
||||||
|
connect(refreshBtn, &QPushButton::clicked, this, &LogPanel::requestRefresh);
|
||||||
|
connect(allOnBtn, &QPushButton::clicked, this, [this]() {
|
||||||
|
for (auto *cb : m_checkboxes)
|
||||||
|
cb->setChecked(true);
|
||||||
|
});
|
||||||
|
connect(allOffBtn, &QPushButton::clicked, this, [this]() {
|
||||||
|
for (auto *cb : m_checkboxes)
|
||||||
|
cb->setChecked(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogPanel::requestRefresh()
|
||||||
|
{
|
||||||
|
if (m_ctrl)
|
||||||
|
m_ctrl->sendCommand(QStringLiteral("LOG"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogPanel::onCheckboxToggled(const QString &name, bool checked)
|
||||||
|
{
|
||||||
|
if (!m_ctrl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString state = checked ? "on" : "off";
|
||||||
|
m_ctrl->sendCommand(QString("LOG %1=%2").arg(name, state));
|
||||||
|
m_ctrl->sendCommand(QStringLiteral("LOG"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogPanel::applyLogResponse(const QString &msg)
|
||||||
|
{
|
||||||
|
for (auto *cb : m_checkboxes)
|
||||||
|
cb->blockSignals(true);
|
||||||
|
|
||||||
|
for (auto *cb : m_checkboxes)
|
||||||
|
cb->setChecked(false);
|
||||||
|
|
||||||
|
const QStringList tokens = msg.split(' ', Qt::SkipEmptyParts);
|
||||||
|
for (int i = 1; i < tokens.size(); ++i) {
|
||||||
|
const QString &tok = tokens[i];
|
||||||
|
const bool isOn = tok.endsWith('*');
|
||||||
|
const QString name = isOn ? tok.chopped(1) : tok;
|
||||||
|
if (m_checkboxes.contains(name))
|
||||||
|
m_checkboxes[name]->setChecked(isOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto *cb : m_checkboxes)
|
||||||
|
cb->blockSignals(false);
|
||||||
|
}
|
||||||
23
LogPanel.h
Normal file
23
LogPanel.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
class QCheckBox;
|
||||||
|
class WebSocketController;
|
||||||
|
|
||||||
|
class LogPanel : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit LogPanel(WebSocketController *ctrl, QWidget *parent = nullptr);
|
||||||
|
void applyLogResponse(const QString &msg);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCheckboxToggled(const QString &name, bool checked);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void requestRefresh();
|
||||||
|
|
||||||
|
WebSocketController *m_ctrl = nullptr;
|
||||||
|
QMap<QString, QCheckBox*> m_checkboxes;
|
||||||
|
};
|
||||||
127
PowerPanel.cpp
Normal file
127
PowerPanel.cpp
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#include "PowerPanel.h"
|
||||||
|
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
static QLabel *makeRow(QWidget *parent)
|
||||||
|
{
|
||||||
|
auto *l = new QLabel(parent);
|
||||||
|
l->setStyleSheet("font-size: 11pt; padding: 2px 0px;");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerPanel::PowerPanel(QWidget *parent) : QWidget(parent)
|
||||||
|
{
|
||||||
|
auto *outer = new QVBoxLayout(this);
|
||||||
|
outer->setContentsMargins(10, 10, 10, 10);
|
||||||
|
outer->setSpacing(2);
|
||||||
|
|
||||||
|
auto *heading = new QLabel("Status:", this);
|
||||||
|
QFont hf = heading->font();
|
||||||
|
hf.setBold(true);
|
||||||
|
hf.setPointSize(10);
|
||||||
|
heading->setFont(hf);
|
||||||
|
outer->addWidget(heading);
|
||||||
|
|
||||||
|
auto *line = new QFrame(this);
|
||||||
|
line->setFrameShape(QFrame::HLine);
|
||||||
|
line->setFrameShadow(QFrame::Sunken);
|
||||||
|
outer->addWidget(line);
|
||||||
|
|
||||||
|
m_statusLabel = makeRow(this);
|
||||||
|
m_supplyLabel = makeRow(this);
|
||||||
|
m_externalLabel = makeRow(this);
|
||||||
|
m_ratedLabel = makeRow(this);
|
||||||
|
m_batt1Label = makeRow(this);
|
||||||
|
m_batt2Label = makeRow(this);
|
||||||
|
m_batt3Label = makeRow(this);
|
||||||
|
|
||||||
|
outer->addWidget(m_statusLabel);
|
||||||
|
outer->addWidget(m_supplyLabel);
|
||||||
|
outer->addWidget(m_externalLabel);
|
||||||
|
outer->addWidget(m_ratedLabel);
|
||||||
|
outer->addWidget(m_batt1Label);
|
||||||
|
outer->addWidget(m_batt2Label);
|
||||||
|
outer->addWidget(m_batt3Label);
|
||||||
|
outer->addStretch(1);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerPanel::reset()
|
||||||
|
{
|
||||||
|
const QString style = "font-size: 11pt; padding: 2px 0px;";
|
||||||
|
m_statusLabel ->setText("Connection status: --"); m_statusLabel ->setStyleSheet(style);
|
||||||
|
m_supplyLabel ->setText("Supply voltage: --"); m_supplyLabel ->setStyleSheet(style);
|
||||||
|
m_externalLabel->setText("External voltage: --"); m_externalLabel->setStyleSheet(style);
|
||||||
|
m_ratedLabel ->setText("Rated voltage: --"); m_ratedLabel ->setStyleSheet(style);
|
||||||
|
m_batt1Label ->setText("Battery 1 voltage: --"); m_batt1Label ->setStyleSheet(style);
|
||||||
|
m_batt2Label ->setText("Battery 2 voltage: --"); m_batt2Label ->setStyleSheet(style);
|
||||||
|
m_batt3Label ->setText("Battery 3 voltage: --"); m_batt3Label ->setStyleSheet(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #P0-P STA ext
|
||||||
|
void PowerPanel::setStatus(const QString &raw)
|
||||||
|
{
|
||||||
|
applyLabel(m_statusLabel,
|
||||||
|
QString("Connection status: %1").arg(statusText(raw)),
|
||||||
|
statusColour(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
// #P0-P RTV 14.00
|
||||||
|
void PowerPanel::setRatedVoltage(const QString &v)
|
||||||
|
{
|
||||||
|
applyLabel(m_ratedLabel, QString("Rated voltage: %1 V").arg(v), "#2196a0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// #P0-P VTG supply ext batt1 batt2 batt3
|
||||||
|
void PowerPanel::setVoltages(const QStringList &v)
|
||||||
|
{
|
||||||
|
auto set = [&](QLabel *l, const QString &prefix, int idx) {
|
||||||
|
if (idx >= v.size()) return;
|
||||||
|
applyLabel(l, QString("%1: %2 V").arg(prefix, v[idx]), voltColour(v[idx]));
|
||||||
|
};
|
||||||
|
set(m_supplyLabel, "Supply voltage", 0);
|
||||||
|
set(m_externalLabel, "External voltage", 1);
|
||||||
|
set(m_batt1Label, "Battery 1 voltage", 2);
|
||||||
|
set(m_batt2Label, "Battery 2 voltage", 3);
|
||||||
|
set(m_batt3Label, "Battery 3 voltage", 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerPanel::applyLabel(QLabel *l, const QString &text, const QString &colour)
|
||||||
|
{
|
||||||
|
l->setText(text);
|
||||||
|
l->setStyleSheet(QString("font-size: 11pt; padding: 2px 0px; color: %1;").arg(colour));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PowerPanel::statusText(const QString &raw) const
|
||||||
|
{
|
||||||
|
if (raw == "ext") return "External supply";
|
||||||
|
if (raw == "bat") return "Battery";
|
||||||
|
if (raw == "low") return "Battery low";
|
||||||
|
if (raw == "crit") return "Battery critical";
|
||||||
|
if (raw == "off") return "Off";
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PowerPanel::statusColour(const QString &raw) const
|
||||||
|
{
|
||||||
|
if (raw == "ext") return "#2e7d32";
|
||||||
|
if (raw == "bat") return "#1565c0";
|
||||||
|
if (raw == "low") return "#e65100";
|
||||||
|
if (raw == "crit") return "#c62828";
|
||||||
|
if (raw == "off") return "#888888";
|
||||||
|
return "#888888";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PowerPanel::voltColour(const QString &val) const
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
const double d = val.toDouble(&ok);
|
||||||
|
if (!ok) return "palette(text)";
|
||||||
|
if (d > 10.0) return "#2e7d32";
|
||||||
|
if (d > 5.0) return "#e65100";
|
||||||
|
if (d > 0.01) return "#c62828";
|
||||||
|
return "#888888";
|
||||||
|
}
|
||||||
29
PowerPanel.h
Normal file
29
PowerPanel.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
class PowerPanel : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit PowerPanel(QWidget *parent = nullptr);
|
||||||
|
void reset();
|
||||||
|
void setStatus(const QString &raw);
|
||||||
|
void setRatedVoltage(const QString &v);
|
||||||
|
void setVoltages(const QStringList &v);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString statusText(const QString &raw) const;
|
||||||
|
QString statusColour(const QString &raw) const;
|
||||||
|
QString voltColour(const QString &val) const;
|
||||||
|
void applyLabel(QLabel *l, const QString &text, const QString &colour);
|
||||||
|
|
||||||
|
QLabel *m_statusLabel = nullptr;
|
||||||
|
QLabel *m_supplyLabel = nullptr;
|
||||||
|
QLabel *m_externalLabel = nullptr;
|
||||||
|
QLabel *m_ratedLabel = nullptr;
|
||||||
|
QLabel *m_batt1Label = nullptr;
|
||||||
|
QLabel *m_batt2Label = nullptr;
|
||||||
|
QLabel *m_batt3Label = nullptr;
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "WebSocketController.h"
|
#include "WebSocketController.h"
|
||||||
#include "GamesPanel.h"
|
#include "GamesPanel.h"
|
||||||
|
#include "LogPanel.h"
|
||||||
|
#include "PowerPanel.h"
|
||||||
#include "SettingsTree.h"
|
#include "SettingsTree.h"
|
||||||
#include "VersionsPanel.h"
|
#include "VersionsPanel.h"
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ WebSocketController::WebSocketController(QLineEdit *urlEdit,
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
QWebSocket *WebSocketController::socket() { return &m_socket; }
|
QWebSocket *WebSocketController::socket() { return &m_socket; }
|
||||||
|
|
||||||
void WebSocketController::addLogView(QTextEdit *log) { m_logs.append(log); }
|
void WebSocketController::addLogView(QTextEdit *log) { m_logs.append(log); }
|
||||||
|
|
||||||
void WebSocketController::setSettingsTree(SettingsTree *tree)
|
void WebSocketController::setSettingsTree(SettingsTree *tree)
|
||||||
@@ -26,49 +29,14 @@ void WebSocketController::setSettingsTree(SettingsTree *tree)
|
|||||||
|
|
||||||
void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; }
|
void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; }
|
||||||
void WebSocketController::setVersionsPanel(VersionsPanel *panel) { m_versionsPanel = panel; }
|
void WebSocketController::setVersionsPanel(VersionsPanel *panel) { m_versionsPanel = panel; }
|
||||||
|
void WebSocketController::setPowerPanel(PowerPanel *panel) { m_powerPanel = panel; }
|
||||||
|
void WebSocketController::setLogPanel(LogPanel *panel) { m_logPanel = panel; }
|
||||||
|
|
||||||
bool WebSocketController::isConnected() const
|
bool WebSocketController::isConnected() const
|
||||||
{
|
{
|
||||||
return m_socket.state() == QAbstractSocket::ConnectedState;
|
return m_socket.state() == QAbstractSocket::ConnectedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Lazy loaders — called ONLY when user clicks a tab for first time
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
void WebSocketController::requestGamesData()
|
|
||||||
{
|
|
||||||
if (!isConnected() || m_gamesRequested) return;
|
|
||||||
m_gamesRequested = true;
|
|
||||||
broadcast("-- Loading games tab --");
|
|
||||||
sendCommand(QStringLiteral("GAM list"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketController::requestVersionsData()
|
|
||||||
{
|
|
||||||
if (!isConnected() || m_versionsRequested) return;
|
|
||||||
m_versionsRequested = true;
|
|
||||||
broadcast("-- Loading versions tab --");
|
|
||||||
sendCommand(QStringLiteral("NAM"));
|
|
||||||
sendCommand(QStringLiteral("VER"));
|
|
||||||
sendCommand(QStringLiteral("UID"));
|
|
||||||
sendCommand(QStringLiteral("RNP"));
|
|
||||||
// Slave VER/UID loop fires in handleProtocol when RNP reply arrives
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketController::requestSettingsData()
|
|
||||||
{
|
|
||||||
if (!isConnected() || m_settingsRequested) return;
|
|
||||||
m_settingsRequested = true;
|
|
||||||
broadcast("-- Loading settings tab --");
|
|
||||||
sendCommand(QStringLiteral("GBL List"));
|
|
||||||
// Individual GBL <key> loop fires in handleProtocol when List reply arrives
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Connection
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
void WebSocketController::startConnection()
|
void WebSocketController::startConnection()
|
||||||
{
|
{
|
||||||
const QUrl url(m_urlEdit->text().trimmed());
|
const QUrl url(m_urlEdit->text().trimmed());
|
||||||
@@ -82,11 +50,9 @@ void WebSocketController::closeConnection()
|
|||||||
{
|
{
|
||||||
broadcast("Closing connection");
|
broadcast("Closing connection");
|
||||||
m_settingsKeys.clear();
|
m_settingsKeys.clear();
|
||||||
m_rnpCount = -1;
|
m_rnpCount = -1;
|
||||||
m_gamesRequested = false;
|
|
||||||
m_versionsRequested = false;
|
|
||||||
m_settingsRequested = false;
|
|
||||||
if (m_versionsPanel) m_versionsPanel->reset();
|
if (m_versionsPanel) m_versionsPanel->reset();
|
||||||
|
if (m_powerPanel) m_powerPanel->reset();
|
||||||
m_socket.close();
|
m_socket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +65,20 @@ void WebSocketController::sendCommand(const QString &cmd)
|
|||||||
|
|
||||||
void WebSocketController::onConnected()
|
void WebSocketController::onConnected()
|
||||||
{
|
{
|
||||||
// Send NOTHING here — all data is lazy-loaded on tab click
|
|
||||||
broadcast("Connected");
|
broadcast("Connected");
|
||||||
m_statusLabel->setText("Connected");
|
m_statusLabel->setText("Connected");
|
||||||
m_rnpCount = -1;
|
m_rnpCount = -1;
|
||||||
m_gamesRequested = false;
|
|
||||||
m_versionsRequested = false;
|
sendCommand(QStringLiteral("GBL List"));
|
||||||
m_settingsRequested = false;
|
sendCommand(QStringLiteral("GAM list"));
|
||||||
|
sendCommand(QStringLiteral("NAM"));
|
||||||
|
sendCommand(QStringLiteral("VER"));
|
||||||
|
sendCommand(QStringLiteral("UID"));
|
||||||
|
sendCommand(QStringLiteral("RNP"));
|
||||||
|
sendCommand(QStringLiteral("#P0-P STA"));
|
||||||
|
sendCommand(QStringLiteral("#P0-P RTV"));
|
||||||
|
sendCommand(QStringLiteral("#P0-P VTG"));
|
||||||
|
sendCommand(QStringLiteral("LOG"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketController::onDisconnected()
|
void WebSocketController::onDisconnected()
|
||||||
@@ -113,10 +86,7 @@ void WebSocketController::onDisconnected()
|
|||||||
broadcast("Disconnected");
|
broadcast("Disconnected");
|
||||||
m_statusLabel->setText("Disconnected");
|
m_statusLabel->setText("Disconnected");
|
||||||
m_settingsKeys.clear();
|
m_settingsKeys.clear();
|
||||||
m_rnpCount = -1;
|
m_rnpCount = -1;
|
||||||
m_gamesRequested = false;
|
|
||||||
m_versionsRequested = false;
|
|
||||||
m_settingsRequested = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketController::onTextMessageReceived(const QString &msg)
|
void WebSocketController::onTextMessageReceived(const QString &msg)
|
||||||
@@ -137,37 +107,54 @@ void WebSocketController::onValueEdited(const QString &key, const QString &newVa
|
|||||||
sendCommand(QString("GBL %1").arg(key));
|
sendCommand(QString("GBL %1").arg(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Protocol handler
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
void WebSocketController::handleProtocol(const QString &msg)
|
void WebSocketController::handleProtocol(const QString &msg)
|
||||||
{
|
{
|
||||||
const QStringList tokens = msg.split(' ', Qt::SkipEmptyParts);
|
const QStringList tokens = msg.split(' ', Qt::SkipEmptyParts);
|
||||||
if (tokens.isEmpty()) return;
|
if (tokens.isEmpty()) return;
|
||||||
const QString cmd = tokens[0];
|
const QString cmd = tokens[0];
|
||||||
|
|
||||||
// NAM <name>
|
// ---- Power: #P0-P STA/RTV/VTG ----
|
||||||
|
if (cmd == "#P0-P" && tokens.size() >= 2 && m_powerPanel) {
|
||||||
|
const QString sub = tokens[1];
|
||||||
|
if (sub == "STA" && tokens.size() >= 3) m_powerPanel->setStatus(tokens[2]);
|
||||||
|
else if (sub == "RTV" && tokens.size() >= 3) m_powerPanel->setRatedVoltage(tokens[2]);
|
||||||
|
else if (sub == "VTG" && tokens.size() >= 3) m_powerPanel->setVoltages(tokens.mid(2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- LOG channel list ----
|
||||||
|
// Response: LOG name1 name2* name3 ... (* = enabled)
|
||||||
|
if (cmd == "LOG" && tokens.size() > 1 && m_logPanel) {
|
||||||
|
// Only handle the full list response (no '=' in any token)
|
||||||
|
bool isList = true;
|
||||||
|
for (int i = 1; i < tokens.size(); ++i) {
|
||||||
|
if (tokens[i].contains('=')) { isList = false; break; }
|
||||||
|
}
|
||||||
|
if (isList) m_logPanel->applyLogResponse(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NAM
|
||||||
if (cmd == "NAM" && tokens.size() >= 2) {
|
if (cmd == "NAM" && tokens.size() >= 2) {
|
||||||
if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]);
|
if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VER M <fw> or VER <n> <fw>
|
// VER
|
||||||
if (cmd == "VER" && tokens.size() >= 3) {
|
if (cmd == "VER" && tokens.size() >= 3) {
|
||||||
if (m_versionsPanel)
|
if (m_versionsPanel)
|
||||||
m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' '));
|
m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' '));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UID M <hex> or UID <n> <hex>
|
// UID
|
||||||
if (cmd == "UID" && tokens.size() >= 3) {
|
if (cmd == "UID" && tokens.size() >= 3) {
|
||||||
if (m_versionsPanel)
|
if (m_versionsPanel)
|
||||||
m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' '));
|
m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' '));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RNP <count> — triggers the slave loop
|
// RNP
|
||||||
if (cmd == "RNP" && tokens.size() >= 2) {
|
if (cmd == "RNP" && tokens.size() >= 2) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const int count = tokens[1].toInt(&ok);
|
const int count = tokens[1].toInt(&ok);
|
||||||
@@ -182,7 +169,7 @@ void WebSocketController::handleProtocol(const QString &msg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GAM list ...
|
// GAM list
|
||||||
if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") {
|
if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") {
|
||||||
if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg);
|
if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg);
|
||||||
return;
|
return;
|
||||||
@@ -204,8 +191,8 @@ void WebSocketController::handleProtocol(const QString &msg)
|
|||||||
const QString payload = tokens[1];
|
const QString payload = tokens[1];
|
||||||
const int eqIdx = payload.indexOf('=');
|
const int eqIdx = payload.indexOf('=');
|
||||||
if (eqIdx > 0) {
|
if (eqIdx > 0) {
|
||||||
const QString key = payload.left(eqIdx);
|
const QString key = payload.left(eqIdx);
|
||||||
QString value = payload.mid(eqIdx + 1);
|
QString value = payload.mid(eqIdx + 1);
|
||||||
if (tokens.size() > 2) value += ' ' + tokens.mid(2).join(' ');
|
if (tokens.size() > 2) value += ' ' + tokens.mid(2).join(' ');
|
||||||
m_settingsTree->setValue(key, value);
|
m_settingsTree->setValue(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class QLabel;
|
|||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
class QTextEdit;
|
class QTextEdit;
|
||||||
class GamesPanel;
|
class GamesPanel;
|
||||||
|
class LogPanel;
|
||||||
|
class PowerPanel;
|
||||||
class SettingsTree;
|
class SettingsTree;
|
||||||
class VersionsPanel;
|
class VersionsPanel;
|
||||||
|
|
||||||
@@ -23,13 +25,10 @@ public:
|
|||||||
void setSettingsTree(SettingsTree *tree);
|
void setSettingsTree(SettingsTree *tree);
|
||||||
void setGamesPanel(GamesPanel *panel);
|
void setGamesPanel(GamesPanel *panel);
|
||||||
void setVersionsPanel(VersionsPanel *panel);
|
void setVersionsPanel(VersionsPanel *panel);
|
||||||
|
void setPowerPanel(PowerPanel *panel);
|
||||||
|
void setLogPanel(LogPanel *panel);
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
|
||||||
// Called by main when user selects a tab for the first time
|
|
||||||
void requestGamesData();
|
|
||||||
void requestVersionsData();
|
|
||||||
void requestSettingsData();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void startConnection();
|
void startConnection();
|
||||||
void closeConnection();
|
void closeConnection();
|
||||||
@@ -45,17 +44,15 @@ private:
|
|||||||
void handleProtocol(const QString &msg);
|
void handleProtocol(const QString &msg);
|
||||||
void broadcast(const QString &line);
|
void broadcast(const QString &line);
|
||||||
|
|
||||||
QLineEdit *m_urlEdit = nullptr;
|
QLineEdit *m_urlEdit = nullptr;
|
||||||
QLabel *m_statusLabel = nullptr;
|
QLabel *m_statusLabel = nullptr;
|
||||||
QWebSocket m_socket;
|
QWebSocket m_socket;
|
||||||
QList<QTextEdit *> m_logs;
|
QList<QTextEdit *> m_logs;
|
||||||
SettingsTree *m_settingsTree = nullptr;
|
SettingsTree *m_settingsTree = nullptr;
|
||||||
GamesPanel *m_gamesPanel = nullptr;
|
GamesPanel *m_gamesPanel = nullptr;
|
||||||
VersionsPanel *m_versionsPanel = nullptr;
|
VersionsPanel *m_versionsPanel = nullptr;
|
||||||
QStringList m_settingsKeys;
|
PowerPanel *m_powerPanel = nullptr;
|
||||||
int m_rnpCount = -1;
|
LogPanel *m_logPanel = nullptr;
|
||||||
|
QStringList m_settingsKeys;
|
||||||
bool m_gamesRequested = false;
|
int m_rnpCount = -1;
|
||||||
bool m_versionsRequested = false;
|
|
||||||
bool m_settingsRequested = false;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
/home/user/Qt/6.7.3/wasm_singlethread/bin/qt-cmake -S . -B build-wasm -G Ninja
|
/home/user/Qt/6.7.3/wasm_singlethread/bin/qt-cmake -S . -B build-wasm -G Ninja
|
||||||
cmake --build build-wasm
|
cmake --build build-wasm
|
||||||
|
|
||||||
|
cd build-wasm
|
||||||
|
python3 -m http.server 8000
|
||||||
|
|
||||||
|
|||||||
465
main.cpp
465
main.cpp
@@ -1,408 +1,189 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QTreeWidget>
|
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QMap>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
#include <QAbstractSocket>
|
#include "GamesPanel.h"
|
||||||
#include <QUrl>
|
#include "LogPanel.h"
|
||||||
#include <QWebSocket>
|
#include "PowerPanel.h"
|
||||||
|
#include "SettingsTree.h"
|
||||||
|
#include "VersionsPanel.h"
|
||||||
|
#include "WebSocketController.h"
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
static QWidget *makeGamesTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
// Settings tree
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
class SettingsTree : public QTreeWidget
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
auto *panel = new GamesPanel(parent);
|
||||||
public:
|
ctrl->setGamesPanel(panel);
|
||||||
// Emitted when user edits a value cell
|
return panel;
|
||||||
// key = full slash path, value = new string
|
}
|
||||||
Q_SIGNAL void valueEdited(const QString &key, const QString &value);
|
|
||||||
|
|
||||||
explicit SettingsTree(QWidget *parent = nullptr)
|
static QWidget *makeVersionsTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
: QTreeWidget(parent)
|
|
||||||
{
|
|
||||||
setColumnCount(2);
|
|
||||||
setHeaderLabels({"Key", "Value"});
|
|
||||||
header()->setSectionResizeMode(0, QHeaderView::Interactive);
|
|
||||||
header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
|
||||||
header()->setStretchLastSection(true);
|
|
||||||
setAlternatingRowColors(true);
|
|
||||||
setUniformRowHeights(true);
|
|
||||||
setAnimated(true);
|
|
||||||
setIndentation(16);
|
|
||||||
setColumnWidth(0, 240);
|
|
||||||
|
|
||||||
connect(this, &QTreeWidget::itemChanged,
|
|
||||||
this, &SettingsTree::onItemChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadKeys(const QStringList &keys)
|
|
||||||
{
|
|
||||||
m_loading = true;
|
|
||||||
clear();
|
|
||||||
m_itemMap.clear();
|
|
||||||
|
|
||||||
for (const QString &key : keys) {
|
|
||||||
const QStringList parts = key.split('/');
|
|
||||||
QTreeWidgetItem *parent = invisibleRootItem();
|
|
||||||
QString accumulated;
|
|
||||||
|
|
||||||
for (int i = 0; i < parts.size(); ++i) {
|
|
||||||
if (!accumulated.isEmpty())
|
|
||||||
accumulated += '/';
|
|
||||||
accumulated += parts[i];
|
|
||||||
|
|
||||||
if (!m_itemMap.contains(accumulated)) {
|
|
||||||
auto *item = new QTreeWidgetItem(parent);
|
|
||||||
item->setText(0, parts[i]);
|
|
||||||
item->setExpanded(false);
|
|
||||||
|
|
||||||
// Only leaf nodes (last part) are editable
|
|
||||||
const bool isLeaf = (i == parts.size() - 1);
|
|
||||||
if (isLeaf) {
|
|
||||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
|
||||||
item->setToolTip(1, "Double-click to edit and send");
|
|
||||||
} else {
|
|
||||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_itemMap[accumulated] = item;
|
|
||||||
}
|
|
||||||
parent = m_itemMap[accumulated];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeColumnToContents(0);
|
|
||||||
m_loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setValue(const QString &key, const QString &value)
|
|
||||||
{
|
|
||||||
if (!m_itemMap.contains(key)) return;
|
|
||||||
|
|
||||||
m_loading = true;
|
|
||||||
m_itemMap[key]->setText(1, value);
|
|
||||||
m_loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onItemChanged(QTreeWidgetItem *item, int column)
|
|
||||||
{
|
|
||||||
if (m_loading || column != 1) return;
|
|
||||||
|
|
||||||
// Find the full path for this item
|
|
||||||
const QString key = fullPath(item);
|
|
||||||
if (key.isEmpty()) return;
|
|
||||||
|
|
||||||
emit valueEdited(key, item->text(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString fullPath(QTreeWidgetItem *item) const
|
|
||||||
{
|
|
||||||
// Walk up the tree to reconstruct the full slash-separated path
|
|
||||||
QStringList parts;
|
|
||||||
QTreeWidgetItem *cur = item;
|
|
||||||
while (cur && cur != invisibleRootItem()) {
|
|
||||||
parts.prepend(cur->text(0));
|
|
||||||
cur = cur->parent();
|
|
||||||
}
|
|
||||||
return parts.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
QMap<QString, QTreeWidgetItem *> m_itemMap;
|
|
||||||
bool m_loading = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// WebSocket controller
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
class WebSocketController : public QObject
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
auto *panel = new VersionsPanel(parent);
|
||||||
public:
|
ctrl->setVersionsPanel(panel);
|
||||||
explicit WebSocketController(QLineEdit *urlEdit,
|
return panel;
|
||||||
QLabel *statusLabel,
|
}
|
||||||
QObject *parent = nullptr)
|
|
||||||
: QObject(parent), m_urlEdit(urlEdit), m_statusLabel(statusLabel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
QWebSocket *socket() { return &m_socket; }
|
static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
void addLogView(QTextEdit *l) { m_logs.append(l); }
|
|
||||||
void setSettingsTree(SettingsTree *t)
|
|
||||||
{
|
|
||||||
m_settingsTree = t;
|
|
||||||
// When user edits a value, send set command then verify
|
|
||||||
connect(t, &SettingsTree::valueEdited,
|
|
||||||
this, &WebSocketController::onValueEdited);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isConnected() const
|
|
||||||
{
|
|
||||||
return m_socket.state() == QAbstractSocket::ConnectedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void startConnection()
|
|
||||||
{
|
|
||||||
const QUrl url(m_urlEdit->text().trimmed());
|
|
||||||
if (!url.isValid()) { broadcast("Invalid URL"); return; }
|
|
||||||
broadcast(QString("Connecting to %1").arg(url.toString()));
|
|
||||||
m_statusLabel->setText("Connecting...");
|
|
||||||
m_socket.open(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeConnection()
|
|
||||||
{
|
|
||||||
broadcast("Closing connection");
|
|
||||||
m_settingsKeys.clear();
|
|
||||||
m_socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCommand(const QString &cmd)
|
|
||||||
{
|
|
||||||
if (!isConnected()) { broadcast("[not connected]"); return; }
|
|
||||||
m_socket.sendTextMessage(cmd);
|
|
||||||
broadcast(QString("TX: %1").arg(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onConnected()
|
|
||||||
{
|
|
||||||
broadcast("Connected");
|
|
||||||
m_statusLabel->setText("Connected");
|
|
||||||
sendCommand(QStringLiteral("GBL List"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDisconnected()
|
|
||||||
{
|
|
||||||
broadcast("Disconnected");
|
|
||||||
m_statusLabel->setText("Disconnected");
|
|
||||||
m_settingsKeys.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onTextMessageReceived(const QString &msg)
|
|
||||||
{
|
|
||||||
broadcast(QString("RX: %1").arg(msg));
|
|
||||||
handleProtocol(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onErrorOccurred(QAbstractSocket::SocketError)
|
|
||||||
{
|
|
||||||
broadcast(QString("Error: %1").arg(m_socket.errorString()));
|
|
||||||
m_statusLabel->setText("Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when user finishes editing a cell in the settings tree
|
|
||||||
void onValueEdited(const QString &key, const QString &newValue)
|
|
||||||
{
|
|
||||||
// Send: GBL key=newvalue
|
|
||||||
sendCommand(QString("GBL %1=%2").arg(key, newValue));
|
|
||||||
// Verify: GBL key (machine echoes back confirmed value)
|
|
||||||
sendCommand(QString("GBL %1").arg(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void handleProtocol(const QString &msg)
|
|
||||||
{
|
|
||||||
if (!m_settingsTree) return;
|
|
||||||
|
|
||||||
const QStringList tokens = msg.split(' ', Qt::SkipEmptyParts);
|
|
||||||
if (tokens.size() < 2) return;
|
|
||||||
|
|
||||||
// GBL List key1 key2 ...
|
|
||||||
if (tokens[0] == "GBL" && tokens[1] == "List" && tokens.size() > 2) {
|
|
||||||
m_settingsKeys = tokens.mid(2);
|
|
||||||
m_settingsTree->loadKeys(m_settingsKeys);
|
|
||||||
|
|
||||||
for (const QString &key : m_settingsKeys)
|
|
||||||
sendCommand(QString("GBL %1").arg(key));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GBL key=value
|
|
||||||
if (tokens[0] == "GBL" && tokens.size() >= 2) {
|
|
||||||
const QString payload = tokens[1];
|
|
||||||
const int eqIdx = payload.indexOf('=');
|
|
||||||
if (eqIdx > 0) {
|
|
||||||
const QString key = payload.left(eqIdx);
|
|
||||||
QString value = payload.mid(eqIdx + 1);
|
|
||||||
if (tokens.size() > 2)
|
|
||||||
value += ' ' + tokens.mid(2).join(' ');
|
|
||||||
m_settingsTree->setValue(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void broadcast(const QString &line)
|
|
||||||
{
|
|
||||||
for (auto *log : m_logs)
|
|
||||||
log->append(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
QLineEdit *m_urlEdit = nullptr;
|
|
||||||
QLabel *m_statusLabel = nullptr;
|
|
||||||
QWebSocket m_socket;
|
|
||||||
QList<QTextEdit *> m_logs;
|
|
||||||
SettingsTree *m_settingsTree = nullptr;
|
|
||||||
QStringList m_settingsKeys;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Tab builders
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
static QWidget *makeSettingsTab(WebSocketController *controller, QWidget *parent = nullptr)
|
|
||||||
{
|
{
|
||||||
auto *page = new QWidget(parent);
|
auto *page = new QWidget(parent);
|
||||||
auto *layout = new QVBoxLayout(page);
|
auto *layout = new QVBoxLayout(page);
|
||||||
layout->setContentsMargins(2, 2, 2, 2);
|
layout->setContentsMargins(2, 2, 2, 2);
|
||||||
|
|
||||||
auto *tree = new SettingsTree(page);
|
auto *tree = new SettingsTree(page);
|
||||||
auto *placeholder = new QTreeWidgetItem(tree);
|
auto *ph = new QTreeWidgetItem(tree);
|
||||||
placeholder->setText(0, "Connect to load settings...");
|
ph->setText(0, "Connect to load settings...");
|
||||||
|
|
||||||
layout->addWidget(tree, 1);
|
layout->addWidget(tree, 1);
|
||||||
controller->setSettingsTree(tree);
|
ctrl->setSettingsTree(tree);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QWidget *makeManualTab(WebSocketController *controller, QWidget *parent = nullptr)
|
static QWidget *makeManualTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
{
|
{
|
||||||
auto *page = new QWidget(parent);
|
auto *page = new QWidget(parent);
|
||||||
auto *layout = new QVBoxLayout(page);
|
auto *layout = new QVBoxLayout(page);
|
||||||
|
auto *row = new QHBoxLayout();
|
||||||
auto *sendRow = new QHBoxLayout();
|
|
||||||
auto *cmdEdit = new QLineEdit(page);
|
auto *cmdEdit = new QLineEdit(page);
|
||||||
cmdEdit->setPlaceholderText("Enter command to send...");
|
cmdEdit->setPlaceholderText("Enter command to send...");
|
||||||
auto *sendBtn = new QPushButton("Send", page);
|
auto *sendBtn = new QPushButton("Send", page);
|
||||||
|
auto *clearBtn = new QPushButton("Clear", page);
|
||||||
|
row->addWidget(new QLabel("Command:", page));
|
||||||
|
row->addWidget(cmdEdit, 1);
|
||||||
|
row->addWidget(sendBtn);
|
||||||
|
row->addWidget(clearBtn);
|
||||||
|
auto *log = new QTextEdit(page);
|
||||||
|
log->setReadOnly(true);
|
||||||
|
layout->addLayout(row);
|
||||||
|
layout->addWidget(log, 1);
|
||||||
|
ctrl->addLogView(log);
|
||||||
|
|
||||||
sendRow->addWidget(new QLabel("Command:", page));
|
auto send = [cmdEdit, ctrl]() {
|
||||||
sendRow->addWidget(cmdEdit, 1);
|
|
||||||
sendRow->addWidget(sendBtn);
|
|
||||||
|
|
||||||
auto *rxLog = new QTextEdit(page);
|
|
||||||
rxLog->setReadOnly(true);
|
|
||||||
rxLog->setPlaceholderText("Received messages will appear here...");
|
|
||||||
|
|
||||||
layout->addLayout(sendRow);
|
|
||||||
layout->addWidget(rxLog, 1);
|
|
||||||
controller->addLogView(rxLog);
|
|
||||||
|
|
||||||
auto send = [cmdEdit, controller]() {
|
|
||||||
const QString cmd = cmdEdit->text().trimmed();
|
const QString cmd = cmdEdit->text().trimmed();
|
||||||
if (!cmd.isEmpty()) {
|
if (!cmd.isEmpty()) { ctrl->sendCommand(cmd); cmdEdit->clear(); }
|
||||||
controller->sendCommand(cmd);
|
|
||||||
cmdEdit->clear();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
QObject::connect(sendBtn, &QPushButton::clicked, page, send);
|
||||||
QObject::connect(sendBtn, &QPushButton::clicked, page, send);
|
QObject::connect(cmdEdit, &QLineEdit::returnPressed, page, send);
|
||||||
QObject::connect(cmdEdit, &QLineEdit::returnPressed, page, send);
|
QObject::connect(clearBtn, &QPushButton::clicked, log, &QTextEdit::clear);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QWidget *makePanelsTab(WebSocketController *controller, QWidget *parent = nullptr)
|
static QWidget *makePowerTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
|
{
|
||||||
|
auto *panel = new PowerPanel(parent);
|
||||||
|
ctrl->setPowerPanel(panel);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QWidget *makeLogsTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
|
{
|
||||||
|
auto *panel = new LogPanel(ctrl, parent);
|
||||||
|
ctrl->setLogPanel(panel);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QWidget *makePanelsTab(WebSocketController *ctrl, QWidget *parent)
|
||||||
{
|
{
|
||||||
auto *page = new QWidget(parent);
|
auto *page = new QWidget(parent);
|
||||||
auto *layout = new QVBoxLayout(page);
|
auto *layout = new QVBoxLayout(page);
|
||||||
auto *log = new QTextEdit(page);
|
auto *log = new QTextEdit(page);
|
||||||
log->setReadOnly(true);
|
log->setReadOnly(true);
|
||||||
layout->addWidget(log, 1);
|
layout->addWidget(log, 1);
|
||||||
controller->addLogView(log);
|
ctrl->addLogView(log);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QWidget *makePlaceholder(const QString &text, QWidget *parent = nullptr)
|
|
||||||
{
|
|
||||||
auto *page = new QWidget(parent);
|
|
||||||
auto *layout = new QVBoxLayout(page);
|
|
||||||
auto *label = new QLabel(text, page);
|
|
||||||
label->setAlignment(Qt::AlignCenter);
|
|
||||||
layout->addWidget(label);
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// main
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
QWidget window;
|
QWidget window;
|
||||||
window.setWindowTitle("Qt WASM WebSocket Client");
|
window.setWindowTitle("ESA WebSocket Client");
|
||||||
|
|
||||||
auto *mainLayout = new QVBoxLayout(&window);
|
auto *mainLayout = new QVBoxLayout(&window);
|
||||||
mainLayout->setContentsMargins(6, 6, 6, 6);
|
mainLayout->setContentsMargins(6, 6, 6, 6);
|
||||||
mainLayout->setSpacing(4);
|
mainLayout->setSpacing(4);
|
||||||
|
|
||||||
auto *headerLayout = new QHBoxLayout();
|
// --- Header bar ---
|
||||||
auto *urlLabel = new QLabel("WebSocket URL:", &window);
|
auto *headerRow = new QHBoxLayout();
|
||||||
auto *urlEdit = new QLineEdit(&window);
|
auto *urlEdit = new QLineEdit(&window);
|
||||||
urlEdit->setPlaceholderText("ws://127.0.0.1:3491/");
|
urlEdit->setPlaceholderText("ws://127.0.0.1:3491/");
|
||||||
urlEdit->setText("ws://127.0.0.1:3491/");
|
urlEdit->setText("ws://127.0.0.1:3491/");
|
||||||
|
auto *connectBtn = new QPushButton("Connect", &window);
|
||||||
|
auto *disconnectBtn = new QPushButton("Disconnect", &window);
|
||||||
|
auto *shutdownBtn = new QPushButton("Shutdown", &window);
|
||||||
|
disconnectBtn->setEnabled(false);
|
||||||
|
shutdownBtn->setEnabled(false);
|
||||||
|
shutdownBtn->setStyleSheet(
|
||||||
|
"QPushButton { color: white; background-color: #c62828;"
|
||||||
|
" border-radius: 4px; padding: 3px 10px; }"
|
||||||
|
"QPushButton:hover { background-color: #b71c1c; }"
|
||||||
|
"QPushButton:disabled { background-color: #888888; color: #cccccc; }");
|
||||||
|
auto *statusLabel = new QLabel("Disconnected", &window);
|
||||||
|
|
||||||
auto *connectButton = new QPushButton("Connect", &window);
|
headerRow->addWidget(new QLabel("WebSocket URL:", &window));
|
||||||
auto *disconnectButton = new QPushButton("Disconnect", &window);
|
headerRow->addWidget(urlEdit, 1);
|
||||||
disconnectButton->setEnabled(false);
|
headerRow->addWidget(connectBtn);
|
||||||
auto *statusLabel = new QLabel("Disconnected", &window);
|
headerRow->addWidget(disconnectBtn);
|
||||||
|
headerRow->addWidget(shutdownBtn);
|
||||||
|
headerRow->addWidget(statusLabel);
|
||||||
|
mainLayout->addLayout(headerRow);
|
||||||
|
|
||||||
headerLayout->addWidget(urlLabel);
|
// --- Controller ---
|
||||||
headerLayout->addWidget(urlEdit, 1);
|
auto *ctrl = new WebSocketController(urlEdit, statusLabel, &window);
|
||||||
headerLayout->addWidget(connectButton);
|
|
||||||
headerLayout->addWidget(disconnectButton);
|
|
||||||
headerLayout->addWidget(statusLabel);
|
|
||||||
mainLayout->addLayout(headerLayout);
|
|
||||||
|
|
||||||
auto *controller = new WebSocketController(urlEdit, statusLabel, &window);
|
|
||||||
|
|
||||||
|
// --- Tabs ---
|
||||||
auto *tabs = new QTabWidget(&window);
|
auto *tabs = new QTabWidget(&window);
|
||||||
tabs->addTab(makePlaceholder("Games content goes here"), "games");
|
tabs->addTab(makeGamesTab (ctrl, &window), "games");
|
||||||
tabs->addTab(makePlaceholder("Versions content goes here"), "versions");
|
tabs->addTab(makeVersionsTab(ctrl, &window), "versions");
|
||||||
tabs->addTab(makeManualTab(controller, &window), "manual");
|
tabs->addTab(makeManualTab (ctrl, &window), "manual");
|
||||||
tabs->addTab(makeSettingsTab(controller, &window), "settings");
|
tabs->addTab(makeSettingsTab(ctrl, &window), "settings");
|
||||||
tabs->addTab(makePlaceholder("Power content goes here"), "power");
|
tabs->addTab(makePowerTab (ctrl, &window), "power");
|
||||||
tabs->addTab(makePanelsTab(controller, &window), "panels");
|
tabs->addTab(makeLogsTab (ctrl, &window), "logs");
|
||||||
|
tabs->addTab(makePanelsTab (ctrl, &window), "panels");
|
||||||
mainLayout->addWidget(tabs, 1);
|
mainLayout->addWidget(tabs, 1);
|
||||||
|
|
||||||
QObject::connect(connectButton, &QPushButton::clicked,
|
// --- Shutdown: confirm then send ---
|
||||||
controller, &WebSocketController::startConnection);
|
QObject::connect(shutdownBtn, &QPushButton::clicked, &window, [ctrl, &window]() {
|
||||||
QObject::connect(disconnectButton, &QPushButton::clicked,
|
QMessageBox msgBox(&window);
|
||||||
controller, &WebSocketController::closeConnection);
|
msgBox.setWindowTitle("Confirm Shutdown");
|
||||||
|
msgBox.setText("Are you sure you want to shut down the machine?");
|
||||||
QObject::connect(controller->socket(), &QWebSocket::connected,
|
msgBox.setInformativeText("This will send the POW ShutDown command immediately.");
|
||||||
controller, &WebSocketController::onConnected);
|
msgBox.setIcon(QMessageBox::Warning);
|
||||||
QObject::connect(controller->socket(), &QWebSocket::disconnected,
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
|
||||||
controller, &WebSocketController::onDisconnected);
|
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||||
QObject::connect(controller->socket(), &QWebSocket::textMessageReceived,
|
if (msgBox.exec() == QMessageBox::Yes)
|
||||||
controller, &WebSocketController::onTextMessageReceived);
|
ctrl->sendCommand(QStringLiteral("POW ShutDown"));
|
||||||
QObject::connect(controller->socket(), &QWebSocket::errorOccurred,
|
|
||||||
controller, &WebSocketController::onErrorOccurred);
|
|
||||||
|
|
||||||
QObject::connect(controller->socket(), &QWebSocket::connected, &window,
|
|
||||||
[connectButton, disconnectButton]() {
|
|
||||||
connectButton->setEnabled(false);
|
|
||||||
disconnectButton->setEnabled(true);
|
|
||||||
});
|
|
||||||
QObject::connect(controller->socket(), &QWebSocket::disconnected, &window,
|
|
||||||
[connectButton, disconnectButton]() {
|
|
||||||
connectButton->setEnabled(true);
|
|
||||||
disconnectButton->setEnabled(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.resize(950, 580);
|
// --- Wire up buttons ---
|
||||||
|
QObject::connect(connectBtn, &QPushButton::clicked, ctrl, &WebSocketController::startConnection);
|
||||||
|
QObject::connect(disconnectBtn, &QPushButton::clicked, ctrl, &WebSocketController::closeConnection);
|
||||||
|
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::connected, ctrl, &WebSocketController::onConnected);
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::disconnected, ctrl, &WebSocketController::onDisconnected);
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, ctrl, &WebSocketController::onTextMessageReceived);
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::errorOccurred, ctrl, &WebSocketController::onErrorOccurred);
|
||||||
|
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::connected, &window,
|
||||||
|
[connectBtn, disconnectBtn, shutdownBtn]() {
|
||||||
|
connectBtn->setEnabled(false);
|
||||||
|
disconnectBtn->setEnabled(true);
|
||||||
|
shutdownBtn->setEnabled(true);
|
||||||
|
});
|
||||||
|
QObject::connect(ctrl->socket(), &QWebSocket::disconnected, &window,
|
||||||
|
[connectBtn, disconnectBtn, shutdownBtn]() {
|
||||||
|
connectBtn->setEnabled(true);
|
||||||
|
disconnectBtn->setEnabled(false);
|
||||||
|
shutdownBtn->setEnabled(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.resize(1050, 620);
|
||||||
window.show();
|
window.show();
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "main.moc"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user