diff --git a/include/boblightserver/BoblightServer.h b/include/boblightserver/BoblightServer.h new file mode 100644 index 00000000..d4049170 --- /dev/null +++ b/include/boblightserver/BoblightServer.h @@ -0,0 +1,57 @@ +#pragma once + +// system includes +#include + +// Qt includes +#include +#include + +// Hyperion includes +#include + +class BoblightClientConnection; + +/// +/// This class creates a TCP server which accepts connections from boblight clients. +/// +class BoblightServer : public QObject +{ + Q_OBJECT + +public: + /// + /// BoblightServer constructor + /// @param hyperion Hyperion instance + /// @param port port number on which to start listening for connections + /// + BoblightServer(Hyperion * hyperion, uint16_t port = 19333); + ~BoblightServer(); + + /// + /// @return the port number on which this TCP listens for incoming connections + /// + uint16_t getPort() const; + +private slots: + /// + /// Slot which is called when a client tries to create a new connection + /// + void newConnection(); + + /// + /// Slot which is called when a client closes a connection + /// @param connection The Connection object which is being closed + /// + void closedConnection(BoblightClientConnection * connection); + +private: + /// Hyperion instance + Hyperion * _hyperion; + + /// The TCP server object + QTcpServer _server; + + /// List with open connections + QSet _openConnections; +}; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index b1b03de3..7e847dbe 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -52,6 +52,17 @@ public: /// void process(const RgbImage& image, std::vector& ledColors); + /// + /// Get the hscan and vscan parameters for a single led + /// + /// @param[in] led Index of the led + /// @param[out] hscanBegin begin of the hscan + /// @param[out] hscanEnd end of the hscan + /// @param[out] vscanBegin begin of the hscan + /// @param[out] vscanEnd end of the hscan + /// @return true if the parameters could be retrieved + bool getScanParameters(size_t led, double & hscanBegin, double & hscanEnd, double & vscanBegin, double & vscanEnd) const; + private: /// Friend declaration of the factory for creating ImageProcessor's friend class ImageProcessorFactory; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index a95f328f..a6874d3a 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -8,5 +8,6 @@ add_subdirectory(dispmanx-grabber) add_subdirectory(hyperion) add_subdirectory(jsonserver) add_subdirectory(protoserver) +add_subdirectory(boblightserver) add_subdirectory(utils) add_subdirectory(xbmcvideochecker) diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp new file mode 100644 index 00000000..5c8ad796 --- /dev/null +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -0,0 +1,217 @@ +// system includes +#include +#include +#include +#include + +// stl includes +#include +#include +#include + +// Qt includes +#include +#include + +// hyperion util includes +#include "hyperion/ImageProcessorFactory.h" +#include "hyperion/ImageProcessor.h" +#include "utils/RgbColor.h" + +// project includes +#include "BoblightClientConnection.h" + +BoblightClientConnection::BoblightClientConnection(QTcpSocket *socket, Hyperion * hyperion) : + QObject(), + _locale(QLocale::C), + _socket(socket), + _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()), + _hyperion(hyperion), + _receiveBuffer(), + _priority(255), + _ledColors(hyperion->getLedCount(), RgbColor::BLACK) +{ + // initalize the locale. Start with the default C-locale + _locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator); + + // connect internal signals and slots + connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); + connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); +} + +BoblightClientConnection::~BoblightClientConnection() +{ + if (_priority < 255) + { + // clear the current channel + _hyperion->clear(_priority); + _priority = 255; + } + + delete _socket; +} + +void BoblightClientConnection::readData() +{ + _receiveBuffer += _socket->readAll(); + + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string (strip the newline) + QString message = QString::fromAscii(_receiveBuffer.data(), bytes-1); + + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); + + // handle message + handleMessage(message); + + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } +} + +void BoblightClientConnection::socketClosed() +{ + if (_priority < 255) + { + // clear the current channel + _hyperion->clear(_priority); + _priority = 255; + } + + emit connectionClosed(this); +} + +void BoblightClientConnection::handleMessage(const QString & message) +{ + //std::cout << "boblight message: " << message.toStdString() << std::endl; + + QStringList messageParts = message.split(" ", QString::SkipEmptyParts); + + if (messageParts.size() > 0) + { + if (messageParts[0] == "hello") + { + sendMessage("hello\n"); + return; + } + else if (messageParts[0] == "ping") + { + sendMessage("ping 1\n"); + return; + } + else if (messageParts[0] == "get" && messageParts.size() > 1) + { + if (messageParts[1] == "version") + { + sendMessage("version 5\n"); + return; + } + else if (messageParts[1] == "lights") + { + sendLightMessage(); + return; + } + } + else if (messageParts[0] == "set" && messageParts.size() > 2) + { + if (messageParts.size() > 3 && messageParts[1] == "light") + { + bool rc; + unsigned ledIndex = messageParts[2].toUInt(&rc); + if (rc && ledIndex < _ledColors.size()) + { + if (messageParts[3] == "rgb" && messageParts.size() == 7) + { + bool rc1, rc2, rc3; + uint8_t red = 255 * messageParts[4].toFloat(&rc1); + if (!rc1) + { + // maybe a locale issue. switch to a locale with a comma instead of a dot as decimal seperator (or vice versa) + _locale = QLocale((_locale.decimalPoint() == QChar('.')) ? QLocale::Dutch : QLocale::C); + _locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator); + + // try again + red = 255 * messageParts[4].toFloat(&rc1); + } + + uint8_t green = 255 * messageParts[5].toFloat(&rc2); + uint8_t blue = 255 * messageParts[6].toFloat(&rc3); + + if (rc1 && rc2 && rc3) + { + RgbColor & rgb = _ledColors[ledIndex]; + rgb.red = red; + rgb.green = green; + rgb.blue = blue; + return; + } + } + else if(messageParts[3] == "speed" || + messageParts[3] == "interpolation" || + messageParts[3] == "use" || + messageParts[3] == "singlechange") + { + // these message are ignored by Hyperion + return; + } + } + } + else if (messageParts.size() == 3 && messageParts[1] == "priority") + { + bool rc; + int prio = messageParts[2].toInt(&rc); + if (rc && prio != _priority) + { + if (_priority < 255) + { + // clear the current channel + _hyperion->clear(_priority); + } + + _priority = prio; + return; + } + } + } + else if (messageParts[0] == "sync") + { + // send current color values to hyperion + if (_priority < 255) + { + _hyperion->setColors(_priority, _ledColors, -1); + } + } + } + + std::cout << "unknown boblight message: " << message.toStdString() << std::endl; +} + +void BoblightClientConnection::sendMessage(const std::string & message) +{ + //std::cout << "send boblight message: " << message; + _socket->write(message.c_str(), message.size()); +} + +void BoblightClientConnection::sendMessage(const char * message, int size) +{ + //std::cout << "send boblight message: " << std::string(message, size); + _socket->write(message, size); +} + +void BoblightClientConnection::sendLightMessage() +{ + char buffer[256]; + int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount()); + sendMessage(buffer, n); + + for (unsigned i = 0; i < _hyperion->getLedCount(); ++i) + { + double h0, h1, v0, v1; + _imageProcessor->getScanParameters(i, h0, h1, v0, v1); + n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); + sendMessage(buffer, n); + } +} diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h new file mode 100644 index 00000000..6bdbdac4 --- /dev/null +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -0,0 +1,103 @@ +#pragma once + +// stl includes +#include + +// Qt includes +#include +#include +#include + +// Hyperion includes +#include + +class ImageProcessor; + +/// +/// The Connection object created by \a BoblightServer when a new connection is establshed +/// +class BoblightClientConnection : public QObject +{ + Q_OBJECT + +public: + /// + /// Constructor + /// @param socket The Socket object for this connection + /// @param hyperion The Hyperion server + /// + BoblightClientConnection(QTcpSocket * socket, Hyperion * hyperion); + + /// + /// Destructor + /// + ~BoblightClientConnection(); + +signals: + /// + /// Signal which is emitted when the connection is being closed + /// @param connection This connection object + /// + void connectionClosed(BoblightClientConnection * connection); + +private slots: + /// + /// Slot called when new data has arrived + /// + void readData(); + + /// + /// Slot called when this connection is being closed + /// + void socketClosed(); + +private: + /// + /// Handle an incoming boblight message + /// + /// @param message the incoming message as string + /// + void handleMessage(const QString &message); + + /// + /// Send a message to the connected client + /// + /// @param message The boblight message to send + /// + void sendMessage(const std::string &message); + + /// + /// Send a message to the connected client + /// + /// @param message The boblight message to send + /// @param size The size of the message + /// + void sendMessage(const char * message, int size); + + /// + /// Send a lights message the to connected client + /// + void sendLightMessage(); + +private: + /// Locale used for parsing floating point values + QLocale _locale; + + /// The TCP-Socket that is connected tot the boblight-client + QTcpSocket * _socket; + + /// The processor for translating images to led-values + ImageProcessor * _imageProcessor; + + /// Link to Hyperion for writing led-values to a priority channel + Hyperion * _hyperion; + + /// The buffer used for reading data from the socket + QByteArray _receiveBuffer; + + /// The priority used by this connection + int _priority; + + /// The latest led color data + std::vector _ledColors; +}; diff --git a/libsrc/boblightserver/BoblightServer.cpp b/libsrc/boblightserver/BoblightServer.cpp new file mode 100644 index 00000000..d7a45cf3 --- /dev/null +++ b/libsrc/boblightserver/BoblightServer.cpp @@ -0,0 +1,57 @@ +// system includes +#include + +// project includes +#include +#include "BoblightClientConnection.h" + +BoblightServer::BoblightServer(Hyperion *hyperion, uint16_t port) : + QObject(), + _hyperion(hyperion), + _server(), + _openConnections() +{ + if (!_server.listen(QHostAddress::Any, port)) + { + throw std::runtime_error("Boblight server could not bind to port"); + } + + // Set trigger for incoming connections + connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); +} + +BoblightServer::~BoblightServer() +{ + foreach (BoblightClientConnection * connection, _openConnections) { + delete connection; + } +} + +uint16_t BoblightServer::getPort() const +{ + return _server.serverPort(); +} + +void BoblightServer::newConnection() +{ + QTcpSocket * socket = _server.nextPendingConnection(); + + if (socket != nullptr) + { + std::cout << "New boblight connection" << std::endl; + BoblightClientConnection * connection = new BoblightClientConnection(socket, _hyperion); + _openConnections.insert(connection); + + // register slot for cleaning up after the connection closed + connect(connection, SIGNAL(connectionClosed(BoblightClientConnection*)), this, SLOT(closedConnection(BoblightClientConnection*))); + } +} + +void BoblightServer::closedConnection(BoblightClientConnection *connection) +{ + std::cout << "Boblight connection closed" << std::endl; + _openConnections.remove(connection); + + // schedule to delete the connection object + connection->deleteLater(); +} diff --git a/libsrc/boblightserver/CMakeLists.txt b/libsrc/boblightserver/CMakeLists.txt new file mode 100644 index 00000000..af825e74 --- /dev/null +++ b/libsrc/boblightserver/CMakeLists.txt @@ -0,0 +1,36 @@ + +# Define the current source locations +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/boblightserver) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/boblightserver) + +# Group the headers that go through the MOC compiler +set(BoblightServer_QT_HEADERS + ${CURRENT_HEADER_DIR}/BoblightServer.h + ${CURRENT_SOURCE_DIR}/BoblightClientConnection.h +) + +set(BoblightServer_HEADERS +) + +set(BoblightServer_SOURCES + ${CURRENT_SOURCE_DIR}/BoblightServer.cpp + ${CURRENT_SOURCE_DIR}/BoblightClientConnection.cpp +) + +qt4_wrap_cpp(BoblightServer_HEADERS_MOC ${BoblightServer_QT_HEADERS}) + +add_library(boblightserver + ${BoblightServer_HEADERS} + ${BoblightServer_QT_HEADERS} + ${BoblightServer_SOURCES} + ${BoblightServer_HEADERS_MOC} +) + +target_link_libraries(boblightserver + hyperion + hyperion-utils) + +qt4_use_modules(boblightserver + Core + Gui + Network) diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index c851c7b3..6300c4f0 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -67,6 +67,20 @@ void ImageProcessor::process(const RgbImage& image, std::vector& ledCo mImageToLeds->getMeanLedColor(image, ledColors); } +bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &hscanEnd, double &vscanBegin, double &vscanEnd) const +{ + if (led < mLedString.leds().size()) + { + const Led & l = mLedString.leds()[led]; + hscanBegin = l.minX_frac; + hscanEnd = l.maxX_frac; + vscanBegin = l.minY_frac; + vscanEnd = l.maxY_frac; + } + + return false; +} + void ImageProcessor::verifyBorder(const RgbImage& image) { if(_enableBlackBorderRemoval && _borderProcessor->process(image)) diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 7cd24ef7..d6f7c7bf 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -295,6 +295,20 @@ "additionalProperties" : false }, "protoServer" : + { + "type" : "object", + "required" : false, + "properties" : { + "port" : { + "type" : "integer", + "required" : true, + "minimum" : 0, + "maximum" : 65535 + } + }, + "additionalProperties" : false + }, + "boblightServer" : { "type" : "object", "required" : false, diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 74dc6e9a..757eed66 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -42,7 +42,7 @@ void JsonClientConnection::readData() _receiveBuffer += _socket->readAll(); int bytes = _receiveBuffer.indexOf('\n') + 1; - if (bytes != 0) + while(bytes > 0) { // create message string std::string message(_receiveBuffer.data(), bytes); @@ -52,6 +52,9 @@ void JsonClientConnection::readData() // handle message handleMessage(message); + + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; } } diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index 01e3488a..2f75e10b 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -8,4 +8,5 @@ target_link_libraries(hyperiond dispmanx-grabber xbmcvideochecker jsonserver - protoserver) + protoserver + boblightserver) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 8d17d92d..a119cffc 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -26,6 +26,9 @@ // ProtoServer includes #include +// BoblightServer includes +#include + void signal_handler(const int signum) { QCoreApplication::quit(); @@ -153,6 +156,15 @@ int main(int argc, char** argv) std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl; } + // Create Boblight server if configuration is present + BoblightServer * boblightServer = nullptr; + if (config.isMember("boblightServer")) + { + const Json::Value & boblightServerConfig = config["boblightServer"]; + boblightServer = new BoblightServer(&hyperion, boblightServerConfig["port"].asUInt()); + std::cout << "Boblight server created and started on port " << boblightServer->getPort() << std::endl; + } + // run the application int rc = app.exec(); std::cout << "Application closed with code " << rc << std::endl;