Skip to content

Commit 37c141a

Browse files
xIronicLord-Grey
andauthored
Add JSON-API support for requesting image snapshots (#1839)
* Implemented a method to receive and store a snapshot of the current image via JSON. * Updated changelog for the implemented method to receive a snapshot of the current image via JSON-API * Updated behaviour of the API to handle instance data requests such as getImageSnapshot and getLedSnapshot. * fix formatting of JsonApi.cpp * removed unnecessary filetype checks * clean up code * Update CHANGELOG.md * Update JsonAPI.cpp * Update schema-instancedata.json * Update JsonApiCommand.h --------- Co-authored-by: LordGrey <[email protected]>
1 parent 0acba71 commit 37c141a

File tree

7 files changed

+159
-1
lines changed

7 files changed

+159
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- Support direct or multiple instance addressing via single requests (#809)
3333
- Support of `serverinfo` subcommands: `getInfo, subscribe, unsubscribe, getSubscriptions, getSubscriptionCommands`
3434
- [Overview](https://github.com/hyperion-project/hyperion.ng/blob/API_Auth/doc/development/JSON-API%20_Commands_Overview.md) of API commands and subscription updates
35+
- Support for requesting instance-data via JSON-API. Implemented requesting the current image in different formats or led colors.
3536

3637
### Changed
3738

include/api/JsonAPI.h

+21
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,25 @@ private slots:
285285
///
286286
void handleSystemCommand(const QJsonObject &message, const JsonApiCommand& cmd);
287287

288+
/// Handle an incoming data request message
289+
///
290+
/// @param message the incoming message
291+
///
292+
void handleInstanceDataCommand(const QJsonObject &message, const JsonApiCommand& cmd);
293+
294+
/// Handle an incoming JSON message to request the current image
295+
///
296+
/// @param message the incoming message
297+
///
298+
void handleGetImageSnapshotCommand(const QJsonObject &message, const JsonApiCommand& cmd);
299+
300+
/// Handle an incoming JSON message to request the current led colors
301+
///
302+
/// @param message the incoming message
303+
///
304+
void handleGetLedSnapshotCommand(const QJsonObject &message, const JsonApiCommand& cmd);
305+
306+
288307
void applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment);
289308
void applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment);
290309
void applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel);
@@ -404,4 +423,6 @@ private slots:
404423
// The JsonCallbacks instance which handles data subscription/notifications
405424
QSharedPointer<JsonCallbacks> _jsonCB;
406425

426+
427+
407428
};

include/api/JsonApiCommand.h

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Command {
2323
Image,
2424
InputSource,
2525
Instance,
26+
InstanceData,
2627
LedColors,
2728
LedDevice,
2829
Logging,
@@ -53,6 +54,7 @@ class Command {
5354
case Image: return "image";
5455
case InputSource: return "inputsource";
5556
case Instance: return "instance";
57+
case InstanceData: return "instance-data";
5658
case LedColors: return "ledcolors";
5759
case LedDevice: return "leddevice";
5860
case Logging: return "logging";
@@ -85,7 +87,9 @@ class SubCommand {
8587
Discover,
8688
GetConfig,
8789
GetConfigOld,
90+
GetImageSnapshot,
8891
GetInfo,
92+
GetLedSnapshot,
8993
GetPendingTokenRequests,
9094
GetProperties,
9195
GetSchema,
@@ -136,7 +140,9 @@ class SubCommand {
136140
case Discover: return "discover";
137141
case GetConfig: return "getconfig";
138142
case GetConfigOld: return "getconfig-old";
143+
case GetImageSnapshot: return "getImageSnapshot";
139144
case GetInfo: return "getInfo";
145+
case GetLedSnapshot: return "getLedSnapshot";
140146
case GetPendingTokenRequests: return "getPendingTokenRequests";
141147
case GetProperties: return "getProperties";
142148
case GetSchema: return "getschema";
@@ -294,6 +300,8 @@ class ApiCommandRegister {
294300
{ {"instance", "startInstance"}, { Command::Instance, SubCommand::StartInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
295301
{ {"instance", "stopInstance"}, { Command::Instance, SubCommand::StopInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
296302
{ {"instance", "switchTo"}, { Command::Instance, SubCommand::SwitchTo, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
303+
{ {"instance-data", "getImageSnapshot"}, { Command::InstanceData, SubCommand::GetImageSnapshot, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
304+
{ {"instance-data", "getLedSnapshot"}, { Command::InstanceData, SubCommand::GetLedSnapshot, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes } },
297305
{ {"ledcolors", "imagestream-start"}, { Command::LedColors, SubCommand::ImageStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
298306
{ {"ledcolors", "imagestream-stop"}, { Command::LedColors, SubCommand::ImageStreamStop, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
299307
{ {"ledcolors", "ledstream-start"}, { Command::LedColors, SubCommand::LedStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"type":"object",
3+
"required":true,
4+
"properties":{
5+
"command": {
6+
"type" : "string",
7+
"required" : true,
8+
"enum" : ["instance-data"]
9+
},
10+
"subcommand" : {
11+
"type" : "string",
12+
"required" : true,
13+
"enum" : ["getImageSnapshot","getLedSnapshot"]
14+
},
15+
"instance" : {
16+
"type": "integer",
17+
"minimum": 0,
18+
"maximum": 255
19+
},
20+
"format" : {
21+
"type" : "string",
22+
"enum" : ["BMP","JPG","PNG"]
23+
},
24+
"tan" : {
25+
"type" : "integer"
26+
}
27+
},
28+
"additionalProperties": false
29+
}

libsrc/api/JSONRPC_schema/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"command": {
66
"type" : "string",
77
"required" : true,
8-
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "service", "system", "transform", "correction", "temperature" ]
8+
"enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "instance-data", "leddevice", "inputsource", "service", "system", "transform", "correction", "temperature" ]
99
}
1010
}
1111
}

libsrc/api/JSONRPC_schemas.qrc

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
2121
<file alias="schema-authorize">JSONRPC_schema/schema-authorize.json</file>
2222
<file alias="schema-instance">JSONRPC_schema/schema-instance.json</file>
23+
<file alias="schema-instance-data">JSONRPC_schema/schema-instancedata.json</file>
2324
<file alias="schema-leddevice">JSONRPC_schema/schema-leddevice.json</file>
2425
<file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file>
2526
<file alias="schema-service">JSONRPC_schema/schema-service.json</file>

libsrc/api/JsonAPI.cpp

+98
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <QMultiMap>
1414
#include <QRegularExpression>
1515
#include <QStringList>
16+
#include <QEventLoop>
1617

1718
// hyperion includes
1819
#include <leddevice/LedDeviceWrapper.h>
@@ -60,6 +61,7 @@ using namespace hyperion;
6061
namespace {
6162

6263
constexpr std::chrono::milliseconds NEW_TOKEN_REQUEST_TIMEOUT{ 180000 };
64+
constexpr std::chrono::milliseconds LED_DATA_TIMEOUT { 1000 };
6365

6466
const char TOKEN_TAG[] = "token";
6567
constexpr int TOKEN_TAG_LENGTH = sizeof(TOKEN_TAG) - 1;
@@ -386,6 +388,9 @@ void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &messag
386388
case Command::ClearAll:
387389
handleClearallCommand(message, cmd);
388390
break;
391+
case Command::InstanceData:
392+
handleInstanceDataCommand(message, cmd);
393+
break;
389394
// BEGIN | The following commands are deprecated but used to ensure backward compatibility with Hyperion Classic remote control
390395
case Command::Transform:
391396
case Command::Correction:
@@ -398,6 +403,99 @@ void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &messag
398403
}
399404
}
400405

406+
void JsonAPI::handleGetImageSnapshotCommand(const QJsonObject &message, const JsonApiCommand &cmd)
407+
{
408+
QString replyMsg;
409+
QString imageFormat = message["format"].toString("PNG");
410+
const PriorityMuxer::InputInfo priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
411+
Image<ColorRgb> image = priorityInfo.image;
412+
QImage snapshot(reinterpret_cast<const uchar *>(image.memptr()), image.width(), image.height(), qsizetype(3) * image.width(), QImage::Format_RGB888);
413+
QByteArray byteArray;
414+
415+
QBuffer buffer{&byteArray};
416+
buffer.open(QIODevice::WriteOnly);
417+
if (!snapshot.save(&buffer, imageFormat.toUtf8().constData()))
418+
{
419+
replyMsg = QString("Failed to create snapshot of the current image in %1 format").arg(imageFormat);
420+
sendErrorReply(replyMsg, cmd);
421+
return;
422+
}
423+
QByteArray base64Image = byteArray.toBase64();
424+
425+
QJsonObject info;
426+
info["format"] = imageFormat;
427+
info["width"] = image.width();
428+
info["height"] = image.height();
429+
info["data"] = base64Image.constData();
430+
sendSuccessDataReply(info, cmd);
431+
}
432+
433+
void JsonAPI::handleGetLedSnapshotCommand(const QJsonObject& /*message*/, const JsonApiCommand &cmd)
434+
{
435+
std::vector<ColorRgb> ledColors;
436+
QEventLoop loop;
437+
QTimer timer;
438+
439+
// Timeout handling (ensuring loop quits on timeout)
440+
timer.setSingleShot(true);
441+
connect(&timer, &QTimer::timeout, this, [&loop]() {
442+
loop.quit(); // Stop waiting if timeout occurs
443+
});
444+
445+
// Capture LED colors when the LED data signal is emitted (execute only once)
446+
std::unique_ptr<QObject> context{new QObject};
447+
QObject* pcontext = context.get();
448+
connect(_hyperion, &Hyperion::ledDeviceData, pcontext,
449+
[this, &loop, context = std::move(context), &ledColors, cmd](std::vector<ColorRgb> ledColorsUpdate) mutable {
450+
ledColors = ledColorsUpdate;
451+
loop.quit(); // ✅ Ensure the event loop quits immediately when data is received
452+
453+
QJsonArray ledRgbColorsArray;
454+
for (const auto &color : ledColors)
455+
{
456+
QJsonArray rgbArray;
457+
rgbArray.append(color.red);
458+
rgbArray.append(color.green);
459+
rgbArray.append(color.blue);
460+
ledRgbColorsArray.append(rgbArray);
461+
}
462+
QJsonObject info;
463+
info["leds"] = ledRgbColorsArray;
464+
465+
sendSuccessDataReply(info, cmd);
466+
context.reset();
467+
}
468+
);
469+
470+
// Start the timer and wait for either the signal or timeout
471+
timer.start(LED_DATA_TIMEOUT);
472+
loop.exec();
473+
474+
// If no data was received, return an error
475+
if (ledColors.empty())
476+
{
477+
QString replyMsg = QString("No LED color data available, i.e.no LED update was done within the last %1 ms").arg(LED_DATA_TIMEOUT.count());
478+
sendErrorReply(replyMsg, cmd);
479+
return;
480+
}
481+
}
482+
483+
void JsonAPI::handleInstanceDataCommand(const QJsonObject &message, const JsonApiCommand &cmd)
484+
{
485+
486+
switch (cmd.subCommand)
487+
{
488+
case SubCommand::GetImageSnapshot:
489+
handleGetImageSnapshotCommand(message, cmd);
490+
break;
491+
case SubCommand::GetLedSnapshot:
492+
handleGetLedSnapshotCommand(message, cmd);
493+
break;
494+
default:
495+
break;
496+
}
497+
}
498+
401499
void JsonAPI::handleColorCommand(const QJsonObject &message, const JsonApiCommand& cmd)
402500
{
403501
emit forwardJsonMessage(message);

0 commit comments

Comments
 (0)