From 21a3c35876388633e17d75e6666c2362ea8b3fcf Mon Sep 17 00:00:00 2001 From: Jon ESA Date: Wed, 1 Apr 2026 19:37:12 +0100 Subject: [PATCH] First test of QT Remote Monitor WASM --- CMakeLists.txt | 21 +++++ WebSocketController.cpp | 176 ++++++++++++++++++++++++++++++++++++++++ build-run.sh | 3 + main.cpp | 151 ++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 WebSocketController.cpp create mode 100644 build-run.sh create mode 100644 main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1c52422 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.16) +project(wsapp VERSION 1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Widgets WebSockets) + +add_executable(wsapp + main.cpp + GamesPanel.cpp GamesPanel.h + SettingsTree.cpp SettingsTree.h + VersionsPanel.cpp VersionsPanel.h + WebSocketController.cpp WebSocketController.h +) + +target_link_libraries(wsapp PRIVATE + Qt6::Core + Qt6::Widgets + Qt6::WebSockets +) diff --git a/WebSocketController.cpp b/WebSocketController.cpp new file mode 100644 index 0000000..68217f3 --- /dev/null +++ b/WebSocketController.cpp @@ -0,0 +1,176 @@ +#include "WebSocketController.h" +#include "GamesPanel.h" +#include "SettingsTree.h" +#include "VersionsPanel.h" + +#include +#include +#include +#include + +WebSocketController::WebSocketController(QLineEdit *urlEdit, + QLabel *statusLabel, + QObject *parent) + : QObject(parent), m_urlEdit(urlEdit), m_statusLabel(statusLabel) +{} + +QWebSocket *WebSocketController::socket() { return &m_socket; } + +void WebSocketController::addLogView(QTextEdit *log) { m_logs.append(log); } + +void WebSocketController::setSettingsTree(SettingsTree *tree) +{ + m_settingsTree = tree; + connect(tree, &SettingsTree::valueEdited, + this, &WebSocketController::onValueEdited); +} + +void WebSocketController::setGamesPanel(GamesPanel *panel) { m_gamesPanel = panel; } +void WebSocketController::setVersionsPanel(VersionsPanel *panel) { m_versionsPanel = panel; } + +bool WebSocketController::isConnected() const +{ + return m_socket.state() == QAbstractSocket::ConnectedState; +} + +void WebSocketController::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 WebSocketController::closeConnection() +{ + broadcast("Closing connection"); + m_settingsKeys.clear(); + m_rnpCount = -1; + if (m_versionsPanel) m_versionsPanel->reset(); + m_socket.close(); +} + +void WebSocketController::sendCommand(const QString &cmd) +{ + if (!isConnected()) { broadcast("[not connected]"); return; } + m_socket.sendTextMessage(cmd); + broadcast(QString("TX: %1").arg(cmd)); +} + +void WebSocketController::onConnected() +{ + broadcast("Connected"); + m_statusLabel->setText("Connected"); + m_rnpCount = -1; + + sendCommand(QStringLiteral("GBL List")); + sendCommand(QStringLiteral("GAM list")); + sendCommand(QStringLiteral("NAM")); + sendCommand(QStringLiteral("VER")); + sendCommand(QStringLiteral("UID")); + sendCommand(QStringLiteral("RNP")); +} + +void WebSocketController::onDisconnected() +{ + broadcast("Disconnected"); + m_statusLabel->setText("Disconnected"); + m_settingsKeys.clear(); + m_rnpCount = -1; +} + +void WebSocketController::onTextMessageReceived(const QString &msg) +{ + broadcast(QString("RX: %1").arg(msg)); + handleProtocol(msg); +} + +void WebSocketController::onErrorOccurred(QAbstractSocket::SocketError) +{ + broadcast(QString("Error: %1").arg(m_socket.errorString())); + m_statusLabel->setText("Error"); +} + +void WebSocketController::onValueEdited(const QString &key, const QString &newValue) +{ + sendCommand(QString("GBL %1=%2").arg(key, newValue)); + sendCommand(QString("GBL %1").arg(key)); +} + +void WebSocketController::handleProtocol(const QString &msg) +{ + const QStringList tokens = msg.split(' ', Qt::SkipEmptyParts); + if (tokens.isEmpty()) return; + const QString cmd = tokens[0]; + + // NAM + if (cmd == "NAM" && tokens.size() >= 2) { + if (m_versionsPanel) m_versionsPanel->setDeviceName(tokens[1]); + return; + } + + // VER M or VER + if (cmd == "VER" && tokens.size() >= 3) { + if (m_versionsPanel) + m_versionsPanel->setVersion(tokens[1], tokens.mid(2).join(' ')); + return; + } + + // UID M or UID + if (cmd == "UID" && tokens.size() >= 3) { + if (m_versionsPanel) + m_versionsPanel->setUid(tokens[1], tokens.mid(2).join(' ')); + return; + } + + // RNP + if (cmd == "RNP" && tokens.size() >= 2) { + bool ok = false; + const int count = tokens[1].toInt(&ok); + if (ok && count > 0) { + m_rnpCount = count; + if (m_versionsPanel) m_versionsPanel->setRnpCount(count); + for (int i = 0; i < count; ++i) { + sendCommand(QString("VER %1").arg(i)); + sendCommand(QString("UID %1").arg(i)); + } + } + return; + } + + // GAM list ... + if (cmd == "GAM" && tokens.size() >= 2 && tokens[1] == "list") { + if (m_gamesPanel) m_gamesPanel->loadFromResponse(msg); + return; + } + + // GBL List key1 key2 ... + if (cmd == "GBL" && tokens.size() > 2 && tokens[1] == "List") { + m_settingsKeys = tokens.mid(2); + if (m_settingsTree) { + m_settingsTree->loadKeys(m_settingsKeys); + for (const QString &key : m_settingsKeys) + sendCommand(QString("GBL %1").arg(key)); + } + return; + } + + // GBL key=value + if (cmd == "GBL" && tokens.size() >= 2 && m_settingsTree) { + 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 WebSocketController::broadcast(const QString &line) +{ + for (auto *log : m_logs) + log->append(line); +} diff --git a/build-run.sh b/build-run.sh new file mode 100644 index 0000000..c4c74c1 --- /dev/null +++ b/build-run.sh @@ -0,0 +1,3 @@ +/home/user/Qt/6.7.3/wasm_singlethread/bin/qt-cmake -S . -B build-wasm -G Ninja +cmake --build build-wasm + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..51ae8da --- /dev/null +++ b/main.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GamesPanel.h" +#include "SettingsTree.h" +#include "VersionsPanel.h" +#include "WebSocketController.h" + +static QWidget *makeGamesTab(WebSocketController *ctrl, QWidget *parent) +{ + auto *panel = new GamesPanel(parent); + ctrl->setGamesPanel(panel); + return panel; +} + +static QWidget *makeVersionsTab(WebSocketController *ctrl, QWidget *parent) +{ + auto *panel = new VersionsPanel(parent); + ctrl->setVersionsPanel(panel); + return panel; +} + +static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) +{ + auto *page = new QWidget(parent); + auto *layout = new QVBoxLayout(page); + layout->setContentsMargins(2, 2, 2, 2); + auto *tree = new SettingsTree(page); + auto *ph = new QTreeWidgetItem(tree); + ph->setText(0, "Connect to load settings..."); + layout->addWidget(tree, 1); + ctrl->setSettingsTree(tree); + return page; +} + +static QWidget *makeManualTab(WebSocketController *ctrl, QWidget *parent) +{ + auto *page = new QWidget(parent); + auto *layout = new QVBoxLayout(page); + auto *row = new QHBoxLayout(); + auto *cmdEdit = new QLineEdit(page); + cmdEdit->setPlaceholderText("Enter command to send..."); + auto *sendBtn = new QPushButton("Send", page); + row->addWidget(new QLabel("Command:", page)); + row->addWidget(cmdEdit, 1); + row->addWidget(sendBtn); + auto *log = new QTextEdit(page); + log->setReadOnly(true); + layout->addLayout(row); + layout->addWidget(log, 1); + ctrl->addLogView(log); + + auto send = [cmdEdit, ctrl]() { + const QString cmd = cmdEdit->text().trimmed(); + if (!cmd.isEmpty()) { ctrl->sendCommand(cmd); cmdEdit->clear(); } + }; + QObject::connect(sendBtn, &QPushButton::clicked, page, send); + QObject::connect(cmdEdit, &QLineEdit::returnPressed, page, send); + return page; +} + +static QWidget *makePanelsTab(WebSocketController *ctrl, QWidget *parent) +{ + auto *page = new QWidget(parent); + auto *layout = new QVBoxLayout(page); + auto *log = new QTextEdit(page); + log->setReadOnly(true); + layout->addWidget(log, 1); + ctrl->addLogView(log); + return page; +} + +static QWidget *makePlaceholder(const QString &text, QWidget *parent) +{ + 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; +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QWidget window; + window.setWindowTitle("ESA WebSocket Client"); + + auto *mainLayout = new QVBoxLayout(&window); + mainLayout->setContentsMargins(6, 6, 6, 6); + mainLayout->setSpacing(4); + + // --- Header bar --- + auto *headerRow = new QHBoxLayout(); + auto *urlEdit = new QLineEdit(&window); + urlEdit->setPlaceholderText("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); + disconnectBtn->setEnabled(false); + auto *statusLabel = new QLabel("Disconnected", &window); + + headerRow->addWidget(new QLabel("WebSocket URL:", &window)); + headerRow->addWidget(urlEdit, 1); + headerRow->addWidget(connectBtn); + headerRow->addWidget(disconnectBtn); + headerRow->addWidget(statusLabel); + mainLayout->addLayout(headerRow); + + // --- Controller --- + auto *ctrl = new WebSocketController(urlEdit, statusLabel, &window); + + // --- Tabs --- + auto *tabs = new QTabWidget(&window); + tabs->addTab(makeGamesTab (ctrl, &window), "games"); + tabs->addTab(makeVersionsTab(ctrl, &window), "versions"); + tabs->addTab(makeManualTab (ctrl, &window), "manual"); + tabs->addTab(makeSettingsTab(ctrl, &window), "settings"); + tabs->addTab(makePlaceholder("Power", &window), "power"); + tabs->addTab(makePanelsTab (ctrl, &window), "panels"); + mainLayout->addWidget(tabs, 1); + + // --- 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]() { + connectBtn->setEnabled(false); disconnectBtn->setEnabled(true); + }); + QObject::connect(ctrl->socket(), &QWebSocket::disconnected, &window, [connectBtn, disconnectBtn]() { + connectBtn->setEnabled(true); disconnectBtn->setEnabled(false); + }); + + window.resize(1050, 620); + window.show(); + return app.exec(); +}