diff --git a/.gitmodules b/.gitmodules index 981fff4d..31ddf173 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,8 @@ [submodule "dependencies/external/flatbuffers"] path = dependencies/external/flatbuffers url = https://github.com/google/flatbuffers + branch = master +[submodule "dependencies/external/protobuf"] + path = dependencies/external/protobuf + url = https://github.com/hyperion-project/protobuf.git + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 6615eee7..a8b24eca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ SET ( DEFAULT_QT ON ) SET ( DEFAULT_WS281XPWM OFF ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS OFF ) +SET ( DEFAULT_USE_SYSTEM_PROTO_LIBS OFF ) SET ( DEFAULT_TESTS OFF ) IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) @@ -164,6 +165,9 @@ message(STATUS "ENABLE_PROFILER = ${ENABLE_PROFILER}") SET ( FLATBUFFERS_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/flatbuf ) SET ( FLATBUFFERS_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/flatbuf ) +SET ( PROTOBUF_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/proto ) +SET ( PROTOBUF_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/proto ) + # check all json files FILE ( GLOB_RECURSE HYPERION_SCHEMAS RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/libsrc/*schema*.json ) SET( JSON_FILES diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index 373dd697..bfced0ff 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -21,6 +21,7 @@ "general_comp_UDPLISTENER" : "UDP Listener", "general_comp_BOBLIGHTSERVER" : "Boblight Server", "general_comp_FLATBUFSERVER" : "Flatbuffers Server", + "general_comp_PROTOSERVER" : "Protocol Buffers Server", "general_comp_GRABBER" : "Plattform Aufnahme", "general_comp_V4L" : "USB Aufnahme", "general_comp_LEDDEVICE" : "LED Hardware", @@ -48,7 +49,7 @@ "dashboard_infobox_label_latesthyp" : "Aktuellste Hyperion Version:", "dashboard_infobox_label_platform" : "Plattform:", "dashboard_infobox_label_instance" : "Instanz:", - "dashboard_infobox_label_ports" : "Port flatbuf:", + "dashboard_infobox_label_ports" : "Ports (flat|proto):", "dashboard_infobox_message_updatewarning" : "Eine aktuellere Version von Hyperion ist verfügbar! (V$1)", "dashboard_infobox_message_updatesuccess" : "Du nutzt die aktuellste Version von Hyperion.", "dashboard_infobox_label_statush" : "Hyperion Status:", @@ -169,6 +170,7 @@ "conf_network_bobl_intro" : "Boblight Empfänger", "conf_network_udpl_intro" : "UDP Empfänger", "conf_network_fbs_intro" : "Google Flatbuffers Empfänger. Wird genutzt für schnellen Bildempfang.", + "conf_network_proto_intro" : "Der PROTO-Port dieser Hyperion-Instanz, wird genutzt für \"Bildstreams\" (HyperionScreenCap, Kodi Addon, ...)", "conf_network_forw_intro" : "Leite alles an eine zweite Hyperion Instanz weiter, diese kann dann mit einer anderen LED Steuerung genutzt werden", "conf_logging_label_intro" : "Überprüfe die Meldungen im Prokotoll um zu erfahren was Hyperion gerade beschäftigt. Je nach eingestellter Protokoll-Stufe siehst du mehr oder weniger Informationen.", "conf_logging_btn_pbupload" : "Bericht für Supportanfrage hochladen", @@ -572,6 +574,9 @@ "edt_conf_fbs_heading_title" : "Flatbuffers Server", "edt_conf_fbs_timeout_title" : "Zeitüberschreitung", "edt_conf_fbs_timeout_expl" : "Wenn für die angegebene Zeit keine Daten empfangen werden, wird die Komponente (vorübergehend) deaktiviert", + "edt_conf_pbs_heading_title" : "Protocol Buffers Server", + "edt_conf_pbs_timeout_title" : "Zeitüberschreitung", + "edt_conf_pbs_timeout_expl" : "Wenn für die angegebene Zeit keine Daten empfangen werden, wird die Komponente (vorübergehend) deaktiviert", "edt_conf_bobls_heading_title" : "Boblight Server", "edt_conf_udpl_heading_title" : "UDP Listener", "edt_conf_udpl_address_title" : "Adresse", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 6dd08730..496c7e76 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -21,6 +21,7 @@ "general_comp_UDPLISTENER" : "UDP Listener", "general_comp_BOBLIGHTSERVER" : "Boblight Server", "general_comp_FLATBUFSERVER" : "Flatbuffers Server", + "general_comp_PROTOSERVER" : "Protocol Buffers Server", "general_comp_GRABBER" : "Platform Capture", "general_comp_V4L" : "USB Capture", "general_comp_LEDDEVICE" : "LED device", @@ -48,7 +49,7 @@ "dashboard_infobox_label_latesthyp" : "Latest Hyperion version:", "dashboard_infobox_label_platform" : "Platform:", "dashboard_infobox_label_instance" : "Instance:", - "dashboard_infobox_label_ports" : "Port flatbuf:", + "dashboard_infobox_label_ports" : "Ports (flat|proto):", "dashboard_infobox_message_updatewarning" : "A newer version of Hyperion is available! ($1)", "dashboard_infobox_message_updatesuccess" : "You run the latest version of Hyperion.", "dashboard_infobox_label_statush" : "Hyperion status:", @@ -169,6 +170,7 @@ "conf_network_bobl_intro" : "Receiver for Boblight", "conf_network_udpl_intro" : "Receiver for UDP", "conf_network_fbs_intro" : "Google Flatbuffers Receiver. Used for fast image transmission.", + "conf_network_proto_intro" : "The PROTO-Port of this Hyperion instance, used for picture streams (HyperionScreenCap, Kodi Addon, ...)", "conf_network_forw_intro" : "Forward all input to a second Hyperion instance which could be driven with another led controller", "conf_logging_label_intro" : "Area to check log messages, depending on loglevel setting you see more or less information.", "conf_logging_btn_pbupload" : "Upload report for support request", @@ -573,6 +575,9 @@ "edt_conf_fbs_heading_title" : "Flatbuffers Server", "edt_conf_fbs_timeout_title" : "Timeout", "edt_conf_fbs_timeout_expl" : "If no data are received for the given period, the component will be (soft) disabled.", + "edt_conf_pbs_heading_title" : "Protocol Buffers Server", + "edt_conf_pbs_timeout_title" : "Timeout", + "edt_conf_pbs_timeout_expl" : "If no data are received for the given period, the component will be (soft) disabled.", "edt_conf_bobls_heading_title" : "Boblight Server", "edt_conf_udpl_heading_title" : "UDP Listener", "edt_conf_udpl_address_title" : "Address", diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index b43ca531..949edab9 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -77,7 +77,7 @@ $(document).ready( function() { $('#dash_leddevice').html(serverInfo.ledDevices.active); $('#dash_currv').html(currentVersion); $('#dash_instance').html(serverConfig.general.name); - $('#dash_ports').html(serverConfig.flatbufServer.port); + $('#dash_ports').html(serverConfig.flatbufServer.port+' | '+serverConfig.protoServer.port); $.get( "https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/version.json", function( data ) { parsedUpdateJSON = JSON.parse(data); diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 7c747121..3776e18b 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -21,6 +21,11 @@ $(document).ready( function() { $('#conf_cont_flatbuf').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fbs_heading_title"), 'editor_container_fbserver', 'btn_submit_fbserver')); $('#conf_cont_flatbuf').append(createHelpTable(schema.flatbufServer.properties, $.i18n("edt_conf_fbs_heading_title"))); + //protoserver + $('#conf_cont').append(createRow('conf_cont_proto')) + $('#conf_cont_proto').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_pbs_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver')); + $('#conf_cont_proto').append(createHelpTable(schema.protoServer.properties, $.i18n("edt_conf_pbs_heading_title"))); + //boblight $('#conf_cont').append(createRow('conf_cont_bobl')) $('#conf_cont_bobl').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_bobls_heading_title"), 'editor_container_boblightserver', 'btn_submit_boblightserver')); @@ -44,6 +49,7 @@ $(document).ready( function() { $('#conf_cont').addClass('row'); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver')); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fbs_heading_title"), 'editor_container_fbserver', 'btn_submit_fbserver')); + $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_pbs_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver')); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_bobls_heading_title"), 'editor_container_boblightserver', 'btn_submit_boblightserver')); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_udpl_heading_title"), 'editor_container_udplistener', 'btn_submit_udplistener')); if(storedAccess != 'default') @@ -76,6 +82,19 @@ $(document).ready( function() { requestWriteConfig(conf_editor_fbs.getValue()); }); + //protobuffer + conf_editor_proto = createJsonEditor('editor_container_protoserver', { + protoServer : schema.protoServer + }, true, true); + + conf_editor_proto.on('change',function() { + conf_editor_proto.validate().length ? $('#btn_submit_protoserver').attr('disabled', true) : $('#btn_submit_protoserver').attr('disabled', false); + }); + + $('#btn_submit_protoserver').off().on('click',function() { + requestWriteConfig(conf_editor_proto.getValue()); + }); + //boblight conf_editor_bobl = createJsonEditor('editor_container_boblightserver', { boblightServer : schema.boblightServer @@ -123,6 +142,7 @@ $(document).ready( function() { { createHint("intro", $.i18n('conf_network_json_intro'), "editor_container_jsonserver"); createHint("intro", $.i18n('conf_network_fbs_intro'), "editor_container_fbserver"); + createHint("intro", $.i18n('conf_network_proto_intro'), "editor_container_protoserver"); createHint("intro", $.i18n('conf_network_bobl_intro'), "editor_container_boblightserver"); createHint("intro", $.i18n('conf_network_udpl_intro'), "editor_container_udplistener"); createHint("intro", $.i18n('conf_network_forw_intro'), "editor_container_forwarder"); diff --git a/assets/webconfig/js/content_remote.js b/assets/webconfig/js/content_remote.js index eab3dca6..ab341632 100644 --- a/assets/webconfig/js/content_remote.js +++ b/assets/webconfig/js/content_remote.js @@ -156,9 +156,12 @@ $(document).ready(function() { case "FLATBUFSERVER": owner = $.i18n('general_comp_FLATBUFSERVER'); break; + case "PROTOSERVER": + owner = $.i18n('general_comp_PROTOSERVER'); + break; } - if(duration && compId != "GRABBER" && compId != "PROTOSERVER") + if(duration && compId != "GRABBER" && compId != "FLATBUFSERVER" && compId != "PROTOSERVER") owner += '
'+$.i18n('remote_input_duration')+' '+duration.toFixed(0)+$.i18n('edt_append_s')+''; var btn = ''; diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 422b7145..80b8d873 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -247,6 +247,15 @@ "timeout" : 5 }, + /// The configuration of the Protobuffer server which enables the Protobuffer remote interface + /// * port : Port at which the protobuffer server is started + "protoServer" : + { + "enable" : true, + "port" : 19445, + "timeout" : 5 + }, + /// The configuration of the boblight server which enables the boblight remote interface /// * enable : Enable or disable the boblight server (true/false) /// * port : Port at which the boblight server is started diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 866879e0..b8b9f394 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -140,6 +140,13 @@ "timeout" : 5 }, + "protoServer" : + { + "enable" : true, + "port" : 19445, + "timeout" : 5 + }, + "boblightServer" : { "enable" : false, diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 1ff24f50..66fcde99 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -9,6 +9,10 @@ if(ENABLE_WS281XPWM) external/rpi_ws281x/rpihw.c) endif() +#============================================================================= +# FLATBUFFER +#============================================================================= + set(USE_SYSTEM_FLATBUFFERS_LIBS ${DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS} CACHE BOOL "use flatbuffers library from system") if (USE_SYSTEM_FLATBUFFERS_LIBS) @@ -63,3 +67,111 @@ function(compile_flattbuffer_schema SRC_FBS OUTPUT_DIR) DEPENDS flatc) endif() endfunction() + +#============================================================================= +# PROTOBUFFER +#============================================================================= + +set(USE_SYSTEM_PROTO_LIBS ${DEFAULT_USE_SYSTEM_PROTO_LIBS} CACHE BOOL "use protobuf library from system") + +if (USE_SYSTEM_PROTO_LIBS) + find_package(Protobuf REQUIRED) + include_directories(${PROTOBUF_INCLUDE_DIRS}) +else () + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared protobuf library") + add_subdirectory(external/protobuf) + + if(CMAKE_CROSSCOMPILING) + # when crosscompiling import the protoc executable targets from a file generated by a native build + option(IMPORT_PROTOC "Protoc export file (protoc_export.cmake) from a native build" "IMPORT_PROTOC-FILE_NOT_FOUND") + include(${IMPORT_PROTOC}) + else() + # export the protoc compiler so it can be used when cross compiling + export(TARGETS protoc_compiler FILE "${CMAKE_BINARY_DIR}/protoc_export.cmake") + endif() + + # define the include for the protobuf library at the parent scope + set(PROTOBUF_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/protobuf/src") + set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS} PARENT_SCOPE) + + # define the protoc executable at the parent scope + get_property(PROTOBUF_PROTOC_EXECUTABLE TARGET protoc_compiler PROPERTY LOCATION) + set(PROTOBUF_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE} PARENT_SCOPE) +endif() + +message(STATUS "Using protobuf compiler: " ${PROTOBUF_PROTOC_EXECUTABLE}) + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# Copyright 2009-2011 Philip Lowman +# Copyright 2008 Esben Mose Hansen, Ange Optimization ApS +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) +function(PROTOBUF_GENERATE_CPP SRCS HDRS) + if(NOT ARGN) + message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files") + return() + endif() + + if(PROTOBUF_GENERATE_CPP_APPEND_PATH) + # Create an include path for each file specified + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(ABS_PATH ${ABS_FIL} PATH) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + else() + set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(DEFINED PROTOBUF_IMPORT_DIRS) + foreach(DIR ${PROTOBUF_IMPORT_DIRS}) + get_filename_component(ABS_PATH ${DIR} ABSOLUTE) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + endif() + + if(CMAKE_CROSSCOMPILING OR USE_SYSTEM_PROTO_LIBS) + set(PROTOC_DEPENDENCY ${PROTOBUF_PROTOC_EXECUTABLE}) + else() + set(PROTOC_DEPENDENCY protoc_compiler) + endif() + + set(${SRCS}) + set(${HDRS}) + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") + list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL} + DEPENDS ${ABS_FIL} ${PROTOC_DEPENDENCY} + COMMENT "Running C++ protocol buffer compiler on ${FIL}" + VERBATIM + ) + endforeach() + + set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} PARENT_SCOPE) +endfunction() diff --git a/dependencies/external/protobuf b/dependencies/external/protobuf new file mode 160000 index 00000000..adce8a99 --- /dev/null +++ b/dependencies/external/protobuf @@ -0,0 +1 @@ +Subproject commit adce8a99fdab90f290d659b6b3bf2d09b721e24a diff --git a/include/hyperion/CaptureCont.h b/include/hyperion/CaptureCont.h index a5b5059e..3c7e866c 100644 --- a/include/hyperion/CaptureCont.h +++ b/include/hyperion/CaptureCont.h @@ -41,13 +41,13 @@ private slots: /// @brief forward system image /// @param image The image /// - void handleSystemImage(const Image& image); + void handleSystemImage(const QString& name, const Image& image); /// /// @brief forward v4l image /// @param image The image /// - void handleV4lImage(const Image & image); + void handleV4lImage(const QString& name, const Image & image); /// /// @brief Is called from _v4lInactiveTimer to set source after specific time to inactive @@ -66,10 +66,12 @@ private: /// Reflect state of System capture and prio bool _systemCaptEnabled; quint8 _systemCaptPrio; + QString _systemCaptName; QTimer* _systemInactiveTimer; /// Reflect state of v4l capture and prio bool _v4lCaptEnabled; quint8 _v4lCaptPrio; + QString _v4lCaptName; QTimer* _v4lInactiveTimer; }; diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index 57c92808..7c3bb53b 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -54,7 +54,7 @@ public: int ret = grabber.grabFrame(_image); if (ret >= 0) { - emit systemImage(_image); + emit systemImage(_grabberName, _image); return true; } return false; @@ -92,7 +92,7 @@ signals: /// /// @brief Emit the final processed image /// - void systemImage(const Image& image); + void systemImage(const QString& name, const Image& image); protected: diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 7440020b..e72dde98 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -411,7 +411,7 @@ signals: void forwardJsonMessage(QJsonObject); /// Signal which is emitted, when a new proto image should be forwarded - void forwardProtoMessage(Image); + void forwardProtoMessage(const QString, const Image); /// /// @brief Is emitted from clients who request a videoMode change diff --git a/include/hyperion/MessageForwarder.h b/include/hyperion/MessageForwarder.h index 58b3254f..4a37fb76 100644 --- a/include/hyperion/MessageForwarder.h +++ b/include/hyperion/MessageForwarder.h @@ -70,7 +70,7 @@ private slots: /// @brief Forward image to all proto slaves /// @param image The PROTO image to send /// - void forwardProtoMessage(const Image &image); + void forwardProtoMessage(const QString& name, const Image &image); /// /// @brief Forward message to a single json slave diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h new file mode 100644 index 00000000..25cbd5a7 --- /dev/null +++ b/include/protoserver/ProtoServer.h @@ -0,0 +1,67 @@ +#pragma once + +// util +#include +#include + +// qt +#include + +class QTcpServer; +class ProtoClientConnection; + +/// +/// @brief This class creates a TCP server which accepts connections wich can then send +/// in Protocol Buffer encoded commands. This interface to Hyperion is used by various +/// third-party applications +/// +class ProtoServer : public QObject +{ + Q_OBJECT + +public: + ProtoServer(const QJsonDocument& config, QObject* parent = nullptr); + ~ProtoServer(); + +public slots: + /// + /// @brief Handle settings update + /// @param type The type from enum + /// @param config The configuration + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + void initServer(); + +private slots: + /// + /// @brief Is called whenever a new socket wants to connect + /// + void newConnection(); + + /// + /// @brief is called whenever a client disconnected + /// + void clientDisconnected(); + +private: + /// + /// @brief Start the server with current _port + /// + void startServer(); + + /// + /// @brief Stop server + /// + void stopServer(); + + +private: + QTcpServer* _server; + Logger* _log; + int _timeout; + quint16 _port; + const QJsonDocument _config; + + QVector _openConnections; +}; diff --git a/include/udplistener/UDPListener.h b/include/udplistener/UDPListener.h index 83b50599..167ccb7d 100644 --- a/include/udplistener/UDPListener.h +++ b/include/udplistener/UDPListener.h @@ -84,7 +84,6 @@ private slots: void processTheDatagram(const QByteArray * datagram, const QHostAddress * sender); private: - /// The UDP server object QUdpSocket * _server; diff --git a/include/utils/Components.h b/include/utils/Components.h index ba2350b6..2222a9f7 100644 --- a/include/utils/Components.h +++ b/include/utils/Components.h @@ -22,7 +22,8 @@ enum Components COMP_IMAGE, COMP_EFFECT, COMP_LEDDEVICE, - COMP_FLATBUFSERVER + COMP_FLATBUFSERVER, + COMP_PROTOSERVER }; inline const char* componentToString(Components c) @@ -41,7 +42,8 @@ inline const char* componentToString(Components c) case COMP_EFFECT: return "Effect"; case COMP_IMAGE: return "Image"; case COMP_LEDDEVICE: return "LED device"; - case COMP_FLATBUFSERVER: return "Image Receiver"; + case COMP_FLATBUFSERVER: return "Image Receiver"; + case COMP_PROTOSERVER: return "Proto Server"; default: return ""; } } @@ -63,6 +65,7 @@ inline const char* componentToIdString(Components c) case COMP_IMAGE: return "IMAGE"; case COMP_LEDDEVICE: return "LEDDEVICE"; case COMP_FLATBUFSERVER: return "FLATBUFSERVER"; + case COMP_PROTOSERVER: return "PROTOSERVER"; default: return ""; } } @@ -83,6 +86,7 @@ inline Components stringToComponent(QString component) if (component == "IMAGE") return COMP_IMAGE; if (component == "LEDDEVICE") return COMP_LEDDEVICE; if (component == "FLATBUFSERVER") return COMP_FLATBUFSERVER; + if (component == "PROTOSERVER") return COMP_PROTOSERVER; return COMP_INVALID; } diff --git a/include/utils/GlobalSignals.h b/include/utils/GlobalSignals.h index e3fd8efd..f97302c8 100644 --- a/include/utils/GlobalSignals.h +++ b/include/utils/GlobalSignals.h @@ -29,13 +29,15 @@ public: signals: /// /// @brief PIPE SystemCapture images from GrabberWrapper to Hyperion class + /// @param name The name of the platform capture that is currently active /// @param image The prepared image /// - void setSystemImage(const Image& image); + void setSystemImage(const QString& name, const Image& image); /// /// @brief PIPE v4lCapture images from v4lCapture over HyperionDaemon to Hyperion class + /// @param name The name of the v4l capture (path) that is currently active /// @param image The prepared image /// - void setV4lImage(const Image & image); + void setV4lImage(const QString& name, const Image & image); }; diff --git a/include/utils/settings.h b/include/utils/settings.h index 607a3719..ef96fa1d 100644 --- a/include/utils/settings.h +++ b/include/utils/settings.h @@ -29,6 +29,7 @@ enum type { INSTCAPTURE, NETWORK, FLATBUFSERVER, + PROTOSERVER, INVALID }; @@ -62,6 +63,7 @@ inline QString typeToString(const type& type) case INSTCAPTURE: return "instCapture"; case NETWORK: return "network"; case FLATBUFSERVER: return "flatbufServer"; + case PROTOSERVER: return "protoServer"; default: return "invalid"; } } @@ -73,7 +75,7 @@ inline QString typeToString(const type& type) /// inline type stringToType(const QString& type) { - if (type == "backgroundEffect") return BGEFFECT; + if (type == "backgroundEffect") return BGEFFECT; else if (type == "foregroundEffect") return FGEFFECT; else if (type == "blackborderdetector") return BLACKBORDER; else if (type == "boblightServer") return BOBLSERVER; @@ -94,6 +96,7 @@ inline type stringToType(const QString& type) else if (type == "instCapture") return INSTCAPTURE; else if (type == "network") return NETWORK; else if (type == "flatbufServer") return FLATBUFSERVER; - else return INVALID; + else if (type == "protoServer") return PROTOSERVER; + else return INVALID; } }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 4ff02b3b..f858b74d 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(commandline) add_subdirectory(blackborder) add_subdirectory(jsonserver) add_subdirectory(flatbufserver) +add_subdirectory(protoserver) add_subdirectory(bonjour) add_subdirectory(ssdp) add_subdirectory(boblightserver) diff --git a/libsrc/flatbufserver/FlatBufferClient.h b/libsrc/flatbufserver/FlatBufferClient.h index cc934b32..9fe6f36e 100644 --- a/libsrc/flatbufserver/FlatBufferClient.h +++ b/libsrc/flatbufserver/FlatBufferClient.h @@ -31,7 +31,7 @@ public: /// @param timeout The timeout when a client is automatically disconnected and the priority unregistered /// @param parent The parent /// - explicit FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent = nullptr); + explicit FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent = nullptr); signals: /// @@ -59,12 +59,12 @@ private slots: /// /// @brief Is called whenever the socket got new data to read /// - void readyRead(); + void readyRead(); /// /// @brief Is called when the socket closed the connection, also requests thread exit /// - void disconnected(); + void disconnected(); private: /// diff --git a/libsrc/flatbufserver/FlatBufferServer.cpp b/libsrc/flatbufserver/FlatBufferServer.cpp index ab87eb3e..0dc953fe 100644 --- a/libsrc/flatbufserver/FlatBufferServer.cpp +++ b/libsrc/flatbufserver/FlatBufferServer.cpp @@ -62,8 +62,6 @@ void FlatBufferServer::newConnection() FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this); // internal connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected); - // forward data - //connect(clientThread, &FlatBufferClient::); _openConnections.append(client); } } diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index 987b016b..320dccd9 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -53,7 +53,7 @@ void V4L2Wrapper::setSignalDetectionOffset(double verticalMin, double horizontal void V4L2Wrapper::newFrame(const Image &image) { - emit systemImage(image); + emit systemImage(_grabberName, image); } void V4L2Wrapper::readError(const char* err) diff --git a/libsrc/hyperion/CaptureCont.cpp b/libsrc/hyperion/CaptureCont.cpp index 12c5cd32..0eb75ccc 100644 --- a/libsrc/hyperion/CaptureCont.cpp +++ b/libsrc/hyperion/CaptureCont.cpp @@ -13,8 +13,10 @@ CaptureCont::CaptureCont(Hyperion* hyperion) : QObject() , _hyperion(hyperion) , _systemCaptEnabled(false) + , _systemCaptName() , _systemInactiveTimer(new QTimer(this)) , _v4lCaptEnabled(false) + , _v4lCaptName() , _v4lInactiveTimer(new QTimer(this)) { // settings changes @@ -41,14 +43,24 @@ CaptureCont::~CaptureCont() { } -void CaptureCont::handleV4lImage(const Image & image) +void CaptureCont::handleV4lImage(const QString& name, const Image & image) { + if(_v4lCaptName != name) + { + _hyperion->registerInput(_v4lCaptPrio, hyperion::COMP_V4L, "System", name); + _v4lCaptName = name; + } _v4lInactiveTimer->start(); _hyperion->setInputImage(_v4lCaptPrio, image); } -void CaptureCont::handleSystemImage(const Image& image) +void CaptureCont::handleSystemImage(const QString& name, const Image& image) { + if(_systemCaptName != name) + { + _hyperion->registerInput(_systemCaptPrio, hyperion::COMP_GRABBER, "System", name); + _systemCaptName = name; + } _systemInactiveTimer->start(); _hyperion->setInputImage(_systemCaptPrio, image); } diff --git a/libsrc/hyperion/MessageForwarder.cpp b/libsrc/hyperion/MessageForwarder.cpp index a8fc4594..ef1423f7 100644 --- a/libsrc/hyperion/MessageForwarder.cpp +++ b/libsrc/hyperion/MessageForwarder.cpp @@ -210,7 +210,7 @@ void MessageForwarder::forwardJsonMessage(const QJsonObject &message) } } -void MessageForwarder::forwardProtoMessage(const Image &image) +void MessageForwarder::forwardProtoMessage(const QString& name, const Image &image) { if (_forwarder_enabled) { diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 8450773f..ca911742 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -55,6 +55,10 @@ { "$ref": "schema-flatbufServer.json" }, + "protoServer" : + { + "$ref": "schema-protoServer.json" + }, "boblightServer" : { "$ref": "schema-boblightServer.json" diff --git a/libsrc/hyperion/resource.qrc b/libsrc/hyperion/resource.qrc index 2d8c1c36..575c2d48 100644 --- a/libsrc/hyperion/resource.qrc +++ b/libsrc/hyperion/resource.qrc @@ -15,6 +15,7 @@ schema/schema-forwarder.json schema/schema-jsonServer.json schema/schema-flatbufServer.json + schema/schema-protoServer.json schema/schema-boblightServer.json schema/schema-udpListener.json schema/schema-webConfig.json diff --git a/libsrc/hyperion/schema/schema-protoServer.json b/libsrc/hyperion/schema/schema-protoServer.json new file mode 100644 index 00000000..6a70e3f9 --- /dev/null +++ b/libsrc/hyperion/schema/schema-protoServer.json @@ -0,0 +1,36 @@ +{ + "type" : "object", + "required" : true, + "title" : "edt_conf_pbs_heading_title", + "properties" : + { + "enable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_general_enable_title", + "default" : true, + "propertyOrder" : 1 + }, + "port" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_port_title", + "minimum" : 1024, + "maximum" : 65535, + "default" : 19445 + }, + "timeout" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_pbs_timeout_title", + "append" : "edt_append_s", + "minimum" : 1, + "default" : 5, + "propertyOrder" : 3 + } + }, + "additionalProperties" : false +} diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt new file mode 100644 index 00000000..541d6045 --- /dev/null +++ b/libsrc/protoserver/CMakeLists.txt @@ -0,0 +1,44 @@ + +# Define the current source locations +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/protoserver) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/protoserver) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${PROTOBUF_INCLUDE_DIRS} +) + +set(ProtoServer_PROTOS ${CURRENT_SOURCE_DIR}/message.proto ) + +protobuf_generate_cpp(ProtoServer_PROTO_SRCS ProtoServer_PROTO_HDRS ${ProtoServer_PROTOS} ) + +### Split protoclient from protoserver as protoserver relates to HyperionDaemon and standalone capture binarys can't link to it + +add_library(protoclient + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.h + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp + ${ProtoServer_PROTO_SRCS} + ${ProtoServer_PROTO_HDRS} +) + +add_library(protoserver + ${CURRENT_HEADER_DIR}/ProtoServer.h + ${CURRENT_SOURCE_DIR}/ProtoServer.cpp +) + +# disable warnings for auto generated proto files, we can't change the files .... +SET_SOURCE_FILES_PROPERTIES ( ${ProtoServer_PROTO_SRCS} ${ProtoServer_PROTO_HDRS} ${ProtoServer_PROTOS} PROPERTIES COMPILE_FLAGS -w ) + +target_link_libraries(protoclient + hyperion + hyperion-utils + protobuf + Qt5::Gui +) + +target_link_libraries(protoserver + hyperion + hyperion-utils + protoclient + Qt5::Gui +) diff --git a/libsrc/protoserver/ProtoClientConnection.cpp b/libsrc/protoserver/ProtoClientConnection.cpp new file mode 100644 index 00000000..5f67a0be --- /dev/null +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -0,0 +1,238 @@ +// project includes +#include "ProtoClientConnection.h" + +// qt +#include +#include +#include +#include + +// Hyperion includes +#include + + + +ProtoClientConnection::ProtoClientConnection(QTcpSocket* socket, const int &timeout, QObject *parent) + : QObject(parent) + , _log(Logger::getInstance("PROTOSERVER")) + , _socket(socket) + , _clientAddress(socket->peerAddress().toString()) + , _timeoutTimer(new QTimer(this)) + , _timeout(timeout * 1000) + , _priority() + , _hyperion(Hyperion::getInstance()) +{ + // timer setup + _timeoutTimer->setSingleShot(true); + _timeoutTimer->setInterval(_timeout); + connect(_timeoutTimer, &QTimer::timeout, this, &ProtoClientConnection::forceClose); + + // connect socket signals + connect(_socket, &QTcpSocket::readyRead, this, &ProtoClientConnection::readyRead); + connect(_socket, &QTcpSocket::disconnected, this, &ProtoClientConnection::disconnected); +} + +void ProtoClientConnection::readyRead() +{ + _receiveBuffer += _socket->readAll(); + + // check if we can read a message size + if (_receiveBuffer.size() <= 4) + { + return; + } + + // read the message size + uint32_t messageSize = + ((_receiveBuffer[0]<<24) & 0xFF000000) | + ((_receiveBuffer[1]<<16) & 0x00FF0000) | + ((_receiveBuffer[2]<< 8) & 0x0000FF00) | + ((_receiveBuffer[3] ) & 0x000000FF); + + // check if we can read a complete message + if ((uint32_t) _receiveBuffer.size() < messageSize + 4) + { + return; + } + + // read a message + proto::HyperionRequest message; + if (!message.ParseFromArray(_receiveBuffer.data() + 4, messageSize)) + { + sendErrorReply("Unable to parse message"); + } + + // handle the message + handleMessage(message); + + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(messageSize + 4); +} + +void ProtoClientConnection::forceClose() +{ + _socket->close(); +} + +void ProtoClientConnection::disconnected() +{ + Debug(_log, "Socket Closed"); + _socket->deleteLater(); + _hyperion->clear(_priority); + emit clientDisconnected(); +} + +void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message) +{ + switch (message.command()) + { + case proto::HyperionRequest::COLOR: + if (!message.HasExtension(proto::ColorRequest::colorRequest)) + { + sendErrorReply("Received COLOR command without ColorRequest"); + break; + } + handleColorCommand(message.GetExtension(proto::ColorRequest::colorRequest)); + break; + case proto::HyperionRequest::IMAGE: + if (!message.HasExtension(proto::ImageRequest::imageRequest)) + { + sendErrorReply("Received IMAGE command without ImageRequest"); + break; + } + handleImageCommand(message.GetExtension(proto::ImageRequest::imageRequest)); + break; + case proto::HyperionRequest::CLEAR: + if (!message.HasExtension(proto::ClearRequest::clearRequest)) + { + sendErrorReply("Received CLEAR command without ClearRequest"); + break; + } + handleClearCommand(message.GetExtension(proto::ClearRequest::clearRequest)); + break; + case proto::HyperionRequest::CLEARALL: + handleClearallCommand(); + break; + default: + handleNotImplemented(); + } +} + +void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &message) +{ + // extract parameters + int priority = message.priority(); + int duration = message.has_duration() ? message.duration() : -1; + ColorRgb color; + color.red = qRed(message.rgbcolor()); + color.green = qGreen(message.rgbcolor()); + color.blue = qBlue(message.rgbcolor()); + + // make sure the prio is registered before setColor() + if(priority != _priority) + { + _hyperion->clear(_priority); + _hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress); + _priority = priority; + } + + // set output + _hyperion->setColor(_priority, color, duration); + + // send reply + sendSuccessReply(); +} + +void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &message) +{ + // extract parameters + int priority = message.priority(); + int duration = message.has_duration() ? message.duration() : -1; + int width = message.imagewidth(); + int height = message.imageheight(); + const std::string & imageData = message.imagedata(); + + // make sure the prio is registered before setInput() + if(priority != _priority) + { + _hyperion->clear(_priority); + _hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress); + _priority = priority; + } + + // check consistency of the size of the received data + if ((int) imageData.size() != width*height*3) + { + sendErrorReply("Size of image data does not match with the width and height"); + return; + } + + // create ImageRgb + Image image(width, height); + memcpy(image.memptr(), imageData.c_str(), imageData.size()); + + _hyperion->setInputImage(_priority, image, duration); + + // send reply + sendSuccessReply(); +} + + +void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &message) +{ + // extract parameters + int priority = message.priority(); + + // clear priority + _hyperion->clear(priority); + // send reply + sendSuccessReply(); +} + +void ProtoClientConnection::handleClearallCommand() +{ + // clear priority + _hyperion->clearall(); + + // send reply + sendSuccessReply(); +} + + +void ProtoClientConnection::handleNotImplemented() +{ + sendErrorReply("Command not implemented"); +} + +void ProtoClientConnection::sendMessage(const google::protobuf::Message &message) +{ + std::string serializedReply = message.SerializeAsString(); + uint32_t size = serializedReply.size(); + uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)}; + _socket->write((const char *) sizeData, sizeof(sizeData)); + _socket->write(serializedReply.data(), serializedReply.length()); + _socket->flush(); +} + +void ProtoClientConnection::sendSuccessReply() +{ + // create reply + proto::HyperionReply reply; + reply.set_type(proto::HyperionReply::REPLY); + reply.set_success(true); + + // send reply + sendMessage(reply); +} + +void ProtoClientConnection::sendErrorReply(const std::string &error) +{ + // create reply + proto::HyperionReply reply; + reply.set_type(proto::HyperionReply::REPLY); + reply.set_success(false); + reply.set_error(error); + + // send reply + sendMessage(reply); +} diff --git a/libsrc/protoserver/ProtoClientConnection.h b/libsrc/protoserver/ProtoClientConnection.h new file mode 100644 index 00000000..d57c2969 --- /dev/null +++ b/libsrc/protoserver/ProtoClientConnection.h @@ -0,0 +1,135 @@ +#pragma once + +// util +#include +#include +#include +#include + +// protobuffer PROTO +#include "message.pb.h" + +class QTcpSocket; +class QTimer; +class Hyperion; + +namespace proto { +class HyperionRequest; +} + +/// +/// The Connection object created by a ProtoServer when a new connection is established +/// +class ProtoClientConnection : public QObject +{ + Q_OBJECT + +public: + /// + /// @brief Construct the client + /// @param socket The socket + /// @param timeout The timeout when a client is automatically disconnected and the priority unregistered + /// @param parent The parent + /// + explicit ProtoClientConnection(QTcpSocket* socket, const int &timeout, QObject *parent); + +signals: + /// + /// @brief Emits whenever the client disconnected + /// + void clientDisconnected(); + +public slots: + /// + /// @brief close the socket and call disconnected() + /// + void forceClose(); + +private slots: + /// + /// @brief Is called whenever the socket got new data to read + /// + void readyRead(); + + /// + /// @brief Is called when the socket closed the connection, also requests thread exit + /// + void disconnected(); + +private: + /// + /// Handle an incoming Proto message + /// + /// @param message the incoming message as string + /// + void handleMessage(const proto::HyperionRequest &message); + + /// + /// Handle an incoming Proto Color message + /// + /// @param message the incoming message + /// + void handleColorCommand(const proto::ColorRequest & message); + + /// + /// Handle an incoming Proto Image message + /// + /// @param message the incoming message + /// + void handleImageCommand(const proto::ImageRequest & message); + + /// + /// Handle an incoming Proto Clear message + /// + /// @param message the incoming message + /// + void handleClearCommand(const proto::ClearRequest & message); + + /// + /// Handle an incoming Proto Clearall message + /// + void handleClearallCommand(); + + /// + /// Handle an incoming Proto message of unknown type + /// + void handleNotImplemented(); + + /// + /// Send a message to the connected client + /// + /// @param message The Proto message to send + /// + void sendMessage(const google::protobuf::Message &message); + + /// + /// Send a standard reply indicating success + /// + void sendSuccessReply(); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// + void sendErrorReply(const std::string & error); + +private: + Logger*_log; + + /// The TCP-Socket that is connected tot the Proto-client + QTcpSocket* _socket; + + /// address of client + const QString _clientAddress; + + QTimer*_timeoutTimer; + int _timeout; + int _priority; + + /// Link to Hyperion for writing led-values to a priority channel + Hyperion* _hyperion; + + /// The buffer used for reading data from the socket + QByteArray _receiveBuffer; +}; diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp new file mode 100644 index 00000000..6c2d8152 --- /dev/null +++ b/libsrc/protoserver/ProtoServer.cpp @@ -0,0 +1,104 @@ +#include +#include "ProtoClientConnection.h" + +// qt +#include +#include +#include + +ProtoServer::ProtoServer(const QJsonDocument& config, QObject* parent) + : QObject(parent) + , _server(new QTcpServer(this)) + , _log(Logger::getInstance("PROTOSERVER")) + , _timeout(5000) + , _config(config) +{ + +} + +ProtoServer::~ProtoServer() +{ + stopServer(); + delete _server; +} + +void ProtoServer::initServer() +{ + connect(_server, &QTcpServer::newConnection, this, &ProtoServer::newConnection); + + // apply config + handleSettingsUpdate(settings::PROTOSERVER, _config); +} + +void ProtoServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::PROTOSERVER) + { + const QJsonObject& obj = config.object(); + + quint16 port = obj["port"].toInt(19445); + + // port check + if(_server->serverPort() != port) + { + stopServer(); + _port = port; + } + + // new timeout just for new connections + _timeout = obj["timeout"].toInt(5000); + // enable check + obj["enable"].toBool(true) ? startServer() : stopServer(); + } +} + +void ProtoServer::newConnection() +{ + while(_server->hasPendingConnections()) + { + if(QTcpSocket * socket = _server->nextPendingConnection()) + { + Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString())); + ProtoClientConnection * client = new ProtoClientConnection(socket, _timeout, this); + // internal + connect(client, &ProtoClientConnection::clientDisconnected, this, &ProtoServer::clientDisconnected); + _openConnections.append(client); + } + } +} + +void ProtoServer::clientDisconnected() +{ + ProtoClientConnection* client = qobject_cast(sender()); + client->deleteLater(); + _openConnections.removeAll(client); +} + +void ProtoServer::startServer() +{ + if(!_server->isListening()) + { + if(!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Failed to bind port %d", _port); + } + else + { + Info(_log,"Started on port %d", _port); + } + } +} + +void ProtoServer::stopServer() +{ + if(_server->isListening()) + { + // close client connections + for(const auto& client : _openConnections) + { + client->forceClose(); + } + _server->close(); + Info(_log, "Stopped"); + } +} diff --git a/libsrc/protoserver/message.proto b/libsrc/protoserver/message.proto new file mode 100644 index 00000000..9652453e --- /dev/null +++ b/libsrc/protoserver/message.proto @@ -0,0 +1,80 @@ +package proto; + +message HyperionRequest { + enum Command { + COLOR = 1; + IMAGE = 2; + CLEAR = 3; + CLEARALL = 4; + } + + // command specification + required Command command = 1; + + // extensions to define all specific requests + extensions 10 to 100; +} + +message ColorRequest { + extend HyperionRequest { + optional ColorRequest colorRequest = 10; + } + + // priority to use when setting the color + required int32 priority = 1; + + // integer value containing the rgb color (0x00RRGGBB) + required int32 RgbColor = 2; + + // duration of the request (negative results in infinite) + optional int32 duration = 3; +} + +message ImageRequest { + extend HyperionRequest { + optional ImageRequest imageRequest = 11; + } + + // priority to use when setting the image + required int32 priority = 1; + + // width of the image + required int32 imagewidth = 2; + + // height of the image + required int32 imageheight = 3; + + // image data + required bytes imagedata = 4; + + // duration of the request (negative results in infinite) + optional int32 duration = 5; +} + +message ClearRequest { + extend HyperionRequest { + optional ClearRequest clearRequest = 12; + } + + // priority which need to be cleared + required int32 priority = 1; +} + +message HyperionReply { + enum Type { + REPLY = 1; + VIDEO = 2; + } + + // Identifies which field is filled in. + required Type type = 1; + + // flag indication success or failure + optional bool success = 2; + + // string indicating the reason for failure (if applicable) + optional string error = 3; + + // Proto Messages for video mode + optional int32 video = 4; +} diff --git a/libsrc/udplistener/UDPListener.cpp b/libsrc/udplistener/UDPListener.cpp index 60c4e3cb..678735f4 100644 --- a/libsrc/udplistener/UDPListener.cpp +++ b/libsrc/udplistener/UDPListener.cpp @@ -5,6 +5,7 @@ #include // hyperion includes +#include #include "HyperionConfig.h" // qt includes @@ -22,6 +23,9 @@ UDPListener::UDPListener(const QJsonDocument& config) : _isActive(false), _listenPort(0) { + // listen for component change + connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &UDPListener::componentStateChanged); + // init handleSettingsUpdate(settings::UDPLISTENER, config); } @@ -70,6 +74,8 @@ void UDPListener::start() _serviceRegister->registerService("_hyperiond-udp._udp", _listenPort); } } + + Hyperion::getInstance()->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); } void UDPListener::stop() @@ -80,7 +86,7 @@ void UDPListener::stop() _server->close(); _isActive = false; Info(_log, "Stopped"); -// emit clearGlobalPriority(_priority, hyperion::COMP_UDPLISTENER); + Hyperion::getInstance()->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); } void UDPListener::componentStateChanged(const hyperion::Components component, bool enable) diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index bcce3312..ebaf09b0 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -17,6 +17,7 @@ target_link_libraries(hyperiond jsonserver udplistener flatbufserver + protoserver webserver bonjour ssdp diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 7cbf64ab..769d17d5 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -26,9 +26,12 @@ #include // Required to determine the cmake options #include "hyperiond.h" -// FlatBufferServer +// Flatbuffer Server #include +// Protobuffer Server +#include + // bonjour browser #include @@ -145,6 +148,8 @@ void HyperionDaemon::freeObjects() delete _jsonServer; _flatBufferServer->thread()->quit(); _flatBufferServer->thread()->wait(1000); + _protoServer->thread()->quit(); + _protoServer->thread()->wait(1000); //ssdp before webserver _ssdp->thread()->quit(); _ssdp->thread()->wait(1000); @@ -166,17 +171,18 @@ void HyperionDaemon::freeObjects() _v4l2Grabbers.clear(); _bonjourBrowserWrapper = nullptr; - _amlGrabber = nullptr; - _dispmanx = nullptr; - _fbGrabber = nullptr; - _osxGrabber = nullptr; - _qtGrabber = nullptr; - _flatBufferServer = nullptr; - _ssdp = nullptr; - _webserver = nullptr; - _jsonServer = nullptr; - _udpListener = nullptr; - _stats = nullptr; + _amlGrabber = nullptr; + _dispmanx = nullptr; + _fbGrabber = nullptr; + _osxGrabber = nullptr; + _qtGrabber = nullptr; + _flatBufferServer = nullptr; + _protoServer = nullptr; + _ssdp = nullptr; + _webserver = nullptr; + _jsonServer = nullptr; + _udpListener = nullptr; + _stats = nullptr; } void HyperionDaemon::startNetworkServices() @@ -198,6 +204,16 @@ void HyperionDaemon::startNetworkServices() connect(this, &HyperionDaemon::settingsChanged, _flatBufferServer, &FlatBufferServer::handleSettingsUpdate); fbThread->start(); + // Create Proto server in thread + _protoServer = new ProtoServer(getSetting(settings::PROTOSERVER)); + QThread* pThread = new QThread(this); + _protoServer->moveToThread(pThread); + connect( pThread, &QThread::started, _protoServer, &ProtoServer::initServer ); + connect( pThread, &QThread::finished, _protoServer, &QObject::deleteLater ); + connect( pThread, &QThread::finished, pThread, &QObject::deleteLater ); + connect(this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate); + pThread->start(); + // Create UDP listener _udpListener = new UDPListener(getSetting(settings::UDPLISTENER)); connect(this, &HyperionDaemon::settingsChanged, _udpListener, &UDPListener::handleSettingsUpdate); diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 3fb18916..1aaee749 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -63,6 +63,7 @@ class SettingsManager; class PythonInit; class SSDPHandler; class FlatBufferServer; +class ProtoServer; class HyperionDaemon : public QObject { @@ -130,33 +131,34 @@ private: void createGrabberX11(const QJsonObject & grabberConfig); void createGrabberQt(const QJsonObject & grabberConfig); - Logger* _log; - BonjourBrowserWrapper* _bonjourBrowserWrapper; - PythonInit* _pyInit; - WebServer* _webserver; - JsonServer* _jsonServer; - UDPListener* _udpListener; + Logger* _log; + BonjourBrowserWrapper* _bonjourBrowserWrapper; + PythonInit* _pyInit; + WebServer* _webserver; + JsonServer* _jsonServer; + UDPListener* _udpListener; std::vector _v4l2Grabbers; - DispmanxWrapper* _dispmanx; - X11Wrapper* _x11Grabber; - AmlogicWrapper* _amlGrabber; - FramebufferWrapper* _fbGrabber; - OsxWrapper* _osxGrabber; - QtWrapper* _qtGrabber; - Hyperion* _hyperion; - Stats* _stats; - SSDPHandler* _ssdp; - FlatBufferServer* _flatBufferServer; + DispmanxWrapper* _dispmanx; + X11Wrapper* _x11Grabber; + AmlogicWrapper* _amlGrabber; + FramebufferWrapper* _fbGrabber; + OsxWrapper* _osxGrabber; + QtWrapper* _qtGrabber; + Hyperion* _hyperion; + Stats* _stats; + SSDPHandler* _ssdp; + FlatBufferServer* _flatBufferServer; + ProtoServer* _protoServer; - unsigned _grabber_width; - unsigned _grabber_height; - unsigned _grabber_frequency; - unsigned _grabber_cropLeft; - unsigned _grabber_cropRight; - unsigned _grabber_cropTop; - unsigned _grabber_cropBottom; - int _grabber_ge2d_mode; - QString _grabber_device; + unsigned _grabber_width; + unsigned _grabber_height; + unsigned _grabber_frequency; + unsigned _grabber_cropLeft; + unsigned _grabber_cropRight; + unsigned _grabber_cropTop; + unsigned _grabber_cropBottom; + int _grabber_ge2d_mode; + QString _grabber_device; QString _prevType;