- TAG [uartscopeclean] eingefügt, über den alle Logs gelöscht werden
- Pauseknopf und Eingabezeile hinzugefügt, um Kommandos an den Pi zu schicken
This commit is contained in:
@@ -26,6 +26,7 @@ set(SOURCES
|
|||||||
src/connectdialog.cpp
|
src/connectdialog.cpp
|
||||||
src/v4l2worker.cpp
|
src/v4l2worker.cpp
|
||||||
src/videowidget.cpp
|
src/videowidget.cpp
|
||||||
|
src/sendbar.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
@@ -38,6 +39,7 @@ set(HEADERS
|
|||||||
include/connectdialog.h
|
include/connectdialog.h
|
||||||
include/v4l2worker.h
|
include/v4l2worker.h
|
||||||
include/videowidget.h
|
include/videowidget.h
|
||||||
|
include/sendbar.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt6_add_resources(RESOURCES resources.qrc)
|
qt6_add_resources(RESOURCES resources.qrc)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "tagwidget.h"
|
#include "tagwidget.h"
|
||||||
#include "videowidget.h"
|
#include "videowidget.h"
|
||||||
#include "connectdialog.h"
|
#include "connectdialog.h"
|
||||||
|
#include "sendbar.h"
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@@ -64,6 +65,7 @@ private:
|
|||||||
TableView *m_tableView = nullptr;
|
TableView *m_tableView = nullptr;
|
||||||
TagWidget *m_tagWidget = nullptr;
|
TagWidget *m_tagWidget = nullptr;
|
||||||
VideoWidget *m_videoWidget= nullptr;
|
VideoWidget *m_videoWidget= nullptr;
|
||||||
|
SendBar *m_sendBar = nullptr;
|
||||||
|
|
||||||
QLabel *m_portLabel = nullptr;
|
QLabel *m_portLabel = nullptr;
|
||||||
QLabel *m_stateLabel = nullptr;
|
QLabel *m_stateLabel = nullptr;
|
||||||
|
|||||||
78
include/sendbar.h
Normal file
78
include/sendbar.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QPoint>
|
||||||
|
|
||||||
|
// SendBar sits below the Raw/Table view and the HDMI preview and lets the
|
||||||
|
// user talk BACK to the firmware over UART/TCP, for debugging:
|
||||||
|
//
|
||||||
|
// [ Pause ] [ ...free-text input...................... ] [Senden] [ending v]
|
||||||
|
//
|
||||||
|
// - The left button sends a fixed, user-configurable byte sequence (right-
|
||||||
|
// click it to change the label and the code - e.g. a single control byte
|
||||||
|
// or a short command string). Handy for a "pause"/"step" command that's
|
||||||
|
// sent identically every time.
|
||||||
|
// - The input line sends whatever is typed the moment Enter is pressed
|
||||||
|
// (or the "Senden" button is clicked), with a selectable line ending
|
||||||
|
// appended.
|
||||||
|
//
|
||||||
|
// SendBar itself never touches the serial device - it only emits
|
||||||
|
// sendRequested() with the raw bytes to write; MainWindow forwards those to
|
||||||
|
// SerialWorker::writeData().
|
||||||
|
class SendBar : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SendBar(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
// Persisted configuration accessors, used by MainWindow::save/restoreSettings.
|
||||||
|
QString buttonLabel() const { return m_buttonLabel; }
|
||||||
|
QString buttonCode() const { return m_buttonCode; } // raw, as typed (with escapes)
|
||||||
|
int lineEndingIndex() const;
|
||||||
|
|
||||||
|
void setButtonLabel(const QString &label);
|
||||||
|
void setButtonCode(const QString &code);
|
||||||
|
void setLineEndingIndex(int index);
|
||||||
|
|
||||||
|
// Turns C-style escapes (\n \r \t \0 \\ and \xHH) in `text` into the
|
||||||
|
// actual bytes they represent. Exposed as static so it can be reused/
|
||||||
|
// unit-tested independently of the widget.
|
||||||
|
static QByteArray unescape(const QString &text);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
// Enables/disables the send controls - there is no point letting the
|
||||||
|
// user try to send anything while nothing is connected.
|
||||||
|
void setConnected(bool connected);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Raw bytes ready to be written to the serial/network device.
|
||||||
|
void sendRequested(const QByteArray &data);
|
||||||
|
// The button label/code or the line-ending choice changed - MainWindow
|
||||||
|
// listens to this to persist it immediately, consistent with how the
|
||||||
|
// Tag filter and Screenshot folder dialogs already behave.
|
||||||
|
void configChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPauseClicked();
|
||||||
|
void onPauseContextMenu(const QPoint &pos);
|
||||||
|
void onSendClicked();
|
||||||
|
void configureButton();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
void updatePauseButtonUi();
|
||||||
|
void sendInputLine();
|
||||||
|
|
||||||
|
QPushButton *m_pauseBtn = nullptr;
|
||||||
|
QLineEdit *m_inputEdit = nullptr;
|
||||||
|
QPushButton *m_sendBtn = nullptr;
|
||||||
|
QComboBox *m_endingCombo = nullptr;
|
||||||
|
|
||||||
|
QString m_buttonLabel = tr("Pause");
|
||||||
|
QString m_buttonCode = QStringLiteral("PAUSE\\n");
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
// SerialWorker lives in its own QThread and owns the actual data source,
|
// SerialWorker lives in its own QThread and owns the actual data source,
|
||||||
@@ -44,6 +45,10 @@ public slots:
|
|||||||
void closePort();
|
void closePort();
|
||||||
void setLogFile(const QString &path);
|
void setLogFile(const QString &path);
|
||||||
void stopLogging();
|
void stopLogging();
|
||||||
|
// Writes raw bytes back out to the currently open device (serial port
|
||||||
|
// or TCP socket) - used by the send bar (Pause button / input line) to
|
||||||
|
// talk back to the firmware. No-op if nothing is currently open.
|
||||||
|
void writeData(const QByteArray &data);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void newLine(const QString &line);
|
void newLine(const QString &line);
|
||||||
@@ -53,6 +58,12 @@ signals:
|
|||||||
// history too). `filename` is whatever followed the tag, verbatim -
|
// history too). `filename` is whatever followed the tag, verbatim -
|
||||||
// the receiver is responsible for sanitizing/defaulting it.
|
// the receiver is responsible for sanitizing/defaulting it.
|
||||||
void screenshotRequested(const QString &filename);
|
void screenshotRequested(const QString &filename);
|
||||||
|
// Fired specifically for a [UARTSCOPECLEAR] control tag (in addition to
|
||||||
|
// the normal tagDetected() above): the firmware asks for a completely
|
||||||
|
// fresh UARTScope state, e.g. right at boot. Handled the same as an
|
||||||
|
// ANSI clear-screen - wipes Raw view, Table view, Tag Monitor and the
|
||||||
|
// video preview frame.
|
||||||
|
void uartscopeClearRequested();
|
||||||
void clearScreen();
|
void clearScreen();
|
||||||
void portOpened();
|
void portOpened();
|
||||||
void portClosed();
|
void portClosed();
|
||||||
|
|||||||
@@ -38,11 +38,24 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
connect(m_worker, &SerialWorker::tagDetected, this, &MainWindow::onTagDetected);
|
connect(m_worker, &SerialWorker::tagDetected, this, &MainWindow::onTagDetected);
|
||||||
connect(m_worker, &SerialWorker::screenshotRequested, this, &MainWindow::onScreenshotRequested);
|
connect(m_worker, &SerialWorker::screenshotRequested, this, &MainWindow::onScreenshotRequested);
|
||||||
connect(m_worker, &SerialWorker::clearScreen, this, &MainWindow::onClearScreen);
|
connect(m_worker, &SerialWorker::clearScreen, this, &MainWindow::onClearScreen);
|
||||||
|
// [UARTSCOPECLEAR] control tag from the firmware: wipe everything, same
|
||||||
|
// as an ANSI clear-screen - lets a baremetal project guarantee a fresh
|
||||||
|
// UARTScope state right when it boots.
|
||||||
|
connect(m_worker, &SerialWorker::uartscopeClearRequested, this, &MainWindow::clearAllViews);
|
||||||
connect(m_worker, &SerialWorker::portOpened, this, &MainWindow::onPortOpened);
|
connect(m_worker, &SerialWorker::portOpened, this, &MainWindow::onPortOpened);
|
||||||
connect(m_worker, &SerialWorker::portClosed, this, &MainWindow::onPortClosed);
|
connect(m_worker, &SerialWorker::portClosed, this, &MainWindow::onPortClosed);
|
||||||
connect(m_worker, &SerialWorker::reconnecting, this, &MainWindow::onReconnecting);
|
connect(m_worker, &SerialWorker::reconnecting, this, &MainWindow::onReconnecting);
|
||||||
connect(m_worker, &SerialWorker::errorOccurred, this, &MainWindow::onError);
|
connect(m_worker, &SerialWorker::errorOccurred, this, &MainWindow::onError);
|
||||||
|
|
||||||
|
// Send bar (Pause button + input line): forward whatever it wants sent
|
||||||
|
// to the worker thread. Queued since m_worker lives in m_thread.
|
||||||
|
connect(m_sendBar, &SendBar::sendRequested, this, [this](const QByteArray &data) {
|
||||||
|
QMetaObject::invokeMethod(m_worker, "writeData",
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
Q_ARG(QByteArray, data));
|
||||||
|
});
|
||||||
|
connect(m_sendBar, &SendBar::configChanged, this, &MainWindow::saveSettings);
|
||||||
|
|
||||||
m_thread->start();
|
m_thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +92,16 @@ void MainWindow::doShutdown()
|
|||||||
|
|
||||||
void MainWindow::setupUi()
|
void MainWindow::setupUi()
|
||||||
{
|
{
|
||||||
m_mainSplitter = new QSplitter(Qt::Horizontal, this);
|
// Central widget: the existing raw/table/tag/video splitter on top,
|
||||||
|
// plus the send bar (Pause button + input line) spanning the full
|
||||||
|
// width underneath - below the log AND the HDMI preview, not tucked
|
||||||
|
// into just one side.
|
||||||
|
auto *central = new QWidget(this);
|
||||||
|
auto *centralLayout = new QVBoxLayout(central);
|
||||||
|
centralLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
centralLayout->setSpacing(0);
|
||||||
|
|
||||||
|
m_mainSplitter = new QSplitter(Qt::Horizontal, central);
|
||||||
m_mainSplitter->setHandleWidth(5);
|
m_mainSplitter->setHandleWidth(5);
|
||||||
auto *mainSplitter = m_mainSplitter;
|
auto *mainSplitter = m_mainSplitter;
|
||||||
|
|
||||||
@@ -137,7 +159,12 @@ void MainWindow::setupUi()
|
|||||||
mainSplitter->setStretchFactor(0, 3);
|
mainSplitter->setStretchFactor(0, 3);
|
||||||
mainSplitter->setStretchFactor(1, 1);
|
mainSplitter->setStretchFactor(1, 1);
|
||||||
|
|
||||||
setCentralWidget(mainSplitter);
|
centralLayout->addWidget(mainSplitter, 1);
|
||||||
|
|
||||||
|
m_sendBar = new SendBar(central);
|
||||||
|
centralLayout->addWidget(m_sendBar);
|
||||||
|
|
||||||
|
setCentralWidget(central);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setupToolBar()
|
void MainWindow::setupToolBar()
|
||||||
@@ -298,6 +325,8 @@ void MainWindow::onPortOpened()
|
|||||||
.arg(m_lastConfig.baudRate));
|
.arg(m_lastConfig.baudRate));
|
||||||
}
|
}
|
||||||
statusBar()->clearMessage();
|
statusBar()->clearMessage();
|
||||||
|
if (m_sendBar)
|
||||||
|
m_sendBar->setConnected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onPortClosed()
|
void MainWindow::onPortClosed()
|
||||||
@@ -307,6 +336,8 @@ void MainWindow::onPortClosed()
|
|||||||
m_stateLabel->setText(tr("○ Disconnected"));
|
m_stateLabel->setText(tr("○ Disconnected"));
|
||||||
m_stateLabel->setStyleSheet(QString());
|
m_stateLabel->setStyleSheet(QString());
|
||||||
m_portLabel->setText(QString());
|
m_portLabel->setText(QString());
|
||||||
|
if (m_sendBar)
|
||||||
|
m_sendBar->setConnected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onReconnecting(int attempt)
|
void MainWindow::onReconnecting(int attempt)
|
||||||
@@ -381,6 +412,13 @@ tag monitor, and the video preview frame):
|
|||||||
|
|
||||||
uart_printf("\033[2J\033[H"); // standard VT100 clear-screen + cursor home
|
uart_printf("\033[2J\033[H"); // standard VT100 clear-screen + cursor home
|
||||||
|
|
||||||
|
Alternative: the [UARTSCOPECLEAR] control tag does the exact same full
|
||||||
|
reset (raw output, table, tag monitor, video frame). No value needed -
|
||||||
|
handy to fire once right at boot so every run starts from an absolutely
|
||||||
|
clean UARTScope state, regardless of whatever the previous run left behind:
|
||||||
|
|
||||||
|
uart_printf("[UARTSCOPECLEAR]\n");
|
||||||
|
|
||||||
────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────
|
||||||
2. TAG MONITOR (side panel, one widget per tag)
|
2. TAG MONITOR (side panel, one widget per tag)
|
||||||
────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────
|
||||||
@@ -467,7 +505,26 @@ Notes:
|
|||||||
log of every capture in addition to the saved image files.
|
log of every capture in addition to the saved image files.
|
||||||
|
|
||||||
────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────
|
||||||
6. COMPLETE EXAMPLE (Chica / Amiga hardware emulation)
|
6. SENDING DATA BACK TO THE FIRMWARE (debugging)
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
UARTScope is no longer read-only. Below the Raw/Table view and the HDMI
|
||||||
|
preview there is a send bar:
|
||||||
|
|
||||||
|
• A "Pause" button sends a fixed, user-configurable byte sequence every
|
||||||
|
time it's clicked (right-click the button in UARTScope to change the
|
||||||
|
label and the code - escape sequences like \n \r \t \xHH are
|
||||||
|
supported, e.g. a single control byte to pause/step your firmware).
|
||||||
|
• An input line sends whatever is typed the instant Enter is pressed
|
||||||
|
(or the "Senden" button is clicked), with a selectable line ending
|
||||||
|
(none / LF / CRLF / CR) appended.
|
||||||
|
|
||||||
|
Both are only active while a port/connection is open. On the firmware
|
||||||
|
side this is just ordinary incoming UART data - read it the same way you
|
||||||
|
would read from any other serial console (e.g. a blocking or interrupt-
|
||||||
|
driven UART receive), there's no special protocol UARTScope expects here.
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
7. COMPLETE EXAMPLE (Chica / Amiga hardware emulation)
|
||||||
────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────
|
||||||
void uart_status_update(void) {
|
void uart_status_update(void) {
|
||||||
uart_printf("[WDG] uptime=%lu free=%lu load=%d temp=%d\n",
|
uart_printf("[WDG] uptime=%lu free=%lu load=%d temp=%d\n",
|
||||||
@@ -493,6 +550,16 @@ void trigger_memdump_capture(void) {
|
|||||||
|
|
||||||
// Clear screen from firmware when you want a fresh start:
|
// Clear screen from firmware when you want a fresh start:
|
||||||
// uart_printf("\033[2J\033[H");
|
// uart_printf("\033[2J\033[H");
|
||||||
|
// or, equivalently:
|
||||||
|
// uart_printf("[UARTSCOPECLEAR]\n");
|
||||||
|
|
||||||
|
// Fire once right at boot, before anything else is printed, so every run
|
||||||
|
// starts from a completely clean UARTScope - log, table, tag monitor and
|
||||||
|
// video preview all wiped, no leftovers from a previous run/crash:
|
||||||
|
void uart_init(void) {
|
||||||
|
hal_uart_setup(115200);
|
||||||
|
uart_printf("[UARTSCOPECLEAR]\n");
|
||||||
|
}
|
||||||
)UARTSCOPE";
|
)UARTSCOPE";
|
||||||
|
|
||||||
auto *dlg = new QDialog(this);
|
auto *dlg = new QDialog(this);
|
||||||
@@ -720,6 +787,12 @@ void MainWindow::saveSettings()
|
|||||||
|
|
||||||
s.setValue("screenshot/autoDir", m_screenshotDir);
|
s.setValue("screenshot/autoDir", m_screenshotDir);
|
||||||
|
|
||||||
|
if (m_sendBar) {
|
||||||
|
s.setValue("sendbar/buttonLabel", m_sendBar->buttonLabel());
|
||||||
|
s.setValue("sendbar/buttonCode", m_sendBar->buttonCode());
|
||||||
|
s.setValue("sendbar/lineEnding", m_sendBar->lineEndingIndex());
|
||||||
|
}
|
||||||
|
|
||||||
s.sync(); // flush to disk immediately (important since we also call
|
s.sync(); // flush to disk immediately (important since we also call
|
||||||
// this mid-session from configureTagFilter())
|
// this mid-session from configureTagFilter())
|
||||||
}
|
}
|
||||||
@@ -753,4 +826,13 @@ void MainWindow::restoreSettings()
|
|||||||
if (m_videoWidget)
|
if (m_videoWidget)
|
||||||
m_videoWidget->setAutoScreenshotDir(m_screenshotDir);
|
m_videoWidget->setAutoScreenshotDir(m_screenshotDir);
|
||||||
updateScreenshotDirTooltip();
|
updateScreenshotDirTooltip();
|
||||||
|
|
||||||
|
if (m_sendBar) {
|
||||||
|
if (s.contains("sendbar/buttonLabel"))
|
||||||
|
m_sendBar->setButtonLabel(s.value("sendbar/buttonLabel").toString());
|
||||||
|
if (s.contains("sendbar/buttonCode"))
|
||||||
|
m_sendBar->setButtonCode(s.value("sendbar/buttonCode").toString());
|
||||||
|
if (s.contains("sendbar/lineEnding"))
|
||||||
|
m_sendBar->setLineEndingIndex(s.value("sendbar/lineEnding").toInt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
223
src/sendbar.cpp
Normal file
223
src/sendbar.cpp
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#include "sendbar.h"
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QFont>
|
||||||
|
|
||||||
|
SendBar::SendBar(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
setupUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::setupUi()
|
||||||
|
{
|
||||||
|
auto *layout = new QHBoxLayout(this);
|
||||||
|
layout->setContentsMargins(6, 4, 6, 4);
|
||||||
|
|
||||||
|
m_pauseBtn = new QPushButton(m_buttonLabel, this);
|
||||||
|
m_pauseBtn->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(m_pauseBtn, &QPushButton::clicked, this, &SendBar::onPauseClicked);
|
||||||
|
connect(m_pauseBtn, &QPushButton::customContextMenuRequested,
|
||||||
|
this, &SendBar::onPauseContextMenu);
|
||||||
|
layout->addWidget(m_pauseBtn);
|
||||||
|
|
||||||
|
m_inputEdit = new QLineEdit(this);
|
||||||
|
m_inputEdit->setPlaceholderText(
|
||||||
|
tr("Text eingeben und Enter drücken, um über UART zu senden…"));
|
||||||
|
connect(m_inputEdit, &QLineEdit::returnPressed, this, &SendBar::sendInputLine);
|
||||||
|
layout->addWidget(m_inputEdit, 1);
|
||||||
|
|
||||||
|
m_sendBtn = new QPushButton(tr("Senden"), this);
|
||||||
|
connect(m_sendBtn, &QPushButton::clicked, this, &SendBar::onSendClicked);
|
||||||
|
layout->addWidget(m_sendBtn);
|
||||||
|
|
||||||
|
m_endingCombo = new QComboBox(this);
|
||||||
|
m_endingCombo->addItem(tr("kein Zeilenende"));
|
||||||
|
m_endingCombo->addItem(tr("\\n (LF)"));
|
||||||
|
m_endingCombo->addItem(tr("\\r\\n (CRLF)"));
|
||||||
|
m_endingCombo->addItem(tr("\\r (CR)"));
|
||||||
|
m_endingCombo->setCurrentIndex(1); // \n ist der übliche Default für UART-Konsolen
|
||||||
|
m_endingCombo->setToolTip(tr("Zeilenende, das an den über die Eingabezeile\ngesendeten Text angehängt wird"));
|
||||||
|
connect(m_endingCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
|
this, &SendBar::configChanged);
|
||||||
|
layout->addWidget(m_endingCombo);
|
||||||
|
|
||||||
|
updatePauseButtonUi();
|
||||||
|
|
||||||
|
// Solange nichts verbunden ist, gibt es nichts zu senden.
|
||||||
|
setConnected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::updatePauseButtonUi()
|
||||||
|
{
|
||||||
|
if (!m_pauseBtn)
|
||||||
|
return;
|
||||||
|
m_pauseBtn->setText(m_buttonLabel);
|
||||||
|
m_pauseBtn->setToolTip(
|
||||||
|
tr("Sendet über UART: %1\n(Rechtsklick zum Ändern)").arg(m_buttonCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── public accessors ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
int SendBar::lineEndingIndex() const
|
||||||
|
{
|
||||||
|
return m_endingCombo ? m_endingCombo->currentIndex() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::setButtonLabel(const QString &label)
|
||||||
|
{
|
||||||
|
m_buttonLabel = label.isEmpty() ? tr("Pause") : label;
|
||||||
|
updatePauseButtonUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::setButtonCode(const QString &code)
|
||||||
|
{
|
||||||
|
m_buttonCode = code;
|
||||||
|
updatePauseButtonUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::setLineEndingIndex(int index)
|
||||||
|
{
|
||||||
|
if (m_endingCombo && index >= 0 && index < m_endingCombo->count())
|
||||||
|
m_endingCombo->setCurrentIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── slots ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void SendBar::setConnected(bool connected)
|
||||||
|
{
|
||||||
|
if (m_pauseBtn) m_pauseBtn->setEnabled(connected);
|
||||||
|
if (m_inputEdit) m_inputEdit->setEnabled(connected);
|
||||||
|
if (m_sendBtn) m_sendBtn->setEnabled(connected);
|
||||||
|
// m_endingCombo bleibt immer bedienbar - reine Konfiguration.
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::onPauseClicked()
|
||||||
|
{
|
||||||
|
emit sendRequested(unescape(m_buttonCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::onPauseContextMenu(const QPoint &pos)
|
||||||
|
{
|
||||||
|
QMenu menu(this);
|
||||||
|
QAction *cfgAction = menu.addAction(tr("Code konfigurieren…"));
|
||||||
|
connect(cfgAction, &QAction::triggered, this, &SendBar::configureButton);
|
||||||
|
menu.exec(m_pauseBtn->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::onSendClicked()
|
||||||
|
{
|
||||||
|
sendInputLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::sendInputLine()
|
||||||
|
{
|
||||||
|
const QString text = m_inputEdit->text();
|
||||||
|
if (text.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QByteArray data = text.toUtf8();
|
||||||
|
switch (m_endingCombo->currentIndex()) {
|
||||||
|
case 1: data += "\n"; break;
|
||||||
|
case 2: data += "\r\n"; break;
|
||||||
|
case 3: data += "\r"; break;
|
||||||
|
default: break; // 0 = kein Zeilenende
|
||||||
|
}
|
||||||
|
|
||||||
|
emit sendRequested(data);
|
||||||
|
m_inputEdit->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendBar::configureButton()
|
||||||
|
{
|
||||||
|
auto *dlg = new QDialog(this);
|
||||||
|
dlg->setWindowTitle(tr("Button konfigurieren"));
|
||||||
|
dlg->setMinimumWidth(380);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(dlg);
|
||||||
|
|
||||||
|
auto *infoLabel = new QLabel(
|
||||||
|
tr("Der Code wird beim Klick als Rohbytes über UART gesendet.<br>"
|
||||||
|
"Escape-Sequenzen werden unterstützt: <tt>\\n \\r \\t \\\\ \\xHH</tt>"), dlg);
|
||||||
|
infoLabel->setWordWrap(true);
|
||||||
|
layout->addWidget(infoLabel);
|
||||||
|
|
||||||
|
auto *form = new QFormLayout();
|
||||||
|
auto *labelEdit = new QLineEdit(m_buttonLabel, dlg);
|
||||||
|
auto *codeEdit = new QLineEdit(m_buttonCode, dlg);
|
||||||
|
QFont mono("Monospace");
|
||||||
|
mono.setStyleHint(QFont::Monospace);
|
||||||
|
codeEdit->setFont(mono);
|
||||||
|
form->addRow(tr("Beschriftung:"), labelEdit);
|
||||||
|
form->addRow(tr("Code:"), codeEdit);
|
||||||
|
layout->addLayout(form);
|
||||||
|
|
||||||
|
auto *btnBox = new QDialogButtonBox(
|
||||||
|
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg);
|
||||||
|
connect(btnBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
|
||||||
|
connect(btnBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
|
||||||
|
layout->addWidget(btnBox);
|
||||||
|
|
||||||
|
if (dlg->exec() == QDialog::Accepted) {
|
||||||
|
const QString newLabel = labelEdit->text().trimmed();
|
||||||
|
m_buttonLabel = newLabel.isEmpty() ? tr("Pause") : newLabel;
|
||||||
|
m_buttonCode = codeEdit->text();
|
||||||
|
updatePauseButtonUi();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
dlg->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── static helper ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
QByteArray SendBar::unescape(const QString &text)
|
||||||
|
{
|
||||||
|
QByteArray out;
|
||||||
|
out.reserve(text.size());
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < text.size()) {
|
||||||
|
const QChar c = text.at(i);
|
||||||
|
if (c == QLatin1Char('\\') && i + 1 < text.size()) {
|
||||||
|
const QChar next = text.at(i + 1);
|
||||||
|
bool handled = true;
|
||||||
|
switch (next.toLatin1()) {
|
||||||
|
case 'n': out.append('\n'); break;
|
||||||
|
case 'r': out.append('\r'); break;
|
||||||
|
case 't': out.append('\t'); break;
|
||||||
|
case '0': out.append('\0'); break;
|
||||||
|
case '\\': out.append('\\'); break;
|
||||||
|
case 'x':
|
||||||
|
if (i + 3 < text.size()) {
|
||||||
|
bool ok = false;
|
||||||
|
const int val = text.mid(i + 2, 2).toInt(&ok, 16);
|
||||||
|
if (ok) {
|
||||||
|
out.append(static_cast<char>(val));
|
||||||
|
i += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handled = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (handled) {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Kein erkanntes Escape (oder ein einzelnes '\' am Ende): das
|
||||||
|
// Zeichen unverändert als UTF-8 übernehmen.
|
||||||
|
out += QString(c).toUtf8();
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -62,7 +62,7 @@ void SerialWorker::openPort(const QString &portName, qint32 baudRate,
|
|||||||
m_port->setStopBits(stopBits);
|
m_port->setStopBits(stopBits);
|
||||||
m_port->setFlowControl(flowControl);
|
m_port->setFlowControl(flowControl);
|
||||||
|
|
||||||
if (!m_port->open(QIODevice::ReadOnly)) {
|
if (!m_port->open(QIODevice::ReadWrite)) {
|
||||||
emit errorOccurred(tr("Cannot open %1: %2").arg(portName, m_port->errorString()));
|
emit errorOccurred(tr("Cannot open %1: %2").arg(portName, m_port->errorString()));
|
||||||
if (m_autoReconnect)
|
if (m_autoReconnect)
|
||||||
scheduleReconnect();
|
scheduleReconnect();
|
||||||
@@ -164,6 +164,13 @@ void SerialWorker::stopLogging()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SerialWorker::writeData(const QByteArray &data)
|
||||||
|
{
|
||||||
|
if (!m_device || data.isEmpty())
|
||||||
|
return;
|
||||||
|
m_device->write(data);
|
||||||
|
}
|
||||||
|
|
||||||
// ── private slots ───────────────────────────────────────────────────────────
|
// ── private slots ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void SerialWorker::onReadyRead()
|
void SerialWorker::onReadyRead()
|
||||||
@@ -256,7 +263,7 @@ void SerialWorker::tryReconnect()
|
|||||||
m_port->setStopBits(m_stopBits);
|
m_port->setStopBits(m_stopBits);
|
||||||
m_port->setFlowControl(m_flowControl);
|
m_port->setFlowControl(m_flowControl);
|
||||||
|
|
||||||
if (m_port->open(QIODevice::ReadOnly)) {
|
if (m_port->open(QIODevice::ReadWrite)) {
|
||||||
m_device = m_port;
|
m_device = m_port;
|
||||||
m_idleFlushTimer->stop();
|
m_idleFlushTimer->stop();
|
||||||
m_buffer.clear();
|
m_buffer.clear();
|
||||||
@@ -410,6 +417,13 @@ void SerialWorker::processLine(const QString &line)
|
|||||||
// reaction time with the manual screenshot button.
|
// reaction time with the manual screenshot button.
|
||||||
if (tag == QLatin1String("SCREENSHOT"))
|
if (tag == QLatin1String("SCREENSHOT"))
|
||||||
emit screenshotRequested(value);
|
emit screenshotRequested(value);
|
||||||
|
|
||||||
|
// [UARTSCOPECLEAR] is a control tag (no value expected): fire
|
||||||
|
// immediately so a baremetal project can guarantee a completely
|
||||||
|
// fresh UARTScope state right as it boots, regardless of whatever
|
||||||
|
// was left over from a previous run/crash.
|
||||||
|
if (tag == QLatin1String("UARTSCOPECLEAR"))
|
||||||
|
emit uartscopeClearRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit newLine(line);
|
emit newLine(line);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: diabolus <your@email.com>
|
# Maintainer: diabolus <your@email.com>
|
||||||
pkgname=uartscope
|
pkgname=uartscope
|
||||||
pkgver=1.1.0.r0.g36923e1
|
pkgver=1.1.0.r1.gef335f2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect"
|
pkgdesc="Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,12 +3,12 @@
|
|||||||
pkgname = uartscope
|
pkgname = uartscope
|
||||||
pkgbase = uartscope
|
pkgbase = uartscope
|
||||||
xdata = pkgtype=pkg
|
xdata = pkgtype=pkg
|
||||||
pkgver = 1.1.0.r0.g36923e1-1
|
pkgver = 1.1.0.r1.gef335f2-1
|
||||||
pkgdesc = Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect
|
pkgdesc = Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect
|
||||||
url = https://git.projekt-hirnfrei.de/diabolus/uartscope
|
url = https://git.projekt-hirnfrei.de/diabolus/uartscope
|
||||||
builddate = 1787031398
|
builddate = 1787701264
|
||||||
packager = Unknown Packager
|
packager = Unknown Packager
|
||||||
size = 422641
|
size = 434929
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
license = MIT
|
license = MIT
|
||||||
conflict = uartscope
|
conflict = uartscope
|
||||||
|
|||||||
Binary file not shown.
Submodule uartscope-git/src/uartscope updated: 36923e1646...ef335f2d9e
@@ -1,3 +1,3 @@
|
|||||||
36923e16461bc15086c84187790dbcb5f0a5030f not-for-merge branch 'main' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
ef335f2d9eaa98572ab305e5830a0372474163e8 not-for-merge branch 'main' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
||||||
cc102c93eb17f7b910d8e74c3505f198bed77f10 not-for-merge tag 'v1.0.0' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
cc102c93eb17f7b910d8e74c3505f198bed77f10 not-for-merge tag 'v1.0.0' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
||||||
36923e16461bc15086c84187790dbcb5f0a5030f not-for-merge tag 'v1.1.0' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
36923e16461bc15086c84187790dbcb5f0a5030f not-for-merge tag 'v1.1.0' of https://git.projekt-hirnfrei.de/diabolus/uartscope
|
||||||
|
|||||||
Reference in New Issue
Block a user