From d3707cb118febbe34b570f80788ce1a75ae07ea7 Mon Sep 17 00:00:00 2001 From: brindosch Date: Sat, 17 Jun 2017 23:29:04 +0200 Subject: [PATCH] Websocket auto serverinfo responder (#443) * serverinfo cb * remove webui cron * missing header file * tcp connection should trigger to --- assets/webconfig/content/connection_lost.html | 1 - assets/webconfig/content/restart.html | 1 - assets/webconfig/js/hyperion.js | 9 ---- include/hyperion/Hyperion.h | 27 +++++++++-- include/hyperion/PriorityMuxer.h | 20 +++++++- include/jsonserver/JsonServer.h | 11 +++++ libsrc/hyperion/CMakeLists.txt | 2 +- libsrc/hyperion/Hyperion.cpp | 47 ++++++++++++------- libsrc/hyperion/PriorityMuxer.cpp | 28 ++++++++++- libsrc/jsonserver/JsonClientConnection.cpp | 19 +++++++- libsrc/jsonserver/JsonClientConnection.h | 10 ++++ libsrc/jsonserver/JsonServer.cpp | 29 +++++++++++- 12 files changed, 166 insertions(+), 38 deletions(-) diff --git a/assets/webconfig/content/connection_lost.html b/assets/webconfig/content/connection_lost.html index 5441a7c9..847c5f15 100644 --- a/assets/webconfig/content/connection_lost.html +++ b/assets/webconfig/content/connection_lost.html @@ -51,7 +51,6 @@ function connectionLostAction() { if(!connectionLost) { - window.clearInterval(cronId); connectionLost = true; connectionTimer = window.setInterval(tryReconnect, 4000); } diff --git a/assets/webconfig/content/restart.html b/assets/webconfig/content/restart.html index eab7f4fb..fc85b876 100644 --- a/assets/webconfig/content/restart.html +++ b/assets/webconfig/content/restart.html @@ -37,7 +37,6 @@ function restartAction() { if(!connectionLost) { - window.clearInterval(cronId); connectionLost = true; connectionTimer = window.setInterval(tryReconnect, 1000); } diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 1b31f175..39695d7a 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -15,7 +15,6 @@ var jsonPort = 19444; var websocket = null; var hyperion = {}; var wsTan = 1; -var cronId = 0; var ledStreamActive = false; var imageStreamActive = false; var loggingStreamActive = false; @@ -32,13 +31,6 @@ function initRestart() connectionLostDetection('restart'); } -function cron() -{ - requestServerInfo(); - $(hyperion).trigger({type:"cron"}); -} - - function connectionLostDetection(type) { if ( watchdog > 2 ) @@ -82,7 +74,6 @@ function initWebSocket() $(hyperion).on("cmd-serverinfo", function(event) { watchdog = 0; }); - cronId = window.setInterval(cron,2000); }; websocket.onclose = function (event) { diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index af587b12..ce249101 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -13,6 +13,7 @@ #include #include #include +#include // hyperion-utils includes #include @@ -170,9 +171,9 @@ public: ComponentRegister& getComponentRegister() { return _componentRegister; }; - bool configModified(); + bool configModified() { return _configMod; }; - bool configWriteable(); + bool configWriteable() { return _configWrite; }; /// gets the methode how image is maped to leds int getLedMappingType() { return _ledMAppingType; }; @@ -308,6 +309,9 @@ signals: void emitImage(int priority, const Image & image, const int timeout_ms); void closing(); + /// Signal which is emitted, when state of config or bonjour or priorityMuxer changed + void hyperionStateChanged(); + private slots: /// /// Updates the priority muxer with the current time and (re)writes the led color with applied @@ -316,9 +320,12 @@ private slots: void update(); void currentBonjourRecordsChanged(const QList &list); - void bonjourRecordResolved(const QHostInfo &hostInfo, int port); + void bonjourRecordResolved(const QHostInfo &hostInfo, int port); void bonjourResolve(); + /// check for configWriteable and modified changes, called by _cTimer timeout() + void checkConfigState(); + private: /// @@ -335,6 +342,7 @@ private: LedString _ledStringClone; std::vector _ledStringColorOrder; + /// The priority muxer PriorityMuxer _muxer; @@ -356,7 +364,7 @@ private: // json configuration const QJsonObject& _qjsonConfig; - // the name of config file + /// the name of config file QString _configFile; /// The timer for handling priority channel timeouts @@ -396,4 +404,15 @@ private: BonjourServiceResolver _bonjourResolver; BonjourRegister _hyperionSessions; QString _bonjourCurrentServiceToResolve; + + /// Interval timer to check config write and mod + QTimer _cTimer; + + /// holds the prev states of configWriteable and modified + bool _prevConfigMod = false; + bool _prevConfigWrite = true; + + /// holds the current states of configWriteable and modified + bool _configMod = false; + bool _configWrite = true; }; diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index cd908f0e..cbdf0673 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -8,6 +8,8 @@ // QT includes #include +#include +#include // Utils includes #include @@ -18,8 +20,9 @@ /// and the muxer keeps track of all active priorities. The current priority can be queried and per /// priority the led colors. /// -class PriorityMuxer +class PriorityMuxer : public QObject { + Q_OBJECT public: /// /// The information structure for a single priority channel @@ -118,6 +121,18 @@ public: /// void setCurrentTime(const int64_t& now); +signals: + /// + /// Signal which is called, when a effect or color with timeout is running, once per second + /// + void timerunner(); + +private slots: + /// + /// Slots which is called to adapt to 1s interval for signal timerunner() + /// + void emitReq(); + private: /// The current priority (lowest value in _activeInputs) int _currentPriority; @@ -128,4 +143,7 @@ private: /// The information of the lowest priority channel InputInfo _lowestPriorityInfo; + QTimer _timer; + QTimer _blockTimer; + }; diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index 9cbc3deb..5ee71c18 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -6,6 +6,7 @@ // Qt includes #include #include +#include // Hyperion includes #include @@ -41,6 +42,10 @@ private slots: /// Slot which is called when a client tries to create a new connection /// void newConnection(); + /// + /// Slot which is called when a new forced serverinfo should be pushed + /// + void pushReq(); /// /// Slot which is called when a client closes a connection @@ -52,9 +57,15 @@ private: /// The TCP server object QTcpServer _server; + /// Link to Hyperion to get hyperion state emiter + Hyperion * _hyperion; + /// List with open connections QSet _openConnections; /// the logger instance Logger * _log; + + QTimer _timer; + QTimer _blockTimer; }; diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 71d39022..290b13a5 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -11,13 +11,13 @@ SET(Hyperion_QT_HEADERS ${CURRENT_SOURCE_DIR}/LinearColorSmoothing.h ${CURRENT_HEADER_DIR}/GrabberWrapper.h ${CURRENT_HEADER_DIR}/ComponentRegister.h + ${CURRENT_HEADER_DIR}/PriorityMuxer.h ) SET(Hyperion_HEADERS ${CURRENT_HEADER_DIR}/ImageProcessorFactory.h ${CURRENT_HEADER_DIR}/ImageToLedsMap.h ${CURRENT_HEADER_DIR}/LedString.h - ${CURRENT_HEADER_DIR}/PriorityMuxer.h ${CURRENT_SOURCE_DIR}/MultiColorAdjustment.h ${CURRENT_HEADER_DIR}/MessageForwarder.h diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index aeef591e..689d603f 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // QT includes #include @@ -444,9 +445,13 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile) Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount); WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section"); - WarningIf(!configWriteable(), _log, "Your config is not writeable - you won't be able to use the web ui for configuration."); - // initialize hash of current config - configModified(); + // setup interval timer for config state checks and initial shot + checkConfigState(); + QObject::connect(&_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); + _cTimer.start(2000); + + // pipe muxer signal for effect/color timerunner to hyperionStateChanged signal + QObject::connect(&_muxer, &PriorityMuxer::timerunner, this, &Hyperion::hyperionStateChanged); const QJsonObject & generalConfig = qjsonConfig["general"].toObject(); _configVersionId = generalConfig["configVersion"].toInt(-1); @@ -511,6 +516,9 @@ void Hyperion::bonjourRecordResolved(const QHostInfo &hostInfo, int port) _hyperionSessions[_bonjourCurrentServiceToResolve].port = port; _hyperionSessions[_bonjourCurrentServiceToResolve].address = hostInfo.addresses().isEmpty() ? "" : hostInfo.addresses().first().toString(); Debug(_log, "found hyperion session: %s:%d",QSTRING_CSTR(hostInfo.hostName()), port); + + //emit change + emit hyperionStateChanged(); } } @@ -532,9 +540,9 @@ Hyperion::BonjourRegister Hyperion::getHyperionSessions() return _hyperionSessions; } -bool Hyperion::configModified() +void Hyperion::checkConfigState() { - bool isModified = false; + // Check config modifications QFile f(_configFile); if (f.open(QFile::ReadOnly)) { @@ -545,25 +553,29 @@ bool Hyperion::configModified() { _configHash = hash.result(); } - else - { - isModified = _configHash != hash.result(); - } + _configMod = _configHash != hash.result() ? true : false; } } f.close(); - return isModified; -} + if(_prevConfigMod != _configMod) + { + emit hyperionStateChanged(); + _prevConfigMod = _configMod; + } -bool Hyperion::configWriteable() -{ + // Check config writeable QFile file(_configFile); QFileInfo fileInfo(file); - return fileInfo.isWritable() && fileInfo.isReadable(); + _configWrite = fileInfo.isWritable() && fileInfo.isReadable() ? true : false; + + if(_prevConfigWrite != _configWrite) + { + emit hyperionStateChanged(); + _prevConfigWrite = _configWrite; + } } - void Hyperion::registerPriority(const QString &name, const int priority/*, const QString &origin*/) { Info(_log, "Register new input source named '%s' for priority channel '%d'", QSTRING_CSTR(name), priority ); @@ -575,12 +587,14 @@ void Hyperion::registerPriority(const QString &name, const int priority/*, const } _priorityRegister.insert(name, priority); + emit hyperionStateChanged(); } void Hyperion::unRegisterPriority(const QString &name) { Info(_log, "Unregister input source named '%s' from priority register", QSTRING_CSTR(name)); _priorityRegister.remove(name); + emit hyperionStateChanged(); } void Hyperion::setSourceAutoSelectEnabled(bool enabled) @@ -868,7 +882,8 @@ void Hyperion::update() else { int timeout_ms = std::max(0, int(priorityInfo.timeoutTime_ms - QDateTime::currentMSecsSinceEpoch())); - _timer.start(timeout_ms); + // std::min() 200ms forced refresh if color is active to update priorityMuxer properly for forced serverinfo push + _timer.start(std::min(timeout_ms, 200)); } } diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index a71df018..5000b6cc 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -1,4 +1,4 @@ - +#include // STL includes #include #include @@ -18,6 +18,11 @@ PriorityMuxer::PriorityMuxer(int ledCount) _lowestPriorityInfo.origin = "System"; _activeInputs[_currentPriority] = _lowestPriorityInfo; + + // do a reuqest after blocking timer runs out + connect(&_timer, SIGNAL(timeout()), this, SLOT(emitReq())); + _timer.setSingleShot(true); + _blockTimer.setSingleShot(true); } PriorityMuxer::~PriorityMuxer() @@ -96,9 +101,28 @@ void PriorityMuxer::setCurrentTime(const int64_t& now) infoIt = _activeInputs.erase(infoIt); } else - { + { _currentPriority = std::min(_currentPriority, infoIt->priority); + + // call emitReq when effect or color is running with timeout > -1, blacklist prio 255 + if(infoIt->priority < 254 && infoIt->timeoutTime_ms > -1 && (infoIt->componentId == hyperion::COMP_EFFECT || infoIt->componentId == hyperion::COMP_COLOR)) + { + emitReq(); + } ++infoIt; } } } + +void PriorityMuxer::emitReq() +{ + if(_blockTimer.isActive()) + { + _timer.start(500); + } + else + { + emit timerunner(); + _blockTimer.start(1000); + } +} diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index a27d00f3..0af414cc 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -48,7 +48,6 @@ using namespace hyperion; -int _connectionCounter = 0; std::map JsonClientConnection::_componentsPrevState; JsonClientConnection::JsonClientConnection(QTcpSocket *socket) @@ -83,7 +82,7 @@ JsonClientConnection::~JsonClientConnection() void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); - + if (_webSocketHandshakeDone) { // websocket mode, data frame @@ -237,6 +236,7 @@ void JsonClientConnection::doWebSocketHandshake() _socket->write(h.str().c_str()); _socket->flush(); + // we are in WebSocket mode, data frames should follow next _webSocketHandshakeDone = true; } @@ -1394,6 +1394,14 @@ void JsonClientConnection::sendSuccessReply(const QString &command, const int ta // send reply sendMessage(reply); + + // blacklisted commands for emitter + QVector vector; + vector << "ledcolors-imagestream-stop" << "ledcolors-imagestream-start" << "ledcolors-ledstream-stop" << "ledcolors-ledstream-start" << "logging-start" << "logging-stop"; + if(vector.indexOf(command) == -1) + { + emit pushReq(); + } } void JsonClientConnection::sendErrorReply(const QString &error, const QString &command, const int tan) @@ -1510,4 +1518,11 @@ void JsonClientConnection::setImage(int priority, const Image & image, } } +void JsonClientConnection::forceServerInfo() +{ + const QString command("serverinfo"); + const int tan = 1; + const QJsonObject obj; + handleServerInfoCommand(obj,command,tan); +} diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 81b7e72b..f925fa6e 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -114,6 +114,11 @@ public: /// ~JsonClientConnection(); + /// + /// send a forced serverinfo to a client + /// + void forceServerInfo(); + public slots: void componentStateChanged(const hyperion::Components component, bool enable); void streamLedcolorsUpdate(); @@ -127,6 +132,11 @@ signals: /// void connectionClosed(JsonClientConnection * connection); + /// + /// Signal which is emitted when a sendSuccessReply() has been executed + /// + void pushReq(); + private slots: /// /// Slot called when new data has arrived diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index c6ef055d..56e4bc9c 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -8,6 +8,7 @@ JsonServer::JsonServer(uint16_t port) : QObject() , _server() + , _hyperion(Hyperion::getInstance()) , _openConnections() , _log(Logger::getInstance("JSONSERVER")) { @@ -27,6 +28,14 @@ JsonServer::JsonServer(uint16_t port) // Set trigger for incoming connections connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + // connect delay timer and setup + connect(&_timer, SIGNAL(timeout()), this, SLOT(pushReq())); + _timer.setSingleShot(true); + _blockTimer.setSingleShot(true); + + // register for hyprion state changes (bonjour, config, prioritymuxer, register/unregister source) + connect(_hyperion, SIGNAL(hyperionStateChanged()), this, SLOT(pushReq())); + // make sure the resources are loaded (they may be left out after static linking Q_INIT_RESOURCE(JsonSchemas); @@ -50,10 +59,13 @@ void JsonServer::newConnection() if (socket != nullptr) { - Debug(_log, "New connection"); + Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str()); JsonClientConnection * connection = new JsonClientConnection(socket); _openConnections.insert(connection); + // register for JSONClientConnection events + connect(connection, SIGNAL(pushReq()), this, SLOT(pushReq())); + // register slot for cleaning up after the connection closed connect(connection, SIGNAL(connectionClosed(JsonClientConnection*)), this, SLOT(closedConnection(JsonClientConnection*))); } @@ -67,3 +79,18 @@ void JsonServer::closedConnection(JsonClientConnection *connection) // schedule to delete the connection object connection->deleteLater(); } + +void JsonServer::pushReq() +{ + if(_blockTimer.isActive()) + { + _timer.start(250); + } + else + { + foreach (JsonClientConnection * connection, _openConnections) { + connection->forceServerInfo(); + } + _blockTimer.start(250); + } +}