mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into temperture
This commit is contained in:
		@@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
- Allow to force starting Hyperion in read-only mode (`--readonlyMode`)
 | 
			
		||||
- JSON-API: Support to query for a dedicated set of configuration items for a set of instances
 | 
			
		||||
- JSON-API: Support to save a dedicated set of configuration items for a set of instances
 | 
			
		||||
- JSON-API: Limit update emission frequency: Images (25Hz), raw LED-Colors (40Hz) & LED-Device data (200Hz) 
 | 
			
		||||
- Effects: Limit the maximum update rate to 200Hz
 | 
			
		||||
 | 
			
		||||
**JSON-API**
 | 
			
		||||
- New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`.
 | 
			
		||||
@@ -44,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
- Refactored: Python to enable parallel effect processing under Python 3.12
 | 
			
		||||
- Fixed: Python 3.12 crashes (#1747)
 | 
			
		||||
- osX Grabber: Use ScreenCaptureKit under macOS 15 and above
 | 
			
		||||
- Removed maximum LED number constraint from Matrix layout schema which was not synced with the UI behaviour (#1804)
 | 
			
		||||
 | 
			
		||||
**JSON-API**
 | 
			
		||||
- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,7 @@ The amount of "%" must match with following arguments
 | 
			
		||||
> TODO
 | 
			
		||||
 | 
			
		||||
## Visual Studio Code
 | 
			
		||||
**We assume that you successfully compiled Hyperion with the [Compile HowTo](CompileHowto.md) WITHOUT Docker** \
 | 
			
		||||
**We assume that you successfully compiled Hyperion with the [Compile HowTo](doc/development/CompileHowto.md) WITHOUT Docker** \
 | 
			
		||||
If you want to use VSCode for development follow the steps.
 | 
			
		||||
 | 
			
		||||
- Install [VSCode](https://code.visualstudio.com/). On Ubuntu 16.04+ you can also use the [Snapcraft VSCode](https://snapcraft.io/code) package.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								dependencies/external/mbedtls
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								dependencies/external/mbedtls
									
									
									
									
										vendored
									
									
								
							 Submodule dependencies/external/mbedtls updated: 2ca6c285a0...107ea89daa
									
								
							
							
								
								
									
										2
									
								
								dependencies/external/protobuf
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								dependencies/external/protobuf
									
									
									
									
										vendored
									
									
								
							 Submodule dependencies/external/protobuf updated: 3d9f7c430a...5864b5078a
									
								
							@@ -20,6 +20,9 @@ brightness = float(hyperion.args.get('brightness', 100))/100.0
 | 
			
		||||
 | 
			
		||||
sleepTime = float(hyperion.args.get('sleepTime', 0.14))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
candles = hyperion.args.get('candles', "all")
 | 
			
		||||
ledlist = hyperion.args.get('ledlist', "1")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,10 @@ cropBottom = int(hyperion.args.get('cropBottom', 0))
 | 
			
		||||
grayscale = bool(hyperion.args.get('grayscale', False))
 | 
			
		||||
 | 
			
		||||
sleepTime = 1./framesPerSecond
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
imageFrameList = []
 | 
			
		||||
 | 
			
		||||
if imageData:
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,9 @@ import time
 | 
			
		||||
# Get parameters
 | 
			
		||||
sleepTime = float(hyperion.args.get('sleepTime', 0.5))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
def TestRgb( iteration ):
 | 
			
		||||
 | 
			
		||||
    switcher = {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,9 @@ sleepTime = float(hyperion.args.get('sleepTime', 0.5))
 | 
			
		||||
testleds = hyperion.args.get('testleds', "all")
 | 
			
		||||
ledlist = hyperion.args.get('ledlist', "1")
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
testlist = ()
 | 
			
		||||
if (testleds == "list") and (type(ledlist) is str):
 | 
			
		||||
	for s in ledlist.split(','):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@ width = hyperion.imageWidth()
 | 
			
		||||
height = hyperion.imageHeight()
 | 
			
		||||
sleepTime = float(hyperion.args.get('sleepTime', 0.2))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
def mapto(x, in_min, in_max, out_min, out_max):
 | 
			
		||||
	return float((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,11 @@ import hyperion, time
 | 
			
		||||
sleepTime  = float(hyperion.args.get('speed', 1.5)) * 0.005
 | 
			
		||||
whiteLevel = int(hyperion.args.get('whiteLevel', 0))
 | 
			
		||||
lvl        = int(hyperion.args.get('colorLevel', 220))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
# check value
 | 
			
		||||
# check value
 | 
			
		||||
whiteLevel = min( whiteLevel, 254 )
 | 
			
		||||
lvl        = min( lvl, 255 )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,9 @@ height         = 10
 | 
			
		||||
 | 
			
		||||
imageData      = bytearray(height * width * (0,0,0))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
# Start the write data loop
 | 
			
		||||
if initialBlink:
 | 
			
		||||
	for i in range(6):
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@ saturation   = float(hyperion.args.get('saturation', 100))/100.0
 | 
			
		||||
color        = list(hyperion.args.get('color', (255,255,255)))
 | 
			
		||||
randomColor  = bool(hyperion.args.get('random-color', False))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
# Check parameters
 | 
			
		||||
rotationTime = max(0.1, rotationTime)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,10 @@ def getPoint(rand = True ,x = 0.5, y = 0.5):
 | 
			
		||||
def getSTime(rt, steps = 360):
 | 
			
		||||
	rt = float(rt)
 | 
			
		||||
	sleepTime = max(0.1, rt) / steps
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	# Limit update rate
 | 
			
		||||
	sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
	# adapt sleeptime to hardware
 | 
			
		||||
	minStepTime= float(hyperion.latchTime)/1000.0
 | 
			
		||||
	if minStepTime == 0: minStepTime = 0.001
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,9 @@ minStepTime = float(hyperion.latchTime)/1000.0
 | 
			
		||||
if minStepTime == 0: minStepTime = 0.001
 | 
			
		||||
factor      = 1 if sleepTime > minStepTime else int(math.ceil(minStepTime/sleepTime))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
runners = [
 | 
			
		||||
	{ "i":0, "pos":0, "c":0, "step":9, "lvl":255},
 | 
			
		||||
	{ "i":1, "pos":0, "c":0, "step":8, "lvl":255},
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,10 @@ sleepTime = float(hyperion.args.get('speed', 1)) / 1000.0
 | 
			
		||||
color = list(hyperion.args.get('color', (255,255,255)))
 | 
			
		||||
randomise = bool(hyperion.args.get('random', False))
 | 
			
		||||
iWidth = hyperion.imageWidth()
 | 
			
		||||
iHeight = hyperion.imageHeight()
 | 
			
		||||
iHeight = hyperion.imageHeight()
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
class trail:
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,10 @@ import hyperion, time
 | 
			
		||||
sleepTime   = float(hyperion.args.get('sleepTime', 1000))/1000.0
 | 
			
		||||
length = hyperion.args.get('length', 1)
 | 
			
		||||
color1 = hyperion.args.get('color1', (255,255,255))
 | 
			
		||||
color2 = hyperion.args.get('color2', (255,0,0))
 | 
			
		||||
color2 = hyperion.args.get('color2', (255,0,0))
 | 
			
		||||
 | 
			
		||||
# Limit update rate
 | 
			
		||||
sleepTime = max(hyperion.lowestUpdateInterval(), sleepTime)
 | 
			
		||||
 | 
			
		||||
# Initialize the led data
 | 
			
		||||
i = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,8 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	void initialize();
 | 
			
		||||
 | 
			
		||||
	QSharedPointer<JsonCallbacks> getCallBack() const;
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
@@ -82,7 +84,7 @@ signals:
 | 
			
		||||
	///
 | 
			
		||||
	/// Signal emits with the reply message provided with handleMessage()
 | 
			
		||||
	///
 | 
			
		||||
	void callbackMessage(QJsonObject);
 | 
			
		||||
	void callbackReady(QJsonObject);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// Signal emits whenever a JSON-message should be forwarded
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,7 @@ signals:
 | 
			
		||||
	/// @brief Emits whenever a new json mesage callback is ready to send
 | 
			
		||||
	/// @param The JsonObject message
 | 
			
		||||
	///
 | 
			
		||||
	void newCallback(QJsonObject);
 | 
			
		||||
	void callbackReady(QJsonObject);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	///
 | 
			
		||||
@@ -182,6 +182,8 @@ private:
 | 
			
		||||
 | 
			
		||||
	/// construct callback msg
 | 
			
		||||
	void doCallback(Subscription::Type cmd, const QVariant& data);
 | 
			
		||||
	void doCallback(Subscription::Type cmd, const QJsonArray& data);
 | 
			
		||||
	void doCallback(Subscription::Type cmd, const QJsonObject& data);
 | 
			
		||||
 | 
			
		||||
	Logger *_log;
 | 
			
		||||
	Hyperion* _hyperion;
 | 
			
		||||
 
 | 
			
		||||
@@ -103,4 +103,6 @@ private:
 | 
			
		||||
	QImage          _image;
 | 
			
		||||
	QPainter*       _painter;
 | 
			
		||||
	QVector<QImage> _imageStack;
 | 
			
		||||
 | 
			
		||||
	double	_lowestUpdateIntervalInSeconds;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -21,28 +21,29 @@ public:
 | 
			
		||||
 | 
			
		||||
	// Wrapper methods for Python interpreter extra buildin methods
 | 
			
		||||
	static PyMethodDef effectMethods[];
 | 
			
		||||
	static PyObject* wrapSetColor(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapSetImage(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapGetImage(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapAbort(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageShow(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageLinearGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageConicalGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageRadialGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSolidFill(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawLine(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPoint(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawRect(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPolygon(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPie(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSetPixel(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageGetPixel(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSave(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageMinSize(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageWidth(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageHeight(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCRotate(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCOffset(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCShear(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageResetT(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapSetColor              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapSetImage              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapGetImage              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapAbort                 (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageShow             (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageLinearGradient   (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageConicalGradient  (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageRadialGradient   (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSolidFill        (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawLine         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPoint        (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawRect         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPolygon      (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPie          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSetPixel         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageGetPixel         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSave             (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageMinSize          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageWidth            (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageHeight           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCRotate          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCOffset          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCShear           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageResetT           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapLowestUpdateInterval  (PyObject* self, PyObject* args);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
// stl includes
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
// QT includes
 | 
			
		||||
#include <QString>
 | 
			
		||||
@@ -11,6 +12,7 @@
 | 
			
		||||
#include <QJsonValue>
 | 
			
		||||
#include <QJsonArray>
 | 
			
		||||
#include <QMap>
 | 
			
		||||
#include <QElapsedTimer>
 | 
			
		||||
 | 
			
		||||
// hyperion-utils includes
 | 
			
		||||
#include <utils/Image.h>
 | 
			
		||||
@@ -604,4 +606,14 @@ private:
 | 
			
		||||
	/// Boblight instance
 | 
			
		||||
	BoblightServer* _boblightServer;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	QElapsedTimer _imageTimer;  // Timer for controlling image emission frequency
 | 
			
		||||
	QElapsedTimer _rawLedDataTimer;  // Timer for controlling rawLedColors emission frequency
 | 
			
		||||
	QElapsedTimer _ledDeviceDataTimer; // Timer for controlling LedDevice data emission frequency
 | 
			
		||||
	qint64 _lastImageEmission;  // Last timestamp of image signal emission
 | 
			
		||||
	qint64 _lastRawLedDataEmission;  // Last timestamp of rawLedColors signal emission
 | 
			
		||||
	qint64 _lastLedDeviceDataEmission; // Last timestamp of ledDeviceData signal emission
 | 
			
		||||
	std::chrono::milliseconds _imageEmissionInterval;
 | 
			
		||||
	std::chrono::milliseconds _rawLedDataEmissionInterval;
 | 
			
		||||
	std::chrono::milliseconds _ledDeviceDataEmissionInterval;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,11 @@ JsonAPI::JsonAPI(QString peerAddress, Logger *log, bool localConnection, QObject
 | 
			
		||||
	_jsonCB = QSharedPointer<JsonCallbacks>(new JsonCallbacks( _log, _peerAddress, parent));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QSharedPointer<JsonCallbacks> JsonAPI::getCallBack() const
 | 
			
		||||
{
 | 
			
		||||
	return _jsonCB;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonAPI::initialize()
 | 
			
		||||
{
 | 
			
		||||
	// init API, REQUIRED!
 | 
			
		||||
@@ -98,9 +103,6 @@ void JsonAPI::initialize()
 | 
			
		||||
	// listen for killed instances
 | 
			
		||||
	connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
 | 
			
		||||
 | 
			
		||||
	// pipe callbacks from subscriptions to parent
 | 
			
		||||
	connect(_jsonCB.data(), &JsonCallbacks::newCallback, this, &JsonAPI::callbackMessage);
 | 
			
		||||
 | 
			
		||||
	// notify hyperion about a jsonMessageForward
 | 
			
		||||
	if (_hyperion != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
@@ -1547,7 +1549,7 @@ void JsonAPI::sendSuccessReply(const JsonApiCommand& cmd)
 | 
			
		||||
 | 
			
		||||
void JsonAPI::sendSuccessReply(const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
 | 
			
		||||
{
 | 
			
		||||
	emit callbackMessage(getBasicCommandReply(true, command, tan , isInstanceCmd));
 | 
			
		||||
	emit callbackReady(getBasicCommandReply(true, command, tan , isInstanceCmd));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd)
 | 
			
		||||
@@ -1582,7 +1584,7 @@ void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const QS
 | 
			
		||||
		reply["errorData"] = errorsArray;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit callbackMessage(reply);
 | 
			
		||||
	emit callbackReady(reply);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonAPI::sendErrorReply(const QString &error, const JsonApiCommand& cmd)
 | 
			
		||||
@@ -1611,7 +1613,7 @@ void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetai
 | 
			
		||||
		reply["errorData"] = errorsArray;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit callbackMessage(reply);
 | 
			
		||||
	emit callbackReady(reply);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonAPI::sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd)
 | 
			
		||||
@@ -1631,7 +1633,7 @@ void JsonAPI::sendNewRequest(const QJsonValue &infoData, const QString &command,
 | 
			
		||||
 | 
			
		||||
	request["info"] = infoData;
 | 
			
		||||
 | 
			
		||||
	emit callbackMessage(request);
 | 
			
		||||
	emit callbackReady(request);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonAPI::sendNoAuthorization(const JsonApiCommand& cmd)
 | 
			
		||||
 
 | 
			
		||||
@@ -282,22 +282,43 @@ QStringList JsonCallbacks::getSubscribedCommands() const
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::doCallback(Subscription::Type cmd, const QVariant& data)
 | 
			
		||||
{
 | 
			
		||||
	if (data.userType() == QMetaType::QJsonArray)
 | 
			
		||||
	{
 | 
			
		||||
		doCallback(cmd, data.toJsonArray());
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		doCallback(cmd, data.toJsonObject());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::doCallback(Subscription::Type cmd, const QJsonArray& data)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject obj;
 | 
			
		||||
	obj["command"] = Subscription::toString(cmd);
 | 
			
		||||
 | 
			
		||||
	if (Subscription::isInstanceSpecific(cmd))
 | 
			
		||||
	{
 | 
			
		||||
		obj["instance"] = _hyperion->getInstanceIndex();
 | 
			
		||||
		obj.insert("instance", _hyperion->getInstanceIndex());
 | 
			
		||||
	}
 | 
			
		||||
	obj.insert("data", data);
 | 
			
		||||
 | 
			
		||||
	if (data.userType() == QMetaType::QJsonArray) {
 | 
			
		||||
		obj["data"] = data.toJsonArray();
 | 
			
		||||
	} else {
 | 
			
		||||
		obj["data"] = data.toJsonObject();
 | 
			
		||||
	emit callbackReady(obj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::doCallback(Subscription::Type cmd, const QJsonObject& data)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject obj;
 | 
			
		||||
	obj["command"] = Subscription::toString(cmd);
 | 
			
		||||
 | 
			
		||||
	if (Subscription::isInstanceSpecific(cmd))
 | 
			
		||||
	{
 | 
			
		||||
		obj.insert("instance", _hyperion->getInstanceIndex());
 | 
			
		||||
	}
 | 
			
		||||
	obj.insert("data", data);
 | 
			
		||||
 | 
			
		||||
	emit newCallback(obj);
 | 
			
		||||
	emit callbackReady(obj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleComponentState(hyperion::Components comp, bool state)
 | 
			
		||||
@@ -306,7 +327,7 @@ void JsonCallbacks::handleComponentState(hyperion::Components comp, bool state)
 | 
			
		||||
	data["name"] = componentToIdString(comp);
 | 
			
		||||
	data["enabled"] = state;
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::ComponentsUpdate, QVariant(data));
 | 
			
		||||
	doCallback(Subscription::ComponentsUpdate, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs)
 | 
			
		||||
@@ -315,7 +336,7 @@ void JsonCallbacks::handlePriorityUpdate(int currentPriority, const PriorityMuxe
 | 
			
		||||
	data["priorities"] = JsonInfo::getPrioritiestInfo(currentPriority, activeInputs);
 | 
			
		||||
	data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::PrioritiesUpdate, QVariant(data));
 | 
			
		||||
	doCallback(Subscription::PrioritiesUpdate, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleImageToLedsMappingChange(int mappingType)
 | 
			
		||||
@@ -323,7 +344,7 @@ void JsonCallbacks::handleImageToLedsMappingChange(int mappingType)
 | 
			
		||||
	QJsonObject data;
 | 
			
		||||
	data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType);
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::ImageToLedMappingUpdate, QVariant(data));
 | 
			
		||||
	doCallback(Subscription::ImageToLedMappingUpdate, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleAdjustmentChange()
 | 
			
		||||
@@ -335,7 +356,7 @@ void JsonCallbacks::handleVideoModeChange(VideoMode mode)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject data;
 | 
			
		||||
	data["videomode"] = QString(videoMode2String(mode));
 | 
			
		||||
	doCallback(Subscription::VideomodeUpdate, QVariant(data));
 | 
			
		||||
	doCallback(Subscription::VideomodeUpdate, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(ENABLE_EFFECTENGINE)
 | 
			
		||||
@@ -343,29 +364,29 @@ void JsonCallbacks::handleEffectListChange()
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject effects;
 | 
			
		||||
	effects["effects"] = JsonInfo::getEffects(_hyperion);
 | 
			
		||||
	doCallback(Subscription::EffectsUpdate, QVariant(effects));
 | 
			
		||||
	doCallback(Subscription::EffectsUpdate, effects);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleSettingsChange(settings::type type, const QJsonDocument& data)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject dat;
 | 
			
		||||
	QJsonObject obj;
 | 
			
		||||
	if(data.isObject()) {
 | 
			
		||||
		dat[typeToString(type)] = data.object();
 | 
			
		||||
		obj[typeToString(type)] = data.object();
 | 
			
		||||
	} else {
 | 
			
		||||
		dat[typeToString(type)] = data.array();
 | 
			
		||||
		obj[typeToString(type)] = data.array();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::SettingsUpdate, QVariant(dat));
 | 
			
		||||
	doCallback(Subscription::SettingsUpdate, obj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleLedsConfigChange(settings::type type, const QJsonDocument& data)
 | 
			
		||||
{
 | 
			
		||||
	if(type == settings::LEDS)
 | 
			
		||||
	{
 | 
			
		||||
		QJsonObject dat;
 | 
			
		||||
		dat[typeToString(type)] = data.array();
 | 
			
		||||
		doCallback(Subscription::LedsUpdate, QVariant(dat));
 | 
			
		||||
		QJsonObject obj;
 | 
			
		||||
		obj[typeToString(type)] = data.array();
 | 
			
		||||
		doCallback(Subscription::LedsUpdate, obj);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -385,7 +406,7 @@ void JsonCallbacks::handleTokenChange(const QVector<AuthManager::AuthDefinition>
 | 
			
		||||
		sub["last_use"] = entry.lastUse;
 | 
			
		||||
		arr.push_back(sub);
 | 
			
		||||
	}
 | 
			
		||||
	doCallback(Subscription::TokenUpdate, QVariant(arr));
 | 
			
		||||
	doCallback(Subscription::TokenUpdate, arr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleLedColorUpdate(const std::vector<ColorRgb> &ledColors)
 | 
			
		||||
@@ -393,13 +414,16 @@ void JsonCallbacks::handleLedColorUpdate(const std::vector<ColorRgb> &ledColors)
 | 
			
		||||
	QJsonObject result;
 | 
			
		||||
	QJsonArray leds;
 | 
			
		||||
 | 
			
		||||
	for (const auto &color : ledColors)
 | 
			
		||||
	// Avoid copying by appending RGB values directly
 | 
			
		||||
	for (const auto& color : ledColors)
 | 
			
		||||
	{
 | 
			
		||||
		leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue);
 | 
			
		||||
		leds.append(QJsonValue(color.red));
 | 
			
		||||
		leds.append(QJsonValue(color.green));
 | 
			
		||||
		leds.append(QJsonValue(color.blue));
 | 
			
		||||
	}
 | 
			
		||||
	result["leds"] = leds;
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::LedColorsUpdate, QVariant(result));
 | 
			
		||||
	doCallback(Subscription::LedColorsUpdate, result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleImageUpdate(const Image<ColorRgb> &image)
 | 
			
		||||
@@ -413,7 +437,7 @@ void JsonCallbacks::handleImageUpdate(const Image<ColorRgb> &image)
 | 
			
		||||
	QJsonObject result;
 | 
			
		||||
	result["image"] = "data:image/jpg;base64," + QString(byteArray.toBase64());
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::ImageUpdate, QVariant(result));
 | 
			
		||||
	doCallback(Subscription::ImageUpdate, result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &msg)
 | 
			
		||||
@@ -445,7 +469,7 @@ void JsonCallbacks::handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &msg)
 | 
			
		||||
	}
 | 
			
		||||
	result.insert("messages", messageArray);
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::LogMsgUpdate, QVariant(result));
 | 
			
		||||
	doCallback(Subscription::LogMsgUpdate, result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JsonCallbacks::handleEventUpdate(const Event &event)
 | 
			
		||||
@@ -454,6 +478,6 @@ void JsonCallbacks::handleEventUpdate(const Event &event)
 | 
			
		||||
 | 
			
		||||
	result["event"] = eventToString(event);
 | 
			
		||||
 | 
			
		||||
	doCallback(Subscription::EventUpdate, QVariant(result));
 | 
			
		||||
	doCallback(Subscription::EventUpdate, result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,13 @@
 | 
			
		||||
// python utils
 | 
			
		||||
#include <python/PythonProgram.h>
 | 
			
		||||
 | 
			
		||||
Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString& script, const QString& name, const QJsonObject& args, const QString& imageData)
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
	int DEFAULT_MAX_UPDATE_RATE_HZ { 200 };
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
Effect::Effect(Hyperion *hyperion, int priority, int timeout, const QString &script, const QString &name, const QJsonObject &args, const QString &imageData)
 | 
			
		||||
	: QThread()
 | 
			
		||||
	, _hyperion(hyperion)
 | 
			
		||||
	, _priority(priority)
 | 
			
		||||
@@ -26,7 +32,8 @@ Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString& scr
 | 
			
		||||
	, _endTime(-1)
 | 
			
		||||
	, _interupt(false)
 | 
			
		||||
	, _imageSize(hyperion->getLedGridSize())
 | 
			
		||||
	, _image(_imageSize, QImage::Format_ARGB32_Premultiplied)
 | 
			
		||||
	, _image(_imageSize,QImage::Format_ARGB32_Premultiplied)
 | 
			
		||||
	, _lowestUpdateIntervalInSeconds(1/static_cast<double>(DEFAULT_MAX_UPDATE_RATE_HZ))
 | 
			
		||||
{
 | 
			
		||||
	_colors.resize(_hyperion->getLedCount());
 | 
			
		||||
	_colors.fill(ColorRgb::BLACK);
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,7 @@ PyMethodDef EffectModule::effectMethods[] = {
 | 
			
		||||
	{"imageCOffset"          , EffectModule::wrapImageCOffset          , METH_VARARGS, "Add offset to the coordinate system"},
 | 
			
		||||
	{"imageCShear"           , EffectModule::wrapImageCShear           , METH_VARARGS, "Shear of coordinate system by the given horizontal/vertical axis"},
 | 
			
		||||
	{"imageResetT"           , EffectModule::wrapImageResetT           , METH_NOARGS,  "Resets all coords modifications (rotate,offset,shear)"},
 | 
			
		||||
	{"lowestUpdateInterval"  , EffectModule::wrapLowestUpdateInterval  , METH_NOARGS,  "Gets the lowest permissible interval time in seconds"},
 | 
			
		||||
	{NULL, NULL, 0, NULL}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -1106,3 +1107,10 @@ PyObject* EffectModule::wrapImageResetT(PyObject* self, PyObject* args)
 | 
			
		||||
	getEffect()->_painter->resetTransform();
 | 
			
		||||
	Py_RETURN_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PyObject* EffectModule::wrapLowestUpdateInterval(PyObject* self, PyObject* args)
 | 
			
		||||
{
 | 
			
		||||
	qDebug() << "_lowestUpdateIntervalInSeconds: " << getEffect()->_lowestUpdateIntervalInSeconds;
 | 
			
		||||
 | 
			
		||||
	return Py_BuildValue("d", getEffect()->_lowestUpdateIntervalInSeconds);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,13 @@
 | 
			
		||||
#include <boblightserver/BoblightServer.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_MAX_IMAGE_EMISSION_INTERVAL{ 40 }; // 25 Hz
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL{ 25 }; // 40 Hz
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL{ 5 }; // 200 Hz
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
Hyperion::Hyperion(quint8 instance)
 | 
			
		||||
	: QObject()
 | 
			
		||||
	, _instIndex(instance)
 | 
			
		||||
@@ -75,6 +82,12 @@ Hyperion::Hyperion(quint8 instance)
 | 
			
		||||
#if defined(ENABLE_BOBLIGHT_SERVER)
 | 
			
		||||
	, _boblightServer(nullptr)
 | 
			
		||||
#endif
 | 
			
		||||
	, _lastImageEmission(0)
 | 
			
		||||
	, _lastRawLedDataEmission(0)
 | 
			
		||||
	, _lastLedDeviceDataEmission(0)
 | 
			
		||||
	, _imageEmissionInterval(DEFAULT_MAX_IMAGE_EMISSION_INTERVAL)
 | 
			
		||||
	, _rawLedDataEmissionInterval(DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL)
 | 
			
		||||
	, _ledDeviceDataEmissionInterval(DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL)
 | 
			
		||||
{
 | 
			
		||||
	qRegisterMetaType<ComponentList>("ComponentList");
 | 
			
		||||
 | 
			
		||||
@@ -109,6 +122,8 @@ void Hyperion::start()
 | 
			
		||||
	// handle hwLedCount
 | 
			
		||||
	_hwLedCount = getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// Initialize colororder vector
 | 
			
		||||
	for (const Led& led : _ledString.leds())
 | 
			
		||||
	{
 | 
			
		||||
@@ -180,6 +195,15 @@ void Hyperion::start()
 | 
			
		||||
	connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor, this, &Hyperion::setColor);
 | 
			
		||||
	connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage, this, &Hyperion::setInputImage);
 | 
			
		||||
 | 
			
		||||
	// Limit LED data emission, if high rumber of LEDs configured
 | 
			
		||||
	_rawLedDataEmissionInterval = (_ledString.leds().size() > 1000) ? 2 * DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL : DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL;
 | 
			
		||||
	_ledDeviceDataEmissionInterval = (_hwLedCount > 1000) ? 2 * DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL : DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL;
 | 
			
		||||
 | 
			
		||||
	// Set up timers to throttle specific signals
 | 
			
		||||
	_imageTimer.start();  // Start the elapsed timer for image signal throttling
 | 
			
		||||
	_rawLedDataTimer.start();  // Start the elapsed timer for rawLedColors throttling
 | 
			
		||||
	_ledDeviceDataTimer.start(); // Start the elapsed timer for LED-Device data throttling
 | 
			
		||||
 | 
			
		||||
	// if there is no startup / background effect and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a priority change)
 | 
			
		||||
	update();
 | 
			
		||||
 | 
			
		||||
@@ -267,6 +291,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
 | 
			
		||||
		std::vector<ColorRgb> color(_ledString.leds().size(), ColorRgb{0,0,0});
 | 
			
		||||
		_ledBuffer = color;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		_ledStringColorOrder.clear();
 | 
			
		||||
		for (const Led& led : _ledString.leds())
 | 
			
		||||
		{
 | 
			
		||||
@@ -276,6 +301,10 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
 | 
			
		||||
		// handle hwLedCount update
 | 
			
		||||
		_hwLedCount = getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount());
 | 
			
		||||
 | 
			
		||||
		// Limit LED data emission, if high rumber of LEDs configured
 | 
			
		||||
		_rawLedDataEmissionInterval = (_ledString.leds().size() > 1000) ? 2 * DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL : DEFAULT_MAX_RAW_LED_DATA_EMISSION_INTERVAL;
 | 
			
		||||
		_ledDeviceDataEmissionInterval = (_hwLedCount > 1000) ? 2 * DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL : DEFAULT_MAX_LED_DEVICE_DATA_EMISSION_INTERVAL;
 | 
			
		||||
 | 
			
		||||
		// change in leds are also reflected in adjustment
 | 
			
		||||
		delete _raw2ledAdjustment;
 | 
			
		||||
		_raw2ledAdjustment = hyperion::createLedColorsAdjustment(static_cast<int>(_ledString.leds().size()), getSetting(settings::COLOR).object());
 | 
			
		||||
@@ -672,7 +701,14 @@ void Hyperion::update()
 | 
			
		||||
	Image<ColorRgb> image = priorityInfo.image;
 | 
			
		||||
	if (image.width() > 1 || image.height() > 1)
 | 
			
		||||
	{
 | 
			
		||||
		emit currentImage(image);
 | 
			
		||||
		_imageEmissionInterval = (image.width() > 1280) ?  2 * DEFAULT_MAX_IMAGE_EMISSION_INTERVAL : DEFAULT_MAX_IMAGE_EMISSION_INTERVAL;
 | 
			
		||||
		// Throttle the emission of currentImage(image) signal
 | 
			
		||||
		qint64 elapsedImageEmissionTime = _imageTimer.elapsed();
 | 
			
		||||
		if (elapsedImageEmissionTime - _lastImageEmission >= _imageEmissionInterval.count())
 | 
			
		||||
		{
 | 
			
		||||
			_lastImageEmission = elapsedImageEmissionTime;
 | 
			
		||||
			emit currentImage(image);  // Emit the image signal at the controlled rate
 | 
			
		||||
		}
 | 
			
		||||
		_ledBuffer = _imageProcessor->process(image);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
@@ -692,9 +728,15 @@ void Hyperion::update()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// emit rawLedColors before transform
 | 
			
		||||
	emit rawLedColors(_ledBuffer);
 | 
			
		||||
	// Throttle the emission of rawLedColors(_ledBuffer) signal
 | 
			
		||||
	qint64 elapsedRawLedDataEmissionTime = _rawLedDataTimer.elapsed();
 | 
			
		||||
	if (elapsedRawLedDataEmissionTime - _lastRawLedDataEmission >= _rawLedDataEmissionInterval.count())
 | 
			
		||||
	{
 | 
			
		||||
		_lastRawLedDataEmission = elapsedRawLedDataEmissionTime;
 | 
			
		||||
		emit rawLedColors(_ledBuffer);  // Emit the rawLedColors signal at the controlled rate
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Start transformations
 | 
			
		||||
	_raw2ledAdjustment->applyAdjustment(_ledBuffer);
 | 
			
		||||
 | 
			
		||||
	int i = 0;
 | 
			
		||||
@@ -740,7 +782,13 @@ void Hyperion::update()
 | 
			
		||||
		// Smoothing is disabled
 | 
			
		||||
		if  (! _deviceSmooth->enabled())
 | 
			
		||||
		{
 | 
			
		||||
			emit ledDeviceData(_ledBuffer);
 | 
			
		||||
			// Throttle the emission of LED-Device data signal
 | 
			
		||||
			qint64 elapsedLedDeviceDataEmissionTime = _ledDeviceDataTimer.elapsed();
 | 
			
		||||
			if (elapsedLedDeviceDataEmissionTime - _lastLedDeviceDataEmission >= _ledDeviceDataEmissionInterval.count())
 | 
			
		||||
			{
 | 
			
		||||
				_lastLedDeviceDataEmission = elapsedLedDeviceDataEmissionTime;
 | 
			
		||||
				emit ledDeviceData(_ledBuffer);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
 
 | 
			
		||||
@@ -124,13 +124,11 @@
 | 
			
		||||
				"ledshoriz": {
 | 
			
		||||
					"type": "integer",
 | 
			
		||||
					"minimum": 0,
 | 
			
		||||
					"maximum": 50,
 | 
			
		||||
					"default": 0
 | 
			
		||||
				},
 | 
			
		||||
				"ledsvert": {
 | 
			
		||||
					"type": "integer",
 | 
			
		||||
					"minimum": 0,
 | 
			
		||||
					"maximum": 50,
 | 
			
		||||
					"default": 0
 | 
			
		||||
				},
 | 
			
		||||
				"cabling": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
// project includes
 | 
			
		||||
#include "JsonClientConnection.h"
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
#include <api/JsonCallbacks.h>
 | 
			
		||||
 | 
			
		||||
// qt inc
 | 
			
		||||
#include <QTcpSocket>
 | 
			
		||||
@@ -17,8 +18,9 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, bool localConnect
 | 
			
		||||
	// create a new instance of JsonAPI
 | 
			
		||||
	_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
 | 
			
		||||
	// get the callback messages from JsonAPI and send it to the client
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::callbackMessage, this , &JsonClientConnection::sendMessage);
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::callbackReady, this , &JsonClientConnection::sendMessage);
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::forceClose, this , [&](){ _socket->close(); } );
 | 
			
		||||
	connect(_jsonAPI->getCallBack().get(), &JsonCallbacks::callbackReady, this, &JsonClientConnection::sendMessage);
 | 
			
		||||
 | 
			
		||||
	_jsonAPI->initialize();
 | 
			
		||||
}
 | 
			
		||||
@@ -47,7 +49,8 @@ void JsonClientConnection::readRequest()
 | 
			
		||||
qint64 JsonClientConnection::sendMessage(QJsonObject message)
 | 
			
		||||
{
 | 
			
		||||
	QJsonDocument writer(message);
 | 
			
		||||
	QByteArray data = writer.toJson(QJsonDocument::Compact) + "\n";
 | 
			
		||||
	QByteArray data = writer.toJson(QJsonDocument::Compact);
 | 
			
		||||
	data.append('\n');
 | 
			
		||||
 | 
			
		||||
	if (!_socket || (_socket->state() != QAbstractSocket::ConnectedState)) return 0;
 | 
			
		||||
	return _socket->write(data.data(), data.size());
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@
 | 
			
		||||
#include <QSslSocket>
 | 
			
		||||
 | 
			
		||||
//std includes
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
@@ -231,7 +230,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
 | 
			
		||||
	_networkManager->setTransferTimeout(_requestTimeout.count());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	QDateTime start = QDateTime::currentDateTime();
 | 
			
		||||
	QDateTime const start = QDateTime::currentDateTime();
 | 
			
		||||
	QString opCode;
 | 
			
		||||
	QNetworkReply* reply;
 | 
			
		||||
 | 
			
		||||
@@ -267,7 +266,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
 | 
			
		||||
 | 
			
		||||
	// Go into the loop until the request is finished.
 | 
			
		||||
	loop.exec();
 | 
			
		||||
	QDateTime end = QDateTime::currentDateTime();
 | 
			
		||||
	QDateTime const end = QDateTime::currentDateTime();
 | 
			
		||||
 | 
			
		||||
	httpResponse response = (reply->operation() == operation) ? getResponse(reply) : httpResponse();
 | 
			
		||||
 | 
			
		||||
@@ -283,18 +282,18 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
 | 
			
		||||
{
 | 
			
		||||
	httpResponse response;
 | 
			
		||||
 | 
			
		||||
	HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
 | 
			
		||||
	HttpStatusCode const httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
 | 
			
		||||
	response.setHttpStatusCode(httpStatusCode);
 | 
			
		||||
	response.setNetworkReplyError(reply->error());
 | 
			
		||||
	response.setHeaders(reply->rawHeaderPairs());
 | 
			
		||||
 | 
			
		||||
	if (reply->error() == QNetworkReply::NoError)
 | 
			
		||||
	{
 | 
			
		||||
		QByteArray replyData = reply->readAll();
 | 
			
		||||
		QByteArray const replyData = reply->readAll();
 | 
			
		||||
		if (!replyData.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			QJsonParseError error;
 | 
			
		||||
			QJsonDocument jsonDoc = QJsonDocument::fromJson(replyData, &error);
 | 
			
		||||
			QJsonDocument const jsonDoc = QJsonDocument::fromJson(replyData, &error);
 | 
			
		||||
 | 
			
		||||
			if (error.error != QJsonParseError::NoError)
 | 
			
		||||
			{
 | 
			
		||||
@@ -322,7 +321,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			if (httpStatusCode > 0) {
 | 
			
		||||
				QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
 | 
			
		||||
				QString const httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
 | 
			
		||||
				QString advise;
 | 
			
		||||
				switch ( httpStatusCode ) {
 | 
			
		||||
				case HttpStatusCode::BadRequest:
 | 
			
		||||
@@ -339,7 +338,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
 | 
			
		||||
					break;
 | 
			
		||||
				case HttpStatusCode::TooManyRequests:
 | 
			
		||||
				{
 | 
			
		||||
					QString retryAfterTime = response.getHeader("Retry-After");
 | 
			
		||||
					QString const retryAfterTime = response.getHeader("Retry-After");
 | 
			
		||||
					if (!retryAfterTime.isEmpty())
 | 
			
		||||
					{
 | 
			
		||||
						advise = "Retry-After: " + response.getHeader("Retry-After");
 | 
			
		||||
@@ -366,7 +365,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
 | 
			
		||||
 | 
			
		||||
void ProviderRestApi::setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value)
 | 
			
		||||
{
 | 
			
		||||
	QVariant headerValue = _networkRequestHeaders.header(header);
 | 
			
		||||
	QVariant const headerValue = _networkRequestHeaders.header(header);
 | 
			
		||||
	if (headerValue.isNull())
 | 
			
		||||
	{
 | 
			
		||||
		_networkRequestHeaders.setHeader(header, value);
 | 
			
		||||
@@ -394,7 +393,7 @@ void httpResponse::setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray httpResponse::getHeader(const QByteArray header) const
 | 
			
		||||
QByteArray httpResponse::getHeader(const QByteArray& header) const
 | 
			
		||||
{
 | 
			
		||||
	return _responseHeaders.value(header);
 | 
			
		||||
}
 | 
			
		||||
@@ -412,7 +411,7 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName)
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QSslCertificate cert (&caFile);
 | 
			
		||||
	QSslCertificate const cert (&caFile);
 | 
			
		||||
	caFile.close();
 | 
			
		||||
 | 
			
		||||
	QList<QSslCertificate> allowedCAs;
 | 
			
		||||
@@ -424,8 +423,8 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName)
 | 
			
		||||
#ifndef QT_NO_SSL
 | 
			
		||||
	if (QSslSocket::supportsSsl())
 | 
			
		||||
	{
 | 
			
		||||
		QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors, Qt::UniqueConnection );
 | 
			
		||||
		_networkManager->connectToHostEncrypted(_apiUrl.host(), _apiUrl.port(), configuration);
 | 
			
		||||
		QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors );
 | 
			
		||||
		_networkManager->connectToHostEncrypted(_apiUrl.host(), static_cast<quint16>(_apiUrl.port()), configuration);
 | 
			
		||||
		rc = true;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -453,8 +452,8 @@ bool ProviderRestApi::checkServerIdentity(const QSslConfiguration& sslConfig) co
 | 
			
		||||
	bool isServerIdentified {false};
 | 
			
		||||
 | 
			
		||||
	// Perform common name validation
 | 
			
		||||
	QSslCertificate serverCertificate = sslConfig.peerCertificate();
 | 
			
		||||
	QStringList commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName);
 | 
			
		||||
	QSslCertificate const serverCertificate = sslConfig.peerCertificate();
 | 
			
		||||
	QStringList const commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName);
 | 
			
		||||
	if ( commonName.contains(getAlternateServerIdentity(), Qt::CaseInsensitive) )
 | 
			
		||||
	{
 | 
			
		||||
		isServerIdentified = true;
 | 
			
		||||
@@ -467,27 +466,36 @@ bool ProviderRestApi::matchesPinnedCertificate(const QSslCertificate& certificat
 | 
			
		||||
{
 | 
			
		||||
	bool isMatching {false};
 | 
			
		||||
 | 
			
		||||
	QList certificateInfos = certificate.subjectInfo(QSslCertificate::CommonName);
 | 
			
		||||
	QList const certificateInfos = certificate.subjectInfo(QSslCertificate::CommonName);
 | 
			
		||||
 | 
			
		||||
	if (certificateInfos.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	QString identifier = certificateInfos.constFirst();
 | 
			
		||||
	QString const& identifier = certificateInfos.constFirst();
 | 
			
		||||
	QString const appDataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
 | 
			
		||||
	QString const certDir = appDataDir + "/certificates";
 | 
			
		||||
 | 
			
		||||
	QString appDataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
 | 
			
		||||
	QString certDir = appDataDir + "/certificates";
 | 
			
		||||
	QDir().mkpath(certDir);
 | 
			
		||||
	Debug (_log,"Directory used for pinned certificates: %s", QSTRING_CSTR(certDir));
 | 
			
		||||
 | 
			
		||||
	QString filePath(certDir + "/" + identifier + ".pem");
 | 
			
		||||
	bool const isMkPath = QDir().mkpath(certDir);
 | 
			
		||||
	if (!isMkPath)
 | 
			
		||||
	{
 | 
			
		||||
		Error (_log,"Failed to create directory \"%s\" to store pinned certificates. 'Trust on first use' is rejected.", QSTRING_CSTR(certDir));
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QString const fileName(identifier + ".pem");
 | 
			
		||||
	QString const filePath(certDir + "/" + fileName);
 | 
			
		||||
	QFile file(filePath);
 | 
			
		||||
 | 
			
		||||
	if (file.open(QIODevice::ReadOnly))
 | 
			
		||||
	{
 | 
			
		||||
		QList certificates = QSslCertificate::fromDevice(&file, QSsl::Pem);
 | 
			
		||||
		QList const certificates = QSslCertificate::fromDevice(&file, QSsl::Pem);
 | 
			
		||||
		if (!certificates.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			Debug (_log,"First used certificate loaded successfully");
 | 
			
		||||
			QSslCertificate pinnedeCertificate = certificates.constFirst();
 | 
			
		||||
			Debug (_log,"First used certificate \"%s\" loaded successfully", QSTRING_CSTR(fileName));
 | 
			
		||||
			QSslCertificate const& pinnedeCertificate = certificates.constFirst();
 | 
			
		||||
			if (pinnedeCertificate == certificate)
 | 
			
		||||
			{
 | 
			
		||||
				isMatching = true;
 | 
			
		||||
@@ -503,8 +511,8 @@ bool ProviderRestApi::matchesPinnedCertificate(const QSslCertificate& certificat
 | 
			
		||||
	{
 | 
			
		||||
		if (file.open(QIODevice::WriteOnly))
 | 
			
		||||
		{
 | 
			
		||||
			QByteArray pemData = certificate.toPem();
 | 
			
		||||
			qint64 bytesWritten = file.write(pemData);
 | 
			
		||||
			QByteArray const pemData = certificate.toPem();
 | 
			
		||||
			qint64 const bytesWritten = file.write(pemData);
 | 
			
		||||
			if (bytesWritten == pemData.size())
 | 
			
		||||
			{
 | 
			
		||||
				Debug (_log,"First used certificate saved to file: %s", QSTRING_CSTR(filePath));
 | 
			
		||||
@@ -538,7 +546,7 @@ void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList<QSslError>&
 | 
			
		||||
		if (_isSeflSignedCertificateAccpeted)
 | 
			
		||||
		{
 | 
			
		||||
			// Get the peer certificate associated with the error
 | 
			
		||||
			QSslCertificate certificate = error.certificate();
 | 
			
		||||
			QSslCertificate const certificate = error.certificate();
 | 
			
		||||
			if (matchesPinnedCertificate(certificate))
 | 
			
		||||
			{
 | 
			
		||||
				Debug (_log,"'Trust on first use' - Certificate received matches pinned certificate");
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ public:
 | 
			
		||||
	void setBody(const QJsonDocument& body) { _responseBody = body; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	QByteArray getHeader(const QByteArray header) const;
 | 
			
		||||
	QByteArray getHeader(const QByteArray& header) const;
 | 
			
		||||
	void setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs);
 | 
			
		||||
 | 
			
		||||
	QString getErrorReason() const { return _errorReason; }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
#include "QtHttpClientWrapper.h"
 | 
			
		||||
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
#include <api/JsonCallbacks.h>
 | 
			
		||||
 | 
			
		||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, bool localConnection, QtHttpClientWrapper* parent)
 | 
			
		||||
	: QObject(parent)
 | 
			
		||||
@@ -14,8 +15,10 @@ WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, bool localC
 | 
			
		||||
{
 | 
			
		||||
	const QString client = request->getClientInfo().clientAddress.toString();
 | 
			
		||||
	_jsonAPI = new JsonAPI(client, _log, localConnection, this, true);
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::callbackReady, this, &WebJsonRpc::sendCallbackMessage);
 | 
			
		||||
	connect(_jsonAPI, &JsonAPI::forceClose, [&]() { _wrapper->closeConnection(); _stopHandle = true; });
 | 
			
		||||
	connect(_jsonAPI->getCallBack().get(), &JsonCallbacks::callbackReady, this, &WebJsonRpc::sendCallbackMessage);
 | 
			
		||||
 | 
			
		||||
	_jsonAPI->initialize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +34,7 @@ void WebJsonRpc::handleMessage(QtHttpRequest* request)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WebJsonRpc::handleCallback(QJsonObject obj)
 | 
			
		||||
void WebJsonRpc::sendCallbackMessage(QJsonObject obj)
 | 
			
		||||
{
 | 
			
		||||
	// guard against wrong callbacks; TODO: Remove when JSONAPI is more solid
 | 
			
		||||
	if(!_unlocked) return;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,5 +26,5 @@ private:
 | 
			
		||||
	bool _unlocked = false;
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void handleCallback(QJsonObject obj);
 | 
			
		||||
	void sendCallbackMessage(QJsonObject obj);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
#include <hyperion/Hyperion.h>
 | 
			
		||||
#include <api/JsonAPI.h>
 | 
			
		||||
#include <api/JsonCallbacks.h>
 | 
			
		||||
 | 
			
		||||
#include <QTcpSocket>
 | 
			
		||||
#include <QtEndian>
 | 
			
		||||
@@ -24,9 +25,11 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, bool
 | 
			
		||||
 | 
			
		||||
	// Json processor
 | 
			
		||||
	_jsonAPI.reset(new JsonAPI(client, _log, localConnection, this));
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::callbackReady, this, &WebSocketClient::sendMessage);
 | 
			
		||||
	connect(_jsonAPI.get(), &JsonAPI::forceClose, this,[this]() { this->sendClose(CLOSECODE::NORMAL); });
 | 
			
		||||
 | 
			
		||||
	connect(_jsonAPI->getCallBack().get(), &JsonCallbacks::callbackReady, this, &WebSocketClient::sendMessage);
 | 
			
		||||
 | 
			
		||||
	connect(this, &WebSocketClient::handleMessage, _jsonAPI.get(), &JsonAPI::handleMessage);
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "New connection from %s", QSTRING_CSTR(client));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user