feat(mock): Implement realistic Hyperion mock server

The mock server now correctly implements the newline-delimited JSON protocol observed in the Hyperion server and client code.

- It properly parses incoming JSON streams line-by-line.
- It creates QImage objects from the raw RGB data using the dimensions provided in the JSON payload.
- It sends a success reply to the client after receiving an image.
- This commit also updates lessons_learned.md with key findings from the debugging session.
This commit is contained in:
Tobias J. Endres 2025-08-15 20:20:14 +02:00
parent d1497f8c91
commit aa57826800
55 changed files with 71 additions and 16 deletions

Binary file not shown.

View File

@ -277,6 +277,7 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/pthread.h \
/usr/include/qt6/QtCore/QByteArray \
/usr/include/qt6/QtCore/QEvent \
/usr/include/qt6/QtCore/QJsonObject \
/usr/include/qt6/QtCore/QList \
/usr/include/qt6/QtCore/QMargins \
/usr/include/qt6/QtCore/QObject \
@ -308,6 +309,9 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/qt6/QtCore/qbytearrayalgorithms.h \
/usr/include/qt6/QtCore/qbytearraylist.h \
/usr/include/qt6/QtCore/qbytearrayview.h \
/usr/include/qt6/QtCore/qcalendar.h \
/usr/include/qt6/QtCore/qcborcommon.h \
/usr/include/qt6/QtCore/qcborvalue.h \
/usr/include/qt6/QtCore/qchar.h \
/usr/include/qt6/QtCore/qcompare.h \
/usr/include/qt6/QtCore/qcompare_impl.h \
@ -324,9 +328,11 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/qt6/QtCore/qcoreevent.h \
/usr/include/qt6/QtCore/qdarwinhelpers.h \
/usr/include/qt6/QtCore/qdatastream.h \
/usr/include/qt6/QtCore/qdatetime.h \
/usr/include/qt6/QtCore/qdeadlinetimer.h \
/usr/include/qt6/QtCore/qdebug.h \
/usr/include/qt6/QtCore/qelapsedtimer.h \
/usr/include/qt6/QtCore/qendian.h \
/usr/include/qt6/QtCore/qeventloop.h \
/usr/include/qt6/QtCore/qexceptionhandling.h \
/usr/include/qt6/QtCore/qflags.h \
@ -343,6 +349,10 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/qt6/QtCore/qiodevicebase.h \
/usr/include/qt6/QtCore/qiterable.h \
/usr/include/qt6/QtCore/qiterator.h \
/usr/include/qt6/QtCore/qjsondocument.h \
/usr/include/qt6/QtCore/qjsonobject.h \
/usr/include/qt6/QtCore/qjsonparseerror.h \
/usr/include/qt6/QtCore/qjsonvalue.h \
/usr/include/qt6/QtCore/qlatin1stringview.h \
/usr/include/qt6/QtCore/qline.h \
/usr/include/qt6/QtCore/qlist.h \
@ -368,6 +378,7 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/qt6/QtCore/qprocessordetection.h \
/usr/include/qt6/QtCore/qrect.h \
/usr/include/qt6/QtCore/qrefcount.h \
/usr/include/qt6/QtCore/qregularexpression.h \
/usr/include/qt6/QtCore/qscopedpointer.h \
/usr/include/qt6/QtCore/qscopeguard.h \
/usr/include/qt6/QtCore/qset.h \
@ -415,7 +426,9 @@ Hyperion_Grabber_Wayland_QT_autogen/timestamp: \
/usr/include/qt6/QtCore/qtversionchecks.h \
/usr/include/qt6/QtCore/qtypeinfo.h \
/usr/include/qt6/QtCore/qtypes.h \
/usr/include/qt6/QtCore/qurl.h \
/usr/include/qt6/QtCore/qutf8stringview.h \
/usr/include/qt6/QtCore/quuid.h \
/usr/include/qt6/QtCore/qvariant.h \
/usr/include/qt6/QtCore/qvarlengtharray.h \
/usr/include/qt6/QtCore/qversiontagging.h \

View File

@ -26,29 +26,61 @@ private slots:
void handleConnection()
{
QTcpSocket *socket = _server->nextPendingConnection();
connect(socket, &QTcpSocket::readyRead, this, [socket]() {
QByteArray data = socket->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.contains("command") && obj["command"].toString() == "image") {
if (obj.contains("imagedata")) {
QByteArray imageData = QByteArray::fromBase64(obj["imagedata"].toString().toUtf8());
QImage image;
image.loadFromData(imageData, "PNG");
if (!image.isNull()) {
QString filename = QString("received_frame_%1.png").arg(QDateTime::currentMSecsSinceEpoch());
image.save(filename);
qDebug() << "Received and saved image:" << filename;
QByteArray *buffer = new QByteArray();
connect(socket, &QTcpSocket::readyRead, this, [this, socket, buffer]() {
buffer->append(socket->readAll());
int newlineIndex;
while ((newlineIndex = buffer->indexOf('\n')) != -1) {
// Extract the message including the newline
QByteArray jsonData = buffer->left(newlineIndex + 1);
buffer->remove(0, newlineIndex + 1);
QJsonDocument doc = QJsonDocument::fromJson(jsonData.trimmed());
if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.contains("command") && obj["command"].toString() == "image") {
QJsonObject params = obj["params"].toObject();
int width = params["imagewidth"].toInt();
int height = params["imageheight"].toInt();
QByteArray imageData = QByteArray::fromBase64(params["imagedata"].toString().toUtf8());
if (width > 0 && height > 0 && !imageData.isEmpty()) {
QImage image(reinterpret_cast<const uchar*>(imageData.constData()), width, height, QImage::Format_RGB888);
if (!image.isNull()) {
QString filename = QString("received_frame_%1.png").arg(QUuid::createUuid().toString());
image.save(filename);
qDebug() << "Received and saved image:" << filename;
// Send success reply
QJsonObject reply;
reply["success"] = true;
reply["command"] = "image";
QByteArray replyData = QJsonDocument(reply).toJson(QJsonDocument::Compact);
replyData.append('\n');
socket->write(replyData);
} else {
qWarning() << "Failed to create QImage from raw data.";
}
} else {
qWarning() << "Failed to decode image data.";
qWarning() << "Invalid image dimensions or empty image data received.";
}
}
} else {
qWarning() << "Failed to parse JSON object:" << jsonData.trimmed();
}
}
});
connect(socket, &QTcpSocket::disconnected, this, [socket, buffer]() {
socket->deleteLater();
delete buffer;
});
}
private:
QTcpServer *_server;
};

View File

@ -94,7 +94,7 @@ This document summarizes the key challenges, debugging steps, and solutions enco
* **D-Bus `SelectSources` Signature Mismatch:**
* **Problem:** `wayland_poc` was failing with "Type of message, “(oosa{sv})”, does not match expected type “(a{sv})”" when calling `SelectSources`. This occurred because the `handleSelectSourcesResponse` function, designed for `SelectSources` replies, was incorrectly connected to the `CreateSession` D-Bus call's `finished` signal. The `CreateSession` reply's signature (starting with an object path) was being misinterpreted, leading to an incorrect `SelectSources` call.
* **Diagnosis:** The `QObject::connect` in `main` was incorrectly routing the `CreateSession` reply to the `handleSelectSourcesResponse` function.
* **Fix:**
* **Fix:**
1. Created a new handler function, `handleCreateSessionFinished`, to specifically process the `CreateSession` reply. This function extracts the session handle and then correctly initiates the `SelectSources` D-Bus call with its own `QDBusPendingCallWatcher` connected to `handleSelectSourcesResponse`.
2. Modified the `main` function to connect the `CreateSession`'s `QDBusPendingCallWatcher` to `handleCreateSessionFinished`.
@ -174,3 +174,13 @@ This document summarizes the key challenges, debugging steps, and solutions enco
* Corrected a syntax error in the `setImgSize` function's string literal (related to escaped quotes) that was causing compilation failures.
* Corrected `QImage::byteCount()` to `QImage::sizeInBytes()` for accurate raw data size calculation.
* **Outcome:** The grabber now sends image data with dynamically updated dimensions in the JSON header, which should resolve the data acknowledgment issue with Hyperion.
## 15. Mock Server Debugging and Protocol Discovery
* **Initial State:** The `hyperion-mock` server was not processing image data, saving no PNGs and logging no errors.
* **Incorrect Assumption (Aha! Moment #1):** My initial diagnosis was based on a faulty memory that the Hyperion protocol was binary and used a 4-byte length prefix for messages. I proposed a "fix" for the mock server based on this incorrect assumption.
* **Investigation:** The user correctly prompted me to read the actual `hyperion.ng` source code to build a realistic mock.
* **Protocol Discovery (Aha! Moment #2):** Reading `JsonClientConnection.cpp` from the `hyperion.ng` repo and re-reading our own `hyperionclient.cpp` revealed the actual protocol: simple **newline-delimited (`\n`) JSON messages**.
* **Mock Server Fix:** The mock server was corrected to use newline-delimited parsing. It was also fixed to correctly parse the raw image data (using `imagewidth` and `imageheight` from the JSON) and to send a `{"success":true}` reply, which the client expects.
* **File Location (Aha! Moment #3):** I repeatedly failed to find the generated PNG files because I was looking in the `build/` directory. The files were being created in the project's root directory, which was the *current working directory* of the mock server process when the user launched it.
* **Operational Improvement (Aha! Moment #4):** The user reminded me that I can use `ps aux | grep <process_name>` to find the PID of background processes myself, rather than asking them for it. This is a more autonomous and efficient workflow.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB