From d762aa2f3e370b857f501158261ce2a1ba6a5b3f Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Thu, 27 Dec 2018 23:11:32 +0100 Subject: [PATCH] Details coming soon. --- assets/webconfig/i18n/de.json | 21 +- assets/webconfig/i18n/en.json | 11 +- assets/webconfig/index.html | 3 + assets/webconfig/js/content_dashboard.js | 47 +- assets/webconfig/js/content_effects.js | 5 +- .../js/content_effectsconfigurator.js | 5 +- assets/webconfig/js/content_grabber.js | 49 +- assets/webconfig/js/content_index.js | 95 +- assets/webconfig/js/content_network.js | 28 +- assets/webconfig/js/content_remote.js | 64 +- assets/webconfig/js/hyperion.js | 22 +- .../webconfig/js/lib/bootstrap-notify.min.js | 2 + assets/webconfig/js/ui_utils.js | 178 ++- config/hyperion.config.json.commented | 31 +- config/hyperion.config.json.default | 21 +- include/blackborder/BlackBorderProcessor.h | 71 +- include/boblightserver/BoblightServer.h | 33 +- include/bonjour/bonjourbrowserwrapper.h | 68 + include/bonjour/bonjourserviceregister.h | 3 +- include/effectengine/Effect.h | 89 ++ include/effectengine/EffectEngine.h | 19 +- .../effectengine/EffectModule.h | 79 +- include/grabber/AmlogicWrapper.h | 3 +- include/grabber/DispmanxFrameGrabber.h | 13 +- include/grabber/DispmanxWrapper.h | 6 +- include/grabber/FramebufferFrameGrabber.h | 11 +- include/grabber/FramebufferWrapper.h | 2 +- include/grabber/OsxFrameGrabber.h | 13 +- include/grabber/OsxWrapper.h | 3 +- include/grabber/V4L2Grabber.h | 51 +- include/grabber/V4L2Wrapper.h | 14 +- include/grabber/X11Grabber.h | 36 +- include/grabber/X11Wrapper.h | 3 +- include/hyperion/BGEffectHandler.h | 71 + include/hyperion/CaptureCont.h | 62 + include/hyperion/ComponentRegister.h | 48 +- include/hyperion/Grabber.h | 67 +- include/hyperion/GrabberWrapper.h | 68 +- include/hyperion/Hyperion.h | 350 +++-- include/hyperion/ImageProcessor.h | 81 +- include/hyperion/ImageProcessorFactory.h | 53 - include/hyperion/ImageToLedsMap.h | 26 +- include/hyperion/MessageForwarder.h | 38 +- .../hyperion/MultiColorAdjustment.h | 2 +- include/hyperion/PriorityMuxer.h | 174 ++- include/hyperion/SettingsManager.h | 72 + include/jsonserver/JsonServer.h | 38 +- include/leddevice/LedDevice.h | 23 +- include/leddevice/LedDeviceFactory.h | 3 +- include/protoserver/ProtoServer.h | 44 +- include/python/PythonInit.h | 13 + include/python/PythonUtils.h | 15 + include/udplistener/UDPListener.h | 37 +- include/utils/Components.h | 9 + include/utils/FileUtils.h | 8 +- include/utils/Image.h | 32 + include/utils/JsonProcessor.h | 280 ---- include/utils/JsonUtils.h | 32 +- include/utils/hyperion.h | 320 +++++ include/utils/settings.h | 99 ++ include/webconfig/WebConfig.h | 34 - include/webserver/WebServer.h | 55 + libsrc/CMakeLists.txt | 4 +- .../JSONRPC_schema/schema-adjustment.json | 11 - .../JSONRPC_schema/schema-clear.json | 2 +- .../JSONRPC_schema/schema-color.json | 0 .../JSONRPC_schema/schema-componentstate.json | 0 .../JSONRPC_schema/schema-config.json | 0 .../JSONRPC_schema/schema-create-effect.json | 0 .../JSONRPC_schema/schema-delete-effect.json | 0 .../JSONRPC_schema/schema-effect.json | 0 .../JSONRPC_schema/schema-image.json | 0 .../JSONRPC_schema/schema-ledcolors.json | 0 .../JSONRPC_schema/schema-logging.json | 0 .../JSONRPC_schema/schema-processing.json | 0 .../JSONRPC_schema/schema-serverinfo.json | 3 + .../JSONRPC_schema/schema-sourceselect.json | 0 .../JSONRPC_schema/schema-sysinfo.json | 0 .../JSONRPC_schema/schema-videomode.json | 0 .../{utils => api}/JSONRPC_schema/schema.json | 0 libsrc/{utils => api}/JSONRPC_schemas.qrc | 1 - libsrc/blackborder/BlackBorderDetector.cpp | 2 +- libsrc/blackborder/BlackBorderProcessor.cpp | 97 +- libsrc/blackborder/CMakeLists.txt | 5 +- .../BoblightClientConnection.cpp | 18 +- .../boblightserver/BoblightClientConnection.h | 7 +- libsrc/boblightserver/BoblightServer.cpp | 78 +- libsrc/bonjour/bonjourbrowserwrapper.cpp | 81 ++ libsrc/bonjour/bonjourserviceregister.cpp | 38 +- libsrc/effectengine/CMakeLists.txt | 1 + libsrc/effectengine/Effect.cpp | 1100 +-------------- libsrc/effectengine/EffectEngine.cpp | 61 +- libsrc/effectengine/EffectModule.cpp | 1077 +++++++++++++++ libsrc/grabber/amlogic/AmlogicWrapper.cpp | 4 +- .../grabber/dispmanx/DispmanxFrameGrabber.cpp | 78 +- libsrc/grabber/dispmanx/DispmanxWrapper.cpp | 4 +- .../framebuffer/FramebufferFrameGrabber.cpp | 64 +- .../framebuffer/FramebufferWrapper.cpp | 4 +- libsrc/grabber/osx/OsxFrameGrabber.cpp | 67 +- libsrc/grabber/osx/OsxWrapper.cpp | 4 +- libsrc/grabber/v4l2/V4L2Grabber.cpp | 133 +- libsrc/grabber/v4l2/V4L2Wrapper.cpp | 49 +- libsrc/grabber/x11/X11Grabber.cpp | 100 +- libsrc/grabber/x11/X11Wrapper.cpp | 6 +- libsrc/hyperion/CaptureCont.cpp | 106 ++ libsrc/hyperion/ComponentRegister.cpp | 67 +- libsrc/hyperion/Grabber.cpp | 19 +- libsrc/hyperion/GrabberWrapper.cpp | 162 +-- libsrc/hyperion/Hyperion.cpp | 940 +++++-------- libsrc/hyperion/ImageProcessor.cpp | 99 +- libsrc/hyperion/ImageProcessorFactory.cpp | 25 - libsrc/hyperion/LinearColorSmoothing.cpp | 100 +- libsrc/hyperion/LinearColorSmoothing.h | 72 +- libsrc/hyperion/MessageForwarder.cpp | 98 +- libsrc/hyperion/MultiColorAdjustment.cpp | 33 +- libsrc/hyperion/PriorityMuxer.cpp | 274 +++- libsrc/hyperion/SettingsManager.cpp | 175 +++ libsrc/hyperion/hyperion.schema.json | 4 + libsrc/hyperion/resource.qrc | 1 + libsrc/hyperion/schema/schema-color.json | 16 - .../hyperion/schema/schema-framegrabber.json | 46 +- .../hyperion/schema/schema-grabberV4L2.json | 58 +- .../hyperion/schema/schema-instCapture.json | 45 + libsrc/jsonserver/CMakeLists.txt | 1 + libsrc/jsonserver/JsonClientConnection.cpp | 15 +- libsrc/jsonserver/JsonClientConnection.h | 7 +- libsrc/jsonserver/JsonServer.cpp | 95 +- libsrc/leddevice/LedDevice.cpp | 10 +- libsrc/leddevice/LedDeviceFactory.cpp | 16 +- libsrc/leddevice/dev_net/ProviderUdp.cpp | 8 +- libsrc/leddevice/dev_net/ProviderUdp.h | 7 +- libsrc/protoserver/CMakeLists.txt | 28 +- libsrc/protoserver/ProtoClientConnection.cpp | 47 +- libsrc/protoserver/ProtoClientConnection.h | 7 - libsrc/protoserver/ProtoServer.cpp | 113 +- libsrc/python/CMakeLists.txt | 21 + libsrc/python/PythonInit.cpp | 31 + libsrc/udplistener/UDPListener.cpp | 70 +- libsrc/utils/CMakeLists.txt | 3 - libsrc/utils/FileUtils.cpp | 19 +- .../utils/JSONRPC_schema/schema-clearall.json | 15 - libsrc/utils/JsonProcessor.cpp | 1196 ----------------- libsrc/utils/JsonUtils.cpp | 37 +- libsrc/utils/Process.cpp | 10 +- libsrc/utils/RgbChannelAdjustment.cpp | 2 +- libsrc/utils/RgbTransform.cpp | 4 +- libsrc/utils/Stats.cpp | 14 +- libsrc/webconfig/WebConfig.cpp | 67 - .../{webconfig => webserver}/CMakeLists.txt | 9 +- .../{webconfig => webserver}/CgiHandler.cpp | 9 +- libsrc/{webconfig => webserver}/CgiHandler.h | 11 +- .../QtHttpClientWrapper.cpp | 3 +- .../QtHttpClientWrapper.h | 0 .../{webconfig => webserver}/QtHttpHeader.cpp | 0 .../{webconfig => webserver}/QtHttpHeader.h | 0 .../{webconfig => webserver}/QtHttpReply.cpp | 0 libsrc/{webconfig => webserver}/QtHttpReply.h | 0 .../QtHttpRequest.cpp | 0 .../{webconfig => webserver}/QtHttpRequest.h | 0 .../{webconfig => webserver}/QtHttpServer.cpp | 53 +- .../{webconfig => webserver}/QtHttpServer.h | 0 .../StaticFileServing.cpp | 52 +- .../StaticFileServing.h | 11 +- .../{webconfig => webserver}/WebConfig.qrc.in | 0 .../{webconfig => webserver}/WebJsonRpc.cpp | 10 +- libsrc/{webconfig => webserver}/WebJsonRpc.h | 4 +- libsrc/webserver/WebServer.cpp | 99 ++ .../WebSocketClient.cpp | 11 +- .../WebSocketClient.h | 4 +- .../{webconfig => webserver}/WebSocketUtils.h | 0 src/CMakeLists.txt | 1 - src/hyperion-aml/CMakeLists.txt | 2 +- src/hyperion-dispmanx/CMakeLists.txt | 2 +- src/hyperion-framebuffer/CMakeLists.txt | 2 +- src/hyperion-osx/CMakeLists.txt | 2 +- src/hyperion-remote/JsonConnection.cpp | 3 +- src/hyperion-v4l2/CMakeLists.txt | 2 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 7 - src/hyperion-x11/CMakeLists.txt | 2 +- src/hyperion-x11/X11Wrapper.cpp | 6 +- src/hyperion-x11/X11Wrapper.h | 2 +- src/hyperion-x11/hyperion-x11.cpp | 5 +- src/hyperiond/CMakeLists.txt | 7 +- src/hyperiond/hyperiond.cpp | 632 ++++----- src/hyperiond/hyperiond.h | 107 +- src/hyperiond/main.cpp | 7 +- 186 files changed, 6156 insertions(+), 5444 deletions(-) create mode 100644 assets/webconfig/js/lib/bootstrap-notify.min.js create mode 100644 include/bonjour/bonjourbrowserwrapper.h create mode 100644 include/effectengine/Effect.h rename libsrc/effectengine/Effect.h => include/effectengine/EffectModule.h (55%) create mode 100644 include/hyperion/BGEffectHandler.h create mode 100644 include/hyperion/CaptureCont.h delete mode 100644 include/hyperion/ImageProcessorFactory.h rename {libsrc => include}/hyperion/MultiColorAdjustment.h (98%) create mode 100644 include/hyperion/SettingsManager.h create mode 100644 include/python/PythonInit.h create mode 100644 include/python/PythonUtils.h delete mode 100644 include/utils/JsonProcessor.h create mode 100644 include/utils/hyperion.h create mode 100644 include/utils/settings.h delete mode 100644 include/webconfig/WebConfig.h create mode 100644 include/webserver/WebServer.h rename libsrc/{utils => api}/JSONRPC_schema/schema-adjustment.json (92%) rename libsrc/{utils => api}/JSONRPC_schema/schema-clear.json (94%) rename libsrc/{utils => api}/JSONRPC_schema/schema-color.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-componentstate.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-config.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-create-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-delete-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-image.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-ledcolors.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-logging.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-processing.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-serverinfo.json (83%) rename libsrc/{utils => api}/JSONRPC_schema/schema-sourceselect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-sysinfo.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-videomode.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema.json (100%) rename libsrc/{utils => api}/JSONRPC_schemas.qrc (94%) create mode 100644 libsrc/bonjour/bonjourbrowserwrapper.cpp create mode 100644 libsrc/effectengine/EffectModule.cpp create mode 100644 libsrc/hyperion/CaptureCont.cpp delete mode 100644 libsrc/hyperion/ImageProcessorFactory.cpp create mode 100644 libsrc/hyperion/SettingsManager.cpp create mode 100644 libsrc/hyperion/schema/schema-instCapture.json create mode 100644 libsrc/python/CMakeLists.txt create mode 100644 libsrc/python/PythonInit.cpp delete mode 100644 libsrc/utils/JSONRPC_schema/schema-clearall.json delete mode 100644 libsrc/utils/JsonProcessor.cpp delete mode 100644 libsrc/webconfig/WebConfig.cpp rename libsrc/{webconfig => webserver}/CMakeLists.txt (78%) rename libsrc/{webconfig => webserver}/CgiHandler.cpp (94%) rename libsrc/{webconfig => webserver}/CgiHandler.h (83%) rename libsrc/{webconfig => webserver}/QtHttpClientWrapper.cpp (99%) rename libsrc/{webconfig => webserver}/QtHttpClientWrapper.h (100%) rename libsrc/{webconfig => webserver}/QtHttpHeader.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpHeader.h (100%) rename libsrc/{webconfig => webserver}/QtHttpReply.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpReply.h (100%) rename libsrc/{webconfig => webserver}/QtHttpRequest.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpRequest.h (100%) rename libsrc/{webconfig => webserver}/QtHttpServer.cpp (66%) rename libsrc/{webconfig => webserver}/QtHttpServer.h (100%) rename libsrc/{webconfig => webserver}/StaticFileServing.cpp (63%) rename libsrc/{webconfig => webserver}/StaticFileServing.h (69%) rename libsrc/{webconfig => webserver}/WebConfig.qrc.in (100%) rename libsrc/{webconfig => webserver}/WebJsonRpc.cpp (74%) rename libsrc/{webconfig => webserver}/WebJsonRpc.h (89%) create mode 100644 libsrc/webserver/WebServer.cpp rename libsrc/{webconfig => webserver}/WebSocketClient.cpp (96%) rename libsrc/{webconfig => webserver}/WebSocketClient.h (97%) rename libsrc/{webconfig => webserver}/WebSocketUtils.h (100%) diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index 45b65f4b..469a5e21 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -416,6 +416,7 @@ "edt_conf_enum_PAL" : "PAL", "edt_conf_enum_NTSC" : "NTSC", "edt_conf_enum_SECAM" : "SECAM", + "edt_conf_enum_NO_CHANGE" : "Auto", "edt_conf_enum_logsilent" : "Stille", "edt_conf_enum_logwarn" : "Warnung", "edt_conf_enum_logverbose" : "Ausführlich", @@ -486,14 +487,8 @@ "edt_conf_v4l2_input_expl" : "Der Eingang des Pfades.", "edt_conf_v4l2_standard_title" : "Videoformat", "edt_conf_v4l2_standard_expl" : "Wähle das passende Videoformat deiner Region.", - "edt_conf_v4l2_width_title" : "Breite", - "edt_conf_v4l2_width_expl" : "Die Breite des Bildes. (-1 = Automatische Breitenbestimmung)", - "edt_conf_v4l2_height_title" : "Höhe", - "edt_conf_v4l2_height_expl" : "Die Höhes des Bildes. (-1 = Automatische Höhenbestimmung)", - "edt_conf_v4l2_frameDecimation_title" : "Bildverkleinerung", - "edt_conf_v4l2_frameDecimation_expl" : "Der Faktor der Bildverkleinerung", - "edt_conf_v4l2_sizeDecimation_title" : "Größenänderung", - "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Größenänderung", + "edt_conf_v4l2_sizeDecimation_title" : "Bildverkleinerung Faktor", + "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Bildverkleinerung ausgehend der von der ursprünglichen Größe, 1 bedeutet keine Änderung (originales Bild).", "edt_conf_v4l2_cropLeft_title" : "Entferne links", "edt_conf_v4l2_cropLeft_expl" : "Anzahl der Pixel auf der linken Seite die vom Bild entfernt werden.", "edt_conf_v4l2_cropRight_title" : "Entferne rechts", @@ -503,7 +498,7 @@ "edt_conf_v4l2_cropBottom_title" : "Entferne unten", "edt_conf_v4l2_cropBottom_expl" : "Anzahl der Pixel auf der unteren Seite die vom Bild entfernt werden.", "edt_conf_v4l2_signalDetection_title" : "Signal Erkennung", - "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten. Das Bild muss dazu 4 Sekunden lang unter die Schwellwerte fallen.", + "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten.", "edt_conf_v4l2_redSignalThreshold_title" : "Rote Signalschwelle", "edt_conf_v4l2_redSignalThreshold_expl" : "Je höher die rote Schwelle je eher wird abgeschalten bei entsprechendem rot-Anteil.", "edt_conf_v4l2_greenSignalThreshold_title" : "Grüne Signalschwelle", @@ -523,16 +518,12 @@ "edt_conf_fg_type_expl" : "Art der Plattform Aufnahme, standard ist 'auto'", "edt_conf_fg_frequency_Hz_title" : "Aufnahmefrequenz", "edt_conf_fg_frequency_Hz_expl" : "Wie schnell neue Bilder aufgenommen werden.", - "edt_conf_fg_horizontalPixelDecimation_title" : "Horizontale Pixelreduzierung", - "edt_conf_fg_horizontalPixelDecimation_expl" : "Horizontale Pixelreduzierung (Faktor)", - "edt_conf_fg_useXGetImage_title" : "Nutze XGetImage", - "edt_conf_fg_useXGetImage_expl" : "XGetImage für aktuelle X11 desktops", "edt_conf_fg_width_title" : "Breite", "edt_conf_fg_width_expl" : "Verkleinere Bild auf dieser Breite, da das Rohmaterial viel Leistung benötigen würde.", "edt_conf_fg_height_title" : "Höhe", "edt_conf_fg_height_expl" : "Verkleinere Bild auf dieser Höhe, da das Rohmaterial viel Leistung benötigen würde.", - "edt_conf_fg_verticalPixelDecimation_title" : "Vertikale Pixelreduzierung", - "edt_conf_fg_verticalPixelDecimation_expl" : "Vertikale Pixelreduzierung (Faktor)", + "edt_conf_fg_pixelDecimation_title" : "Bildverkleinerung Faktor", + "edt_conf_fg_pixelDecimation_expl" : "Bildverkleinerung (Faktor) ausgehend von der original Größe. 1 für unveränderte/originale Größe.", "edt_conf_fg_device_title" : "Device", "edt_conf_fg_display_title" : "Display", "edt_conf_fg_display_expl" : "Gebe an von welchem Desktop aufgenommen werden soll. (Multi Monitor Setup)", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 5431fd04..5c2ae968 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -417,6 +417,7 @@ "edt_conf_enum_PAL" : "PAL", "edt_conf_enum_NTSC" : "NTSC", "edt_conf_enum_SECAM" : "SECAM", + "edt_conf_enum_NO_CHANGE" : "Auto", "edt_conf_enum_logsilent" : "Silent", "edt_conf_enum_logwarn" : "Warning", "edt_conf_enum_logverbose" : "Verbose", @@ -504,7 +505,7 @@ "edt_conf_v4l2_cropBottom_title" : "Crop bottom", "edt_conf_v4l2_cropBottom_expl" : "Count of pixels on the bottom side that are removed from the picture.", "edt_conf_v4l2_signalDetection_title" : "Signal detection", - "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found. This will happen when the picture fall below the threshold value for a period of 4 seconds.", + "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found.", "edt_conf_v4l2_redSignalThreshold_title" : "Red signal threshold", "edt_conf_v4l2_redSignalThreshold_expl" : "Darkens low red values (recognized as black)", "edt_conf_v4l2_greenSignalThreshold_title" : "Green signal threshold", @@ -524,16 +525,12 @@ "edt_conf_fg_type_expl" : "Type of platform capture, default is 'auto'", "edt_conf_fg_frequency_Hz_title" : "Capture frequency", "edt_conf_fg_frequency_Hz_expl" : "How fast new pictures are captured", - "edt_conf_fg_horizontalPixelDecimation_title" : "Horizontal pixel decimation", - "edt_conf_fg_horizontalPixelDecimation_expl" : "Horizontal pixel decimation (factor)", "edt_conf_fg_width_title" : "Width", "edt_conf_fg_width_expl" : "Shrink picture to this width, as raw picture needs a lot of cpu time.", "edt_conf_fg_height_title" : "Height", "edt_conf_fg_height_expl" : "Shrink picture to this height, as raw picture needs a lot of cpu time.", - "edt_conf_fg_useXGetImage_title" : "Use XGetImage", - "edt_conf_fg_useXGetImage_expl" : "XGetImage for newer X11 desktops", - "edt_conf_fg_verticalPixelDecimation_title" : "Vertical pixel decimation", - "edt_conf_fg_verticalPixelDecimation_expl" : "Vertical pixel decimation (factor)", + "edt_conf_fg_pixelDecimation_title" : "Picture decimation", + "edt_conf_fg_pixelDecimation_expl" : "Reduce picture size (factor) based on original size. A factor of 1 means no change", "edt_conf_fg_device_title" : "Device", "edt_conf_fg_display_title" : "Display", "edt_conf_fg_display_expl" : "Select which desktop should be captured (multi monitor setup)", diff --git a/assets/webconfig/index.html b/assets/webconfig/index.html index f222ff77..c2f61a19 100644 --- a/assets/webconfig/index.html +++ b/assets/webconfig/index.html @@ -31,6 +31,9 @@ + + + diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index ca0cae1e..de61dffd 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -1,6 +1,6 @@ $(document).ready( function() { performTranslation(); - + function newsCont(t,e,l) { var h = '
'; @@ -10,7 +10,7 @@ $(document).ready( function() { h += '

'; $('#dash_news').append(h); } - + function createNews(d) { for(var i = 0; i'; @@ -45,30 +45,31 @@ $(document).ready( function() { $('#dash_news').html(h); }); } - + //getNews(); - + function updateComponents() { - var components = serverInfo.components; + var components = comps; components_html = ""; for ( idx=0; idx'; + if(components[idx].name != "ALL") + components_html += ''+$.i18n('general_comp_'+components[idx].name)+''; } $("#tab_components").html(components_html); - + //info - $('#dash_statush').html(serverInfo.hyperion.off? ''+$.i18n('general_btn_off')+'':''+$.i18n('general_btn_on')+''); - $('#btn_hsc').html(serverInfo.hyperion.off? '' : ''); + $('#dash_statush').html(serverInfo.hyperion.enabled ? ''+$.i18n('general_btn_on')+'' : ''+$.i18n('general_btn_off')+''); + $('#btn_hsc').html(serverInfo.hyperion.enabled ? '' : ''); } - + // add more info $('#dash_leddevice').html(serverInfo.ledDevices.active); $('#dash_currv').html(currentVersion); $('#dash_instance').html(serverConfig.general.name); $('#dash_ports').html(jsonPort+' | '+serverConfig.protoServer.port); - + $.get( "https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/version.json", function( data ) { parsedUpdateJSON = JSON.parse(data); latestVersion = parsedUpdateJSON[0].versionnr; @@ -76,13 +77,13 @@ $(document).ready( function() { var cleanCurrentVersion = currentVersion.replace(/\./g, ''); // $('#dash_latev').html(latestVersion); - + // if ( cleanCurrentVersion < cleanLatestVersion ) // $('#versioninforesult').html('
'+$.i18n('dashboard_infobox_message_updatewarning', latestVersion)+'
'); // else $('#versioninforesult').html('
'+$.i18n('dashboard_infobox_message_updatesuccess')+'
'); }); - + //determine platform var grabbers = serverInfo.grabbers.available; var html = ""; @@ -97,16 +98,16 @@ $(document).ready( function() { html += 'Amlogic'; else html += 'Framebuffer'; - - $('#dash_platform').html(html); - - + + $('#dash_platform').html(html); + + //interval update updateComponents(); - $(hyperion).on("cmd-serverinfo",updateComponents); - + $(hyperion).on("components-updated",updateComponents); + if(showOptHelp) createHintH("intro", $.i18n('dashboard_label_intro'), "dash_intro"); - + removeOverlay(); -}); \ No newline at end of file +}); diff --git a/assets/webconfig/js/content_effects.js b/assets/webconfig/js/content_effects.js index 1c054ddf..75053262 100644 --- a/assets/webconfig/js/content_effects.js +++ b/assets/webconfig/js/content_effects.js @@ -121,7 +121,10 @@ $(document).ready( function() { } //interval update - $(hyperion).on("cmd-serverinfo",updateEffectlist); + $(hyperion).on("cmd-effects-update", function(event){ + serverInfo.effects = event.response.data.effects + updateEffectlist(); + }); removeOverlay(); }); diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index e339331e..25adc2ce 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -171,7 +171,10 @@ $(document).ready( function() { updateDelEffectlist(); //interval update - $(hyperion).on("cmd-serverinfo",updateDelEffectlist); + $(hyperion).on("cmd-effects-update", function(event){ + serverInfo.effects = event.response.data.effects + updateDelEffectlist(); + }); removeOverlay(); }); diff --git a/assets/webconfig/js/content_grabber.js b/assets/webconfig/js/content_grabber.js index 6f8f4cae..eedd1d89 100644 --- a/assets/webconfig/js/content_grabber.js +++ b/assets/webconfig/js/content_grabber.js @@ -2,7 +2,8 @@ $(document).ready( function() { performTranslation(); var conf_editor_v4l2 = null; var conf_editor_fg = null; - + var conf_editor_instCapt = null; + function hideEl(el) { for(var i = 0; i -1) - hideEl(["device","verticalPixelDecimation","horizontalPixelDecimation","useXGetImage"]); + hideEl(["device","pixelDecimation"]); else if(grabbers.indexOf('x11') > -1) hideEl(["device","width","height"]); else if(grabbers.indexOf('osx') > -1 ) - hideEl(["device","verticalPixelDecimation","horizontalPixelDecimation","useXGetImage"]); + hideEl(["device","pixelDecimation"]); else if(grabbers.indexOf('amlogic') > -1) - hideEl(["verticalPixelDecimation","horizontalPixelDecimation","useXGetImage"]); + hideEl(["pixelDecimation"]); }); - + removeOverlay(); }); - diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index a45ad775..bdec03c3 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -1,6 +1,5 @@ $(document).ready( function() { var uiLock = false; - var prevSess = 0; loadContentTo("#container_connection_lost","connection_lost"); loadContentTo("#container_restart","restart"); @@ -8,17 +7,28 @@ $(document).ready( function() { $(hyperion).on("cmd-serverinfo",function(event){ serverInfo = event.response.info; + // protect components from serverinfo updates + if(!compsInited) + { + comps = event.response.info.components + compsInited = true; + } + + if(!priosInited) + { + priosInited = true; + } $(hyperion).trigger("ready"); - + if (serverInfo.hyperion.config_modified) $("#hyperion_reload_notify").fadeIn("fast"); else $("#hyperion_reload_notify").fadeOut("fast"); - if (serverInfo.hyperion.off) - $("#hyperion_disabled_notify").fadeIn("fast"); - else + if (serverInfo.hyperion.enabled) $("#hyperion_disabled_notify").fadeOut("fast"); + else + $("#hyperion_disabled_notify").fadeIn("fast"); if (!serverInfo.hyperion.config_writeable) { @@ -33,45 +43,32 @@ $(document).ready( function() { uiLock = false; } - var sess = serverInfo.hyperion.sessions; - if (sess.length != prevSess) - { - wSess = []; - prevSess = sess.length; - for(var i = 0; i 1) - $('#btn_instanceswitch').toggle(true); - else - $('#btn_instanceswitch').toggle(false); - } - + updateSessions(); }); // end cmd-serverinfo + $(hyperion).on("cmd-sessions-update", function(event) { + serverInfo.sessions = event.response.data; + updateSessions(); + }); + $(hyperion).one("cmd-sysinfo", function(event) { requestServerInfo(); sysInfo = event.response.info; currentVersion = sysInfo.hyperion.version; }); - + $(hyperion).one("cmd-config-getschema", function(event) { - serverSchema = event.response.result; + serverSchema = event.response.info; requestServerConfig(); - + schema = serverSchema.properties; }); $(hyperion).one("cmd-config-getconfig", function(event) { - serverConfig = event.response.result; + serverConfig = event.response.info; requestSysInfo(); - + showOptHelp = serverConfig.general.showOptHelp; }); @@ -82,15 +79,48 @@ $(document).ready( function() { $(hyperion).on("open",function(event){ requestServerConfigSchema(); }); - + $(hyperion).one("ready", function(event) { loadContent(); }); - + + $(hyperion).on("cmd-adjustment-update", function(event) { + serverInfo.adjustment = event.response.data + }); + + $(hyperion).on("cmd-videomode-update", function(event) { + serverInfo.videomode = event.response.data.videomode + }); + + $(hyperion).on("cmd-components-update", function(event) { + let obj = event.response.data + + // notfication in index + if (obj.name == "ALL") + { + if(obj.enable) + $("#hyperion_disabled_notify").fadeOut("fast"); + else + $("#hyperion_disabled_notify").fadeIn("fast"); + } + + comps.forEach((entry, index) => { + if (entry.name === obj.name){ + comps[index] = obj; + } + }); + // notify the update + $(hyperion).trigger("components-updated"); + }); + + $(hyperion).on("cmd-effects-update", function(event){ + serverInfo.effects = event.response.data.effects + }); + $("#btn_hyperion_reload").on("click", function(){ initRestart(); }); - + $(".mnava").bind('click.menu', function(e){ loadContent(e); window.scrollTo(0, 0); @@ -105,4 +135,3 @@ $(function(){ $(this).toggleClass('active inactive'); }); }); - diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 94f1fa30..5c176e2f 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -1,24 +1,25 @@ $(document).ready( function() { performTranslation(); - + + var conf_editor_net = null; var conf_editor_json = null; var conf_editor_proto = null; var conf_editor_bobl = null; var conf_editor_udpl = null; var conf_editor_forw = null; - + if(showOptHelp) { //jsonserver $('#conf_cont').append(createRow('conf_cont_json')) $('#conf_cont_json').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver')); $('#conf_cont_json').append(createHelpTable(schema.jsonServer.properties, $.i18n("edt_conf_js_heading_title"))); - + //protoserver $('#conf_cont').append(createRow('conf_cont_proto')) $('#conf_cont_proto').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_ps_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver')); $('#conf_cont_proto').append(createHelpTable(schema.protoServer.properties, $.i18n("edt_conf_ps_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,7 +45,7 @@ $(document).ready( function() { $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_ps_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') + if(storedAccess != 'default') $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fw_heading_title"), 'editor_container_forwarder', 'btn_submit_forwarder')); } @@ -69,11 +70,11 @@ $(document).ready( function() { 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 @@ -82,11 +83,11 @@ $(document).ready( function() { conf_editor_bobl.on('change',function() { conf_editor_bobl.validate().length ? $('#btn_submit_boblightserver').attr('disabled', true) : $('#btn_submit_boblightserver').attr('disabled', false); }); - + $('#btn_submit_boblightserver').off().on('click',function() { requestWriteConfig(conf_editor_bobl.getValue()); }); - + //udplistener conf_editor_udpl = createJsonEditor('editor_container_udplistener', { udpListener : schema.udpListener @@ -95,11 +96,11 @@ $(document).ready( function() { conf_editor_udpl.on('change',function() { conf_editor_udpl.validate().length ? $('#btn_submit_udplistener').attr('disabled', true) : $('#btn_submit_udplistener').attr('disabled', false); }); - + $('#btn_submit_udplistener').off().on('click',function() { requestWriteConfig(conf_editor_udpl.getValue()); }); - + if(storedAccess != 'default') { //forwarder @@ -115,7 +116,7 @@ $(document).ready( function() { requestWriteConfig(conf_editor_forw.getValue()); }); } - + //create introduction if(showOptHelp) { @@ -125,7 +126,6 @@ $(document).ready( function() { createHint("intro", $.i18n('conf_network_udpl_intro'), "editor_container_udplistener"); createHint("intro", $.i18n('conf_network_forw_intro'), "editor_container_forwarder"); } - + removeOverlay(); }); - diff --git a/assets/webconfig/js/content_remote.js b/assets/webconfig/js/content_remote.js index 32d36c99..e997dbf7 100644 --- a/assets/webconfig/js/content_remote.js +++ b/assets/webconfig/js/content_remote.js @@ -86,22 +86,6 @@ $(document).ready(function() { requestSetColor(rgb.r, rgb.g, rgb.b,duration); } - function updateRemote() - { - if ($('#componentsbutton').length == 0) - { - $(hyperion).off("cmd-serverinfo",updateRemote); - } - else - { - updateInputSelect(); - updateLedMapping(); - updateVideoMode(); - updateComponents(); - updateEffectlist(); - } - } - function updateInputSelect() { $('.sstbody').html(""); @@ -123,6 +107,7 @@ $(document).ready(function() { var priority = prios[i].priority; var compId = prios[i].componentId; var duration = prios[i].duration_ms/1000; + var value = "0,0,0"; var btn_type = "default"; var btn_text = $.i18n('remote_input_setsource_btn'); var btn_state = "enabled"; @@ -145,13 +130,16 @@ $(document).ready(function() { if(ip) origin += '
'+$.i18n('remote_input_ip')+' '+ip+''; + if("value" in prios[i]) + value = prios[i].value.RGB; + switch (compId) { case "EFFECT": owner = $.i18n('remote_effects_label_effects')+' '+owner; break; case "COLOR": - owner = $.i18n('remote_color_label_color')+' '+'
'; + owner = $.i18n('remote_color_label_color')+' '+'
'; break; case "GRABBER": owner = $.i18n('general_comp_GRABBER')+': ('+owner+')'; @@ -165,6 +153,9 @@ $(document).ready(function() { case "UDPLISTENER": owner = $.i18n('general_comp_UDPLISTENER'); break; + case "PROTOSERVER": + owner = "Proto"; + break; } if(duration && compId != "GRABBER" && compId != "PROTOSERVER") @@ -195,7 +186,7 @@ $(document).ready(function() { function updateLedMapping() { - mapping = serverInfo.ledMAppingType; + mapping = serverInfo.imageToLedMappingType; $('#mappingsbutton').html(""); for(var ix = 0; ix < mappingList.length; ix++) @@ -211,16 +202,19 @@ $(document).ready(function() { function updateComponents() { - components = serverInfo.components; + components = comps; // create buttons $('#componentsbutton').html(""); for ( idx=0; idx {1} {2}
'};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); \ No newline at end of file diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index fd20c904..4f1c24dc 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -7,7 +7,7 @@ function removeOverlay() function reload() { - location.reload(); + location.reload(); } function storageComp() @@ -48,6 +48,27 @@ function debugMessage(msg) } } +function updateSessions() +{ + var sess = serverInfo.sessions; + if (sess.length) + { + wSess = []; + for(var i = 0; i 1) + $('#btn_instanceswitch').toggle(true); + else + $('#btn_instanceswitch').toggle(false); + } +} + function validateDuration(d) { if(typeof d === "undefined" || d < 0) @@ -73,9 +94,9 @@ function getHashtag() function loadContent(event) { var tag; - + if(typeof event != "undefined") - { + { tag = event.currentTarget.hash; tag = tag.substr(tag.indexOf("#") + 1); setStorage('lasthashtag', tag, true); @@ -130,7 +151,7 @@ function setClassByBool(obj,enable,class1,class2) } function showInfoDialog(type,header,message) -{ +{ if (type=="success"){ $('#id_body').html(''); if(header == "") @@ -143,12 +164,12 @@ function showInfoDialog(type,header,message) $('#id_body').append('

'+$.i18n('infoDialog_general_warning_title')+'

'); $('#id_footer').html(''); } - else if (type=="error"){ + else if (type=="error"){ $('#id_body').html(''); if(header == "") $('#id_body').append('

'+$.i18n('infoDialog_general_error_title')+'

'); $('#id_footer').html(''); - } + } else if (type == "select"){ $('#id_body').html('Redefine ambient light!'); $('#id_footer').html(''); @@ -178,10 +199,10 @@ function showInfoDialog(type,header,message) $('#id_body').append('

'+header+'

'); $('#id_body').append(message); - + if(type == "select" || type == "iswitch") $('#id_body').append(''); - + $("#modal_dialog").modal({ backdrop : "static", keyboard: false, @@ -193,14 +214,14 @@ function createHintH(type, text, container) { if(type = "intro") tclass = "introd"; - + $('#'+container).prepend('

'+text+'


'); } function createHint(type, text, container, buttonid, buttontxt) { var fe, tclass; - + if(type == "intro") { fe = ''; @@ -212,21 +233,21 @@ function createHint(type, text, container, buttonid, buttontxt) tclass = "info-hint"; } else if(type == "wizard") - { + { fe = '
Information
'; tclass = "wizard-hint"; } else if(type == "warning") - { + { fe = '
Information
'; tclass = "warning-hint"; } - + if(buttonid) buttonid = '

'; else buttonid = ""; - + if(type == "intro") $('#'+container).prepend('

'+$.i18n("conf_helptable_expl")+'

'+text+'
'); else if(type == "wizard") @@ -247,8 +268,8 @@ function valValue(id,value,min,max) { if(typeof max === 'undefined' || max == "") max = 999999; - - if(Number(value) > Number(max)) + + if(Number(value) > Number(max)) { $('#'+id).val(max); showInfoDialog("warning","",$.i18n('edt_msg_error_maximum_incl',max)); @@ -260,7 +281,7 @@ function valValue(id,value,min,max) showInfoDialog("warning","",$.i18n('edt_msg_error_minimum_incl',min)); return min; } - return value; + return value; } function readImg(input,cb) @@ -294,12 +315,12 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) { $('#'+container).off(); $('#'+container).html(""); - + //JSONEditor.plugins.selectize.enable = true; - + if (typeof arrayre === 'undefined') arrayre = true; - + var editor = new JSONEditor(document.getElementById(container), { theme: 'bootstrap3', @@ -338,18 +359,18 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) } function buildWL(link,linkt,cl) -{ +{ var baseLink = "https://docs.hyperion-project.org/"; var lang; - + if(typeof linkt == "undefined") linkt = "Placeholder"; - + if(storedLang == "de" || navigator.locale == "de") lang = "de"; else lang = "en"; - + if(cl === true) { linkt = $.i18n(linkt); @@ -366,7 +387,7 @@ function rgbToHex(rgb) return "#" + ("0" + parseInt(rgb[0],10).toString(16)).slice(-2) + ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) + - ("0" + parseInt(rgb[2],10).toString(16)).slice(-2); + ("0" + parseInt(rgb[2],10).toString(16)).slice(-2); } else debugMessage('rgbToHex: Given rgb is no array or has wrong length'); @@ -381,6 +402,57 @@ function hexToRgb(hex) { } : null; } +/* + Show a notification + @param type Valid types are "info","success","warning","danger" + @param message The message to show + @param title A title (optional) + */ +function showNotification(type, message, title="") +{ + if(title == "") + { + switch(type) + { + case "info": + title = $.i18n('infoDialog_general_info_title'); + break; + case "success": + title = $.i18n('infoDialog_general_success_title'); + break; + case "warning": + title = $.i18n('infoDialog_general_warning_title'); + break; + case "danger": + title = $.i18n('infoDialog_general_error_title'); + break; + } + } + + $.notify({ + // options + title: title, + message: message + },{ + // settings + type: type, + animate: { + enter: 'animated fadeInRight', + exit: 'animated fadeOutRight' + }, + mouse_over : 'pause', + template: '' + }); +} function createCP(id, color, cb) { @@ -388,7 +460,7 @@ function createCP(id, color, cb) color = rgbToHex(color); else if(color == "undefined") color = "#AA3399"; - + if(color.startsWith("#")) { $('#'+id).colorpicker({ @@ -425,7 +497,7 @@ function createTable(hid, bid, cont, bless, tclass) var table = document.createElement('table'); var thead = document.createElement('thead'); var tbody = document.createElement('tbody'); - + table.className = "table"; if(bless === true) table.className += " borderless"; @@ -438,30 +510,30 @@ function createTable(hid, bid, cont, bless, tclass) if(hid != "") table.appendChild(thead); table.appendChild(tbody); - + $('#'+cont).append(table); } // Creates a table row // @param array list :innerHTML content for / // @param bool head :if null or false it's body -// @param bool align :if null or false no alignment +// @param bool align :if null or false no alignment // // @return : with or as child(s) function createTableRow(list, head, align) { var row = document.createElement('tr'); - + for(var i = 0; i < list.length; i++) { if(head === true) var el = document.createElement('th'); else var el = document.createElement('td'); - + if(align) el.style.verticalAlign = "middle"; - + el.innerHTML = list[i]; row.appendChild(el); } @@ -483,7 +555,7 @@ function createOptPanel(phicon, phead, bodyid, footerid) pfooter.className = "btn btn-primary"; pfooter.setAttribute("id", footerid); pfooter.innerHTML = ''+$.i18n('general_button_savesettings'); - + return createPanel(phead, "", pfooter, "panel-default", bodyid); } @@ -506,30 +578,30 @@ function createHelpTable(list, phead){ var thead = document.createElement('thead'); var tbody = document.createElement('tbody'); list = sortProperties(list); - + phead = ''+phead+' '+$.i18n("conf_helptable_expl"); - + table.className = 'table table-hover borderless'; - + thead.appendChild(createTableRow([$.i18n('conf_helptable_option'), $.i18n('conf_helptable_expl')], true, false)); - + for (key in list) { if(list[key].access != 'system') { var text = list[key].title.replace('title', 'expl'); tbody.appendChild(createTableRow([$.i18n(list[key].title), $.i18n(text)], false, false)); - + if(list[key].items && list[key].items.properties) { var ilist = sortProperties(list[key].items.properties); for (ikey in ilist) { - + var itext = ilist[ikey].title.replace('title', 'expl'); tbody.appendChild(createTableRow([$.i18n(ilist[ikey].title), $.i18n(itext)], false, false)); } - } + } } } table.appendChild(thead); @@ -544,42 +616,42 @@ function createPanel(head, body, footer, type, bodyid){ var phead = document.createElement('div'); var pbody = document.createElement('div'); var pfooter = document.createElement('div'); - + cont.className = "col-lg-6"; - + if(typeof type == 'undefined') type = 'panel-default'; - + p.className = 'panel '+type; phead.className = 'panel-heading'; pbody.className = 'panel-body'; pfooter.className = 'panel-footer'; - + phead.innerHTML = head; - + if(typeof bodyid != 'undefined') { pfooter.style.textAlign = 'right'; pbody.setAttribute("id", bodyid) } - + if(typeof body != 'undefined' && body != "") pbody.appendChild(body); - + if(typeof footer != 'undefined') pfooter.appendChild(footer); - + p.appendChild(phead); p.appendChild(pbody); - + if(typeof footer != 'undefined') { pfooter.style.textAlign = "right"; p.appendChild(pfooter); } - + cont.appendChild(p); - + return cont; } @@ -589,12 +661,12 @@ function createSelGroup(group) el.setAttribute('label', group); return el; } - + function createSelOpt(opt, title) { var el = document.createElement('option'); el.setAttribute('value', opt); - if (typeof title == 'undefined') + if (typeof title == 'undefined') el.innerHTML = opt; else el.innerHTML = title; diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 10f1a8ce..30375404 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -43,7 +43,7 @@ /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') /// * 'leds' : The indices (or index ranges) of the leds to which this channel adjustment applies /// (eg '0-5, 9, 11, 12-17'). The indices are zero based. - /// * 'black'/'white'/'red'/'green'/'blue'/'cyan'/'magenta'/'yellow' : Array of RGB to adjust the output color + /// * 'white'/'red'/'green'/'blue'/'cyan'/'magenta'/'yellow' : Array of RGB to adjust the output color /// * 'gammaRed'/'gammaGreen'/'gammaBlue' : Gamma value for each channel /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') @@ -60,7 +60,6 @@ { "id" : "default", "leds" : "*", - "black" : [0,0,0], "white" : [255,255,255], "red" : [255,0,0], "green" : [0,255,0], @@ -99,15 +98,10 @@ }, /// Configuration for the embedded V4L2 grabber - /// * enable : Enable or disable the v4lgrabber (true/false) /// * device : V4L2 Device to use [default="/dev/video0"] /// * input : V4L2 input to use [default=0] - /// * standard : Video standard (PAL/NTSC/SECAM) [default="PAL"] - /// * width : V4L2 width to set [default=-1] - /// * height : V4L2 height to set [default=-1] - /// * frameDecimation : Frame decimation factor [default=2] + /// * standard : Video standard (PAL/NTSC/SECAM/NO_CHANGE) [default="NO_CHANGE"] /// * sizeDecimation : Size decimation factor [default=8] - /// * priority : Hyperion priority channel [default=900] /// * cropLeft : Cropping from the left [default=0] /// * cropRight : Cropping from the right [default=0] /// * cropTop : Cropping from the top [default=0] @@ -123,13 +117,9 @@ "grabberV4L2" : [ { - "enable" : false, "device" : "auto", "input" : 0, - "standard" : "PAL", - "width" : 0, - "height" : 0, - "frameDecimation" : 2, + "standard" : "NO_CHANGE", "sizeDecimation" : 8, "priority" : 240, "cropLeft" : 0, @@ -148,20 +138,16 @@ ], /// The configuration for the frame-grabber, contains the following items: - /// * enable : true if the framegrabber (platform grabber) should be activated /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|framebuffer) [auto] /// * width : The width of the grabbed frames [pixels] /// * height : The height of the grabbed frames [pixels] /// * frequency_Hz : The frequency of the frame grab [Hz] - /// * priority : The priority of the frame-gabber (Default=250) HINT: lower value result in HIGHER priority! /// * ATTENTION : Power-of-Two resolution is not supported and leads to unexpected behaviour! "framegrabber" : { // for all type of grabbers - "enable" : true, "type" : "framebuffer", "frequency_Hz" : 10, - "priority" : 250, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -172,9 +158,7 @@ "height" : 96, // valid for x11 - "useXGetImage" : false, - "horizontalPixelDecimation" : 8, - "verticalPixelDecimation" : 8, + "pixelDecimation" : 8, // valid for framebuffer "device" : "/dev/fb0" @@ -313,6 +297,13 @@ ] }, + "instCapture" : { + "systemEnable" : true, + "systemPriority" : 250, + "v4lEnable" : false, + "v4lPriority" : 240 + }, + /// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion. "ledConfig" : { diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index e5385f55..33f27ece 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -26,7 +26,6 @@ { "id" : "default", "leds" : "*", - "black" : [0,0,0], "white" : [255,255,255], "red" : [255,0,0], "green" : [0,255,0], @@ -58,15 +57,10 @@ "grabberV4L2" : [ { - "enable" : false, "device" : "auto", "input" : 0, - "standard" : "PAL", - "width" : 0, - "height" : 0, - "frameDecimation" : 2, + "standard" : "NO_CHANGE", "sizeDecimation" : 8, - "priority" : 240, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -84,15 +78,11 @@ "framegrabber" : { - "enable" : true, "type" : "auto", "width" : 80, "height" : 45, "frequency_Hz" : 10, - "priority" : 250, - "useXGetImage" : false, - "horizontalPixelDecimation" : 8, - "verticalPixelDecimation" : 8, + "pixelDecimation" : 8, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -175,6 +165,13 @@ "disable": [""] }, + "instCapture" : { + "systemEnable" : true, + "systemPriority" : 250, + "v4lEnable" : false, + "v4lPriority" : 240 + }, + "ledConfig" : { "top" : 8, diff --git a/include/blackborder/BlackBorderProcessor.h b/include/blackborder/BlackBorderProcessor.h index d5f1b9de..0b625f08 100644 --- a/include/blackborder/BlackBorderProcessor.h +++ b/include/blackborder/BlackBorderProcessor.h @@ -4,30 +4,28 @@ // QT includes #include +// util +#include +#include +#include + // Local Hyperion includes #include "BlackBorderDetector.h" +class Hyperion; + namespace hyperion { /// /// The BlackBorder processor is a wrapper around the black-border detector for keeping track of /// detected borders and count of the type and size of detected borders. /// - class BlackBorderProcessor + class BlackBorderProcessor : public QObject { + Q_OBJECT public: - /// - /// Constructor for the BlackBorderProcessor - /// @param unknownFrameCnt The number of frames(images) that need to contain an unknown - /// border before the current border is set to unknown - /// @param borderFrameCnt The number of frames(images) that need to contain a vertical or - /// horizontal border becomes the current border - /// @param blurRemoveCnt The size to add to a horizontal or vertical border (because the - /// outer pixels is blurred (black and color combined due to image scaling)) - /// @param[in] blackborderThreshold The threshold which the blackborder detector should use - /// - BlackBorderProcessor(const QJsonObject &blackborderConfig); - + BlackBorderProcessor(Hyperion* hyperion, QObject* parent); + ~BlackBorderProcessor(); /// /// Return the current (detected) border /// @return The current border @@ -46,6 +44,13 @@ namespace hyperion /// void setEnabled(bool enable); + /// + /// Sets the _hardDisabled state, if True prevents the enable from COMP_BLACKBORDER state emit (mimiks wrong state to external!) + /// It's not possible to enable bb from this method, if the user requsted a disable! + /// @param disable The new state + /// + void setHardDisable(const bool& disable); + /// /// Processes the image. This performs detecion of black-border on the given image and /// updates the current border accordingly. If the current border is updated the method call @@ -70,11 +75,11 @@ namespace hyperion } if (_detectionMode == "default") { - imageBorder = _detector.process(image); + imageBorder = _detector->process(image); } else if (_detectionMode == "classic") { - imageBorder = _detector.process_classic(image); + imageBorder = _detector->process_classic(image); } else if (_detectionMode == "osd") { - imageBorder = _detector.process_osd(image); + imageBorder = _detector->process_osd(image); } // add blur to the border if (imageBorder.horizontalSize > 0) @@ -89,8 +94,23 @@ namespace hyperion const bool borderUpdated = updateBorder(imageBorder); return borderUpdated; } + private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief Handle component state changes, it's not possible for BB to be enabled, when a hardDisable is active + /// + void componentStateChanged(const hyperion::Components component, bool enable); private: + /// Hyperion instance + Hyperion* _hyperion; + /// /// Updates the current border based on the newly detected border. Returns true if the /// current border has changed. @@ -102,24 +122,24 @@ namespace hyperion /// flag for blackborder detector usage bool _enabled; - + /// The number of unknown-borders detected before it becomes the current border - const unsigned _unknownSwitchCnt; + unsigned _unknownSwitchCnt; /// The number of horizontal/vertical borders detected before it becomes the current border - const unsigned _borderSwitchCnt; + unsigned _borderSwitchCnt; // The number of frames that are "ignored" before a new border gets set as _previousDetectedBorder - const unsigned _maxInconsistentCnt; + unsigned _maxInconsistentCnt; /// The number of pixels to increase a detected border for removing blury pixels unsigned _blurRemoveCnt; /// The border detection mode - const QString _detectionMode; + QString _detectionMode; /// The blackborder detector - BlackBorderDetector _detector; + BlackBorderDetector* _detector; /// The current detected border BlackBorder _currentBorder; @@ -131,5 +151,12 @@ namespace hyperion unsigned _consistentCnt; /// The number of frame the previous detected border NOT matched the incomming border unsigned _inconsistentCnt; + /// old threshold + double _oldThreshold; + /// True when disabled in specific situations, this prevents to enable BB when the visible priority requested a disable + bool _hardDisabled; + /// Reflect the last component state request from user (comp change) + bool _userEnabled; + }; } // end namespace hyperion diff --git a/include/boblightserver/BoblightServer.h b/include/boblightserver/BoblightServer.h index 42ed587c..3b711816 100644 --- a/include/boblightserver/BoblightServer.h +++ b/include/boblightserver/BoblightServer.h @@ -4,15 +4,19 @@ #include // Qt includes -#include #include +#include // Hyperion includes -#include #include #include +// settings +#include + class BoblightClientConnection; +class Hyperion; +class QTcpServer; /// /// This class creates a TCP server which accepts connections from boblight clients. @@ -27,25 +31,24 @@ public: /// @param hyperion Hyperion instance /// @param port port number on which to start listening for connections /// - BoblightServer(const int priority, uint16_t port = 19333); + BoblightServer(const QJsonDocument& config); ~BoblightServer(); /// /// @return the port number on which this TCP listens for incoming connections /// uint16_t getPort() const; - + /// @return true if server is active (bind to a port) /// - bool active() { return _isActive; }; - bool componentState() { return active(); }; + bool active(); public slots: /// /// bind server to network /// void start(); - + /// /// close server /// @@ -53,8 +56,12 @@ public slots: void componentStateChanged(const hyperion::Components component, bool enable); -signals: - void statusChanged(bool isActive); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// @@ -73,19 +80,17 @@ private: Hyperion * _hyperion; /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// List with open connections QSet _openConnections; /// hyperion priority - const int _priority; + int _priority; /// Logger instance Logger * _log; - /// state of connection - bool _isActive; - + // current port uint16_t _port; }; diff --git a/include/bonjour/bonjourbrowserwrapper.h b/include/bonjour/bonjourbrowserwrapper.h new file mode 100644 index 00000000..527576d4 --- /dev/null +++ b/include/bonjour/bonjourbrowserwrapper.h @@ -0,0 +1,68 @@ +#pragma once +// qt incl +#include +#include +#include + +#include + +class BonjourServiceBrowser; +class BonjourServiceResolver; +class QTimer; + +class BonjourBrowserWrapper : public QObject +{ + Q_OBJECT +private: + friend class HyperionDaemon; + /// + /// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon + /// Searching for hyperion http service by default + /// + BonjourBrowserWrapper(QObject * parent = 0); + +public: + + /// + /// @brief Browse for a service + /// + bool browseForServiceType(const QString &serviceType); + /// + /// @brief Get all available sessions + /// + QMap getAllServices() { return _hyperionSessions; }; + + static BonjourBrowserWrapper* instance; + static BonjourBrowserWrapper* getInstance(){ return instance; }; + +signals: + /// + /// @brief Emits whenever a change happend + /// + void browserChange(const QMap& bRegisters); + +private: + /// map of service names and browsers + QMap< QString, BonjourServiceBrowser* > _browsedServices; + /// Resolver + BonjourServiceResolver* _bonjourResolver; + + // contains all current active service sessions + QMap _hyperionSessions; + + QString _bonjourCurrentServiceToResolve; + /// timer to resolve changes + QTimer* _timerBonjourResolver; + +private slots: + /// + /// @brief is called whenever a BonjourServiceBrowser emits change + void currentBonjourRecordsChanged(const QList &list); + /// @brief new record resolved + void bonjourRecordResolved(const QHostInfo &hostInfo, int port); + + /// + /// @brief timer slot which updates regularly entries + /// + void bonjourResolve(); +}; diff --git a/include/bonjour/bonjourserviceregister.h b/include/bonjour/bonjourserviceregister.h index 137f97cf..6e3e14ec 100755 --- a/include/bonjour/bonjourserviceregister.h +++ b/include/bonjour/bonjourserviceregister.h @@ -43,7 +43,8 @@ public: BonjourServiceRegister(QObject *parent = 0); ~BonjourServiceRegister(); - void registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt); + void registerService(const QString& service, const int& port); + void registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt = std::vector>()); inline BonjourRecord registeredRecord() const {return finalRecord; } signals: diff --git a/include/effectengine/Effect.h b/include/effectengine/Effect.h new file mode 100644 index 00000000..e028a36a --- /dev/null +++ b/include/effectengine/Effect.h @@ -0,0 +1,89 @@ +#pragma once + +// Python includes +// collide of qt slots macro +#undef slots +#include "Python.h" +#define slots + +// Qt includes +#include +#include +#include +#include +#include +#include + +// Hyperion includes +#include +#include + +class Hyperion; +class Logger; + +class Effect : public QThread +{ + Q_OBJECT + +public: + friend class EffectModule; + + Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args = QJsonObject()); + virtual ~Effect(); + + virtual void run(); + + int getPriority() const { return _priority; }; + + /// + /// @brief Set manual interuption to true, + /// Note: DO NOT USE QThread::interuption! + /// + void setInteruptionFlag() { _interupt = true; }; + + /// + /// @brief Check if the interuption flag has been set + /// @return The flag state + /// + bool hasInteruptionFlag() { return _interupt; }; + + QString getScript() const { return _script; } + QString getName() const { return _name; } + + int getTimeout() const {return _timeout; } + + QJsonObject getArgs() const { return _args; } + +signals: + void setInput(const int priority, const std::vector& ledColors, const int timeout_ms, const bool& clearEffect); + void setInputImage(const int priority, const Image& image, const int timeout_ms, const bool& clearEffect); + +private: + + void addImage(); + + Hyperion* _hyperion; + + const int _priority; + + const int _timeout; + + const QString _script; + const QString _name; + + const QJsonObject _args; + + int64_t _endTime; + + /// Buffer for colorData + QVector _colors; + + Logger* _log; + // Reflects whenever this effects should interupt (timeout or external request) + bool _interupt = false; + + QSize _imageSize; + QImage _image; + QPainter* _painter; + QVector _imageStack; +}; diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index 323b8270..138e6ebf 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -19,7 +19,6 @@ // pre-declarioation class Effect; -typedef struct _ts PyThreadState; class EffectEngine : public QObject { @@ -43,6 +42,20 @@ public: return _effectSchemas; }; + /// + /// @brief Get all init data of the running effects and stop them + /// + void cacheRunningEffects(); + + /// + /// @brief Start all cached effects, origin and smooth cfg is default + /// + void startCachedEffects(); + +signals: + /// Emit when the effect list has been updated + void effectListUpdated(); + public slots: /// Run the specified effect on the given priority channel and optionally specify a timeout int runEffect(const QString &effectName, int priority, int timeout = -1, const QString &origin="System"); @@ -78,9 +91,9 @@ private: std::list _availableActiveEffects; + std::list _cachedActiveEffects; + std::list _effectSchemas; Logger * _log; - - PyThreadState* _mainThreadState; }; diff --git a/libsrc/effectengine/Effect.h b/include/effectengine/EffectModule.h similarity index 55% rename from libsrc/effectengine/Effect.h rename to include/effectengine/EffectModule.h index 7d51d5a5..c274d05b 100644 --- a/libsrc/effectengine/Effect.h +++ b/include/effectengine/EffectModule.h @@ -1,49 +1,27 @@ #pragma once -// Python includes -// collide of qt slots macro #undef slots #include #define slots -// Qt includes -#include -#include -#include -#include -#include +#include -// Hyperion includes -#include -#include +class Effect; -class Effect : public QThread +class EffectModule { - Q_OBJECT - public: - Effect(PyThreadState* mainThreadState, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args = QJsonObject(), const QString & origin="System", unsigned smoothCfg=0); - virtual ~Effect(); + // Python 3 module def + static struct PyModuleDef moduleDef; - virtual void run(); + // Init module + static PyObject* PyInit_hyperion(); - int getPriority() const { return _priority; }; + // Register module once + static void registerHyperionExtensionModule(); - QString getScript() const { return _script; } - QString getName() const { return _name; } - - int getTimeout() const {return _timeout; } - - QJsonObject getArgs() const { return _args; } - - /// This function registers the extension module in Python - static void registerHyperionExtensionModule(); - -signals: - void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects, hyperion::Components componentconst, QString origin, unsigned smoothCfg); - -private: - PyObject * json2python(const QJsonValue & jsonData) const; + // json 2 python + static PyObject * json2python(const QJsonValue & jsonData); // Wrapper methods for Python interpreter extra buildin methods static PyMethodDef effectMethods[]; @@ -71,38 +49,5 @@ private: static PyObject* wrapImageCOffset (PyObject *self, PyObject *args); static PyObject* wrapImageCShear (PyObject *self, PyObject *args); static PyObject* wrapImageResetT (PyObject *self, PyObject *args); - static Effect * getEffect(); - - static struct PyModuleDef moduleDef; - static PyObject* PyInit_hyperion(); - - void addImage(); - - PyThreadState* _mainThreadState; - - const int _priority; - - const int _timeout; - - const QString _script; - const QString _name; - unsigned _smoothCfg; - - const QJsonObject _args; - - int64_t _endTime; - - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - - /// Buffer for colorData - QVector _colors; - - Logger* _log; - - QString _origin; - QSize _imageSize; - QImage _image; - QPainter* _painter; - QVector _imageStack; + static Effect * getEffect(); }; diff --git a/include/grabber/AmlogicWrapper.h b/include/grabber/AmlogicWrapper.h index bfcc9552..0f241162 100644 --- a/include/grabber/AmlogicWrapper.h +++ b/include/grabber/AmlogicWrapper.h @@ -18,9 +18,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this dispmanx frame grabber. Releases any claimed resources. diff --git a/include/grabber/DispmanxFrameGrabber.h b/include/grabber/DispmanxFrameGrabber.h index 3d22d3dc..cd885015 100644 --- a/include/grabber/DispmanxFrameGrabber.h +++ b/include/grabber/DispmanxFrameGrabber.h @@ -40,14 +40,23 @@ public: /// int grabFrame(Image & image); + /// + ///@brief Set new width and height for dispmanx, overwrite Grabber.h impl + virtual void setWidthHeight(int width, int height); + private: - /// + /// /// Updates the frame-grab flags as used by the VC library for frame grabbing /// /// @param vc_flags The snapshot grabbing mask /// void setFlags(const int vc_flags); - + + /// + /// @brief free _vc_resource and captureBuffer + /// + void freeResources(); + /// Handle to the display that is being captured DISPMANX_DISPLAY_HANDLE_T _vc_display; diff --git a/include/grabber/DispmanxWrapper.h b/include/grabber/DispmanxWrapper.h index 618a8b27..e231c809 100644 --- a/include/grabber/DispmanxWrapper.h +++ b/include/grabber/DispmanxWrapper.h @@ -7,8 +7,7 @@ /// /// The DispmanxWrapper uses an instance of the DispmanxFrameGrabber to obtain ImageRgb's from the -/// displayed content. This ImageRgb is processed to a ColorRgb for each led and commmited to the -/// attached Hyperion. +/// displayed content. This ImageRgb is forwarded to all Hyperion instances via HyperionDaemon /// class DispmanxWrapper: public GrabberWrapper { @@ -20,9 +19,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this dispmanx frame grabber. Releases any claimed resources. diff --git a/include/grabber/FramebufferFrameGrabber.h b/include/grabber/FramebufferFrameGrabber.h index 6077bb1b..6296755f 100644 --- a/include/grabber/FramebufferFrameGrabber.h +++ b/include/grabber/FramebufferFrameGrabber.h @@ -5,7 +5,7 @@ #include /// -/// The FramebufferFrameGrabber is used for creating snapshots of the display (screenshots) +/// The FramebufferFrameGrabber is used for creating snapshots of the display (screenshots) /// class FramebufferFrameGrabber : public Grabber { @@ -30,13 +30,18 @@ public: /// int grabFrame(Image & image); + /// + /// @brief Overwrite Grabber.h implememtation + /// + virtual void setDevicePath(const QString& path); + private: /// Framebuffer file descriptor int _fbfd; /// Pointer to framebuffer unsigned char * _fbp; - + /// Framebuffer device e.g. /dev/fb0 - const QString _fbDevice; + QString _fbDevice; }; diff --git a/include/grabber/FramebufferWrapper.h b/include/grabber/FramebufferWrapper.h index 164c56b3..a7c9243c 100644 --- a/include/grabber/FramebufferWrapper.h +++ b/include/grabber/FramebufferWrapper.h @@ -20,7 +20,7 @@ public: /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] /// - FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this framebuffer frame grabber. Releases any claimed resources. diff --git a/include/grabber/OsxFrameGrabber.h b/include/grabber/OsxFrameGrabber.h index 8cba6394..59e39930 100644 --- a/include/grabber/OsxFrameGrabber.h +++ b/include/grabber/OsxFrameGrabber.h @@ -12,7 +12,7 @@ #include /// -/// The OsxFrameGrabber is used for creating snapshots of the display (screenshots) +/// The OsxFrameGrabber is used for creating snapshots of the display (screenshots) /// class OsxFrameGrabber : public Grabber { @@ -37,10 +37,15 @@ public: /// int grabFrame(Image & image); -private: + /// + /// @brief Overwrite Grabber.h implementation + /// + virtual void setDisplayIndex(int index); + +private: /// display - const unsigned _screenIndex; - + unsigned _screenIndex; + /// Reference to the captured diaplay CGDirectDisplayID _display; }; diff --git a/include/grabber/OsxWrapper.h b/include/grabber/OsxWrapper.h index 65b765d7..65ce70ab 100644 --- a/include/grabber/OsxWrapper.h +++ b/include/grabber/OsxWrapper.h @@ -19,9 +19,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this osx frame grabber. Releases any claimed resources. diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index b21aa005..7277b69a 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -24,12 +24,9 @@ class V4L2Grabber : public Grabber public: V4L2Grabber(const QString & device, int input, - VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int horizontalPixelDecimation, - int verticalPixelDecimation + VideoStandard videoStandard, + PixelFormat pixelFormat, + int pixelDecimation ); virtual ~V4L2Grabber(); @@ -37,21 +34,46 @@ public: bool getSignalDetectionEnabled(); int grabFrame(Image &); - -public slots: - void setSignalThreshold( + + /// + /// @brief overwrite Grabber.h implementation, as v4l doesn't use width/height + /// + virtual void setWidthHeight(){}; + + /// + /// @brief set new PixelDecimation value to ImageResampler + /// @param pixelDecimation The new pixelDecimation value + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalThreshold( double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, - int noSignalCounterThreshold); + int noSignalCounterThreshold = 50); - void setSignalDetectionOffset( + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalDetectionOffset( double verticalMin, double horizontalMin, double verticalMax, double horizontalMax); + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalDetectionEnable(bool enable); - void setSignalDetectionEnable(bool enable); + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setInputVideoStandard(int input, VideoStandard videoStandard); + +public slots: bool start(); @@ -66,7 +88,7 @@ private slots: private: void getV4Ldevices(); - + bool init(); void uninit(); @@ -120,9 +142,9 @@ private: std::vector _buffers; PixelFormat _pixelFormat; + int _pixelDecimation; int _lineLength; int _frameByteSize; - int _frameDecimation; // signal detection int _noSignalCounterThreshold; @@ -134,7 +156,6 @@ private: double _y_frac_min; double _x_frac_max; double _y_frac_max; - int _currentFrame; QSocketNotifier * _streamNotifier; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index ffc78a7c..dfa14014 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -12,14 +12,7 @@ public: int input, VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int pixelDecimation, - double redSignalThreshold, - double greenSignalThreshold, - double blueSignalThreshold, - const int priority); + int pixelDecimation ); virtual ~V4L2Wrapper() {}; bool getSignalDetectionEnable(); @@ -28,19 +21,16 @@ public slots: bool start(); void stop(); + void setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold); void setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom); void setSignalDetectionOffset(double verticalMin, double horizontalMin, double verticalMax, double horizontalMax); void setSignalDetectionEnable(bool enable); -// signals: -// void emitColors(int priority, const std::vector &ledColors, const int timeout_ms); - private slots: void newFrame(const Image & image); void readError(const char* err); virtual void action(); - void checkSources(); private: /// The V4L2 grabber diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index 7ef286fb..af833f33 100755 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -17,12 +17,12 @@ class X11Grabber : public Grabber { public: - X11Grabber(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); virtual ~X11Grabber(); bool Setup(); - + /// /// Captures a single snapshot of the display and writes the data to the given image. The /// provided image should have the same dimensions as the configured values (_width and @@ -32,15 +32,34 @@ public: /// height) /// virtual int grabFrame(Image & image, bool forceUpdate=false); - + /// /// update dimension according current screen int updateScreenDimensions(bool force=false); virtual void setVideoMode(VideoMode mode); + /// + /// @brief Apply new width/height values, overwrite Grabber.h implementation as X11 doesn't use width/height, just pixelDecimation to calc dimensions + /// + virtual void setWidthHeight(int width, int height); + + /// + /// @brief Apply new pixelDecimation + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// + virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + private: - bool _useXGetImage, _XShmAvailable, _XShmPixmapAvailable, _XRenderAvailable; + bool _XShmAvailable, _XShmPixmapAvailable, _XRenderAvailable; XImage* _xImage; XShmSegmentInfo _shminfo; @@ -49,17 +68,16 @@ private: Display* _x11Display; Window _window; XWindowAttributes _windowAttr; - + Pixmap _pixmap; XRenderPictFormat* _srcFormat; XRenderPictFormat* _dstFormat; XRenderPictureAttributes _pictAttr; Picture _srcPicture; Picture _dstPicture; - + XTransform _transform; - int _horizontalDecimation; - int _verticalDecimation; + int _pixelDecimation; unsigned _screenWidth; unsigned _screenHeight; @@ -67,7 +85,7 @@ private: unsigned _src_y; Image _image; - + void freeResources(); void setupResources(); }; diff --git a/include/grabber/X11Wrapper.h b/include/grabber/X11Wrapper.h index e29b3ca4..51f2c77c 100644 --- a/include/grabber/X11Wrapper.h +++ b/include/grabber/X11Wrapper.h @@ -24,7 +24,7 @@ public: /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] /// - X11Wrapper(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation, const unsigned updateRate_Hz, const int priority); + X11Wrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz); /// /// Destructor of this framebuffer frame grabber. Releases any claimed resources. @@ -43,4 +43,3 @@ private: bool _init; }; - diff --git a/include/hyperion/BGEffectHandler.h b/include/hyperion/BGEffectHandler.h new file mode 100644 index 00000000..3ca229dc --- /dev/null +++ b/include/hyperion/BGEffectHandler.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +/// +/// @brief Handle the background Effect settings, reacts on runtime to settings changes +/// +class BGEffectHandler : public QObject +{ + Q_OBJECT + +public: + BGEffectHandler(Hyperion* hyperion) + : QObject(hyperion) + , _hyperion(hyperion) + { + // listen for config changes + connect(_hyperion, &Hyperion::settingsChanged, this, &BGEffectHandler::handleSettingsUpdate); + + // init + handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT)); + }; + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) + { + if(type == settings::BGEFFECT) + { + const QJsonObject& BGEffectConfig = config.object(); + + #define BGCONFIG_ARRAY bgColorConfig.toArray() + // clear bg prioritiy + _hyperion->clear(254); + // initial background effect/color + if (BGEffectConfig["enable"].toBool(true)) + { + const QString bgTypeConfig = BGEffectConfig["type"].toString("effect"); + const QString bgEffectConfig = BGEffectConfig["effect"].toString("Warm mood blobs"); + const QJsonValue bgColorConfig = BGEffectConfig["color"]; + if (bgTypeConfig.contains("color")) + { + ColorRgb bg_color = { + (uint8_t)BGCONFIG_ARRAY.at(0).toInt(0), + (uint8_t)BGCONFIG_ARRAY.at(1).toInt(0), + (uint8_t)BGCONFIG_ARRAY.at(2).toInt(0) + }; + _hyperion->setColor(254, bg_color); + Info(Logger::getInstance("HYPERION"),"Inital background color set (%d %d %d)",bg_color.red,bg_color.green,bg_color.blue); + } + else + { + int result = _hyperion->setEffect(bgEffectConfig, 254); + Info(Logger::getInstance("HYPERION"),"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); + } + } + + #undef BGCONFIG_ARRAY + } + }; + +private: + /// Hyperion instance pointer + Hyperion* _hyperion; +}; diff --git a/include/hyperion/CaptureCont.h b/include/hyperion/CaptureCont.h new file mode 100644 index 00000000..4322776c --- /dev/null +++ b/include/hyperion/CaptureCont.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +class Hyperion; + +/// +/// @brief Capture Control class which is a interface to the HyperionDaemon native capture classes. +/// It controls the instance based enable/disable of capture feeds and PriorityMuxer registrations +/// +class CaptureCont : public QObject +{ + Q_OBJECT +public: + CaptureCont(Hyperion* hyperion); + ~CaptureCont(); + + void setSystemCaptureEnable(const bool& enable); + void setV4LCaptureEnable(const bool& enable); + +private slots: + /// + /// @brief Handle component state change of V4L and SystemCapture + /// @param component The component from enum + /// @param enable The new state + /// + void componentStateChanged(const hyperion::Components component, bool enable); + + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief forward system image + /// @param image The image + /// + void handleSystemImage(const Image& image); + + /// + /// @brief forward v4l image + /// @param image The image + /// + void handleV4lImage(const Image & image); + +private: + /// Hyperion instance + Hyperion* _hyperion; + + /// Reflect state of System capture and prio + bool _systemCaptEnabled; + quint8 _systemCaptPrio; + + /// Reflect state of v4l capture and prio + bool _v4lCaptEnabled; + quint8 _v4lCaptPrio; +}; diff --git a/include/hyperion/ComponentRegister.h b/include/hyperion/ComponentRegister.h index 5ae442b0..91bd7228 100644 --- a/include/hyperion/ComponentRegister.h +++ b/include/hyperion/ComponentRegister.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include // STL includes @@ -8,21 +8,61 @@ #include +class Hyperion; + +/// +/// @brief The component register reflects and manages the current state of all components and Hyperion as a whole +/// It emits also real component state changes (triggert from the specific component), which can be used for listening APIs (Network Clients/Plugins) +/// class ComponentRegister : public QObject { Q_OBJECT public: - ComponentRegister(); + ComponentRegister(Hyperion* hyperion); ~ComponentRegister(); + /// + /// @brief Enable or disable Hyperion (all components) + /// @param state The new state of Hyperion + /// + /// @return Returns true on success, false when Hyperion is already at the requested state + /// + bool setHyperionEnable(const bool& state); + + /// + /// @brief Check if a component is currently enabled + /// @param comp The component from enum + /// @return True if component is running else false + /// + bool isComponentEnabled(const hyperion::Components& comp) const; + + /// contains all components and their state std::map getRegister() { return _componentStates; }; +signals: + /// + /// @brief Emits whenever a component changed (really) the state + /// @param comp The component + /// @param state The new state of the component + /// + void updatedComponentState(const hyperion::Components comp, const bool state); + public slots: + /// + /// @brief is called whenever a component change a state, DO NOT CALL FROM API (use hyperion->setComponentState() instead) + /// @param comp The component + /// @param state The new state of the component + /// void componentStateChanged(const hyperion::Components comp, const bool activated); private: - std::map _componentStates; + /// Hyperion instance + Hyperion * _hyperion; + /// Logger instance Logger * _log; + /// current state of all components + std::map _componentStates; + /// on hyperion off we save the previous states of all components + std::map _prevComponentStates; }; - diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index fa81a57b..88207e03 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -6,10 +6,14 @@ #include #include #include +#include #include #include - +/// +/// @brief The Grabber class is responsible to apply image resizes (with or without ImageResampler) +/// Overwrite the videoMode with setVideoMode() +/// Overwrite setCropping() class Grabber : public QObject { Q_OBJECT @@ -24,14 +28,71 @@ public: /// virtual void setVideoMode(VideoMode mode); + /// + /// @brief Apply new crop values, on errors reject the values + /// virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); - /// gets resulting height of image + /// + /// @brief Apply new width/height values, on errors (collide with cropping) reject the values + /// + virtual void setWidthHeight(int width, int height); + + /// + /// @brief Apply new pixelDecimation (used from x11) + /// + virtual void setPixelDecimation(int pixelDecimation) {}; + + /// + /// @brief Apply new signalThreshold (used from v4l) + /// + virtual void setSignalThreshold( + double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + int noSignalCounterThreshold = 50) {}; + /// + /// @brief Apply new SignalDetectionOffset (used from v4l) + /// + virtual void setSignalDetectionOffset( + double verticalMin, + double horizontalMin, + double verticalMax, + double horizontalMax) {}; + + /// + /// @brief Apply SignalDetectionEnable (used from v4l) + /// + virtual void setSignalDetectionEnable(bool enable) {}; + + /// + /// @brief Apply input and videoStanded (used from v4l) + /// + virtual void setInputVideoStandard(int input, VideoStandard videoStandard) {}; + + /// + /// @brief Apply display index (used from x11) + /// + virtual void setDisplayIndex(int index) {}; + + /// + /// @brief Apply path for device (used from framebuffer) + /// + virtual void setDevicePath(const QString& path) {}; + + /// + /// @brief get current resulting height of image (after crop) + /// virtual const int getImageWidth() { return _width; }; - /// gets resulting width of image + /// + /// @brief get current resulting width of image (after crop) + /// virtual const int getImageHeight() { return _height; }; + /// + /// @brief Prevent the real capture implementation from capturing if disabled + /// void setEnabled(bool enable); protected: diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index 7e893a82..ca70a436 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -1,27 +1,29 @@ #pragma once #include -#include #include #include #include #include #include -#include #include #include #include +#include -class ImageProcessor; class Grabber; class DispmanxFrameGrabber; +class QTimer; +/// +/// This class will be inherted by FramebufferWrapper and others which contains the real capture interface +/// class GrabberWrapper : public QObject { Q_OBJECT public: - GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz, const int priority, hyperion::Components grabberComponentId=hyperion::COMP_GRABBER); + GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz); virtual ~GrabberWrapper(); @@ -35,8 +37,6 @@ public: /// virtual void stop(); - void setImageProcessorEnabled(bool enable); - static QStringList availableGrabbers(); public: @@ -45,18 +45,15 @@ public: { unsigned w = grabber.getImageWidth(); unsigned h = grabber.getImageHeight(); - if (_imageProcessorEnabled && ( _image.width() != w || _image.height() != h)) + if ( _image.width() != w || _image.height() != h) { - _processor->setSize(w, h); _image.resize(w, h); } int ret = grabber.grabFrame(_image); if (ret >= 0) { - emit emitImage(_priority, _image, _timeout_ms); - _processor->process(_image, _ledColors); - setColors(_ledColors, _timeout_ms); + emit systemImage(_image); return true; } return false; @@ -64,46 +61,51 @@ public: public slots: - void componentStateChanged(const hyperion::Components component, bool enable); - /// /// virtual method, should perform single frame grab and computes the led-colors /// virtual void action() = 0; - void actionWrapper(); - /// /// Set the video mode (2D/3D) /// @param[in] mode The new video mode /// - virtual void setVideoMode(const VideoMode videoMode); + virtual void setVideoMode(const VideoMode& videoMode); + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + /// + /// @brief Handle settings update from HyperionDaemon Settingsmanager emit + /// @param type settingyType from enum + /// @param config configuration object + /// + virtual void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + signals: - void emitImage(int priority, const Image & image, const int timeout_ms); + /// + /// @brief Emit the final processed image + /// + void systemImage(const Image& image); protected: - void setColors(const std::vector &ledColors, const int timeout_ms); - QString _grabberName; /// Pointer to Hyperion for writing led values Hyperion * _hyperion; - /// The priority of the led colors - const int _priority; - /// The timer for generating events with the specified update rate - QTimer _timer; + QTimer* _timer; - /// The update rate [Hz] - const int _updateInterval_ms; - - /// The timeout of the led colors [ms] - const int _timeout_ms; + /// The calced update rate [ms] + int _updateInterval_ms; /// The Logger instance Logger * _log; @@ -111,18 +113,8 @@ protected: // forwarding enabled bool _forward; - /// The processor for transforming images to led colors - ImageProcessor * _processor; - - hyperion::Components _grabberComponentId; - Grabber *_ggrabber; /// The image used for grabbing frames Image _image; - - /// The list with computed led colors - std::vector _ledColors; - - bool _imageProcessorEnabled; }; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 28c34c4d..c39dce72 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -8,12 +8,10 @@ #include #include #include -#include #include #include #include #include -#include #include // hyperion-utils includes @@ -27,7 +25,6 @@ #include #include #include -#include #include // Effect engine includes @@ -35,17 +32,23 @@ #include #include -// bonjour includes -#include -#include +// settings utils +#include // Forward class declaration +class QTimer; + +class HyperionDaemon; +class ImageProcessor; +class MessageForwarder; class LedDevice; class LinearColorSmoothing; -class RgbTransform; class EffectEngine; -class RgbChannelAdjustment; class MultiColorAdjustment; +class ColorAdjustment; +class SettingsManager; +class BGEffectHandler; +class CaptureCont; /// /// The main class of Hyperion. This gives other 'users' access to the attached LedDevice through @@ -57,8 +60,6 @@ class Hyperion : public QObject public: /// Type definition of the info structure used by the priority muxer typedef PriorityMuxer::InputInfo InputInfo; - typedef QMap PriorityRegister; - typedef QMap BonjourRegister; /// /// RGB-Color channel enumeration /// @@ -79,23 +80,54 @@ public: /// /// @brief creates a new Hyperion instance, usually called from the Hyperion Daemon - /// @param[in] qjsonConfig The configuration file + /// @param[in] daemon The Hyperion daemon parent + /// @param[in] instance The instance id /// @param[in] rootPath Root path of all hyperion userdata /// @return Hyperion instance pointer /// - static Hyperion* initInstance(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath); + static Hyperion* initInstance(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath); /// /// @brief Get a pointer of this Hyperion instance - /// @return Hyperion instance pointer + /// @return Hyperion instance pointer /// static Hyperion* getInstance(); + /// + /// @brief Get a pointer to the effect engine + /// @return EffectEngine instance pointer + /// + EffectEngine* getEffectEngineInstance() { return _effectEngine; }; + + /// + /// @brief Get a pointer to the priorityMuxer instance + /// @return PriorityMuxer instance pointer + /// + PriorityMuxer* getMuxerInstance() { return &_muxer; }; + + /// + /// @brief Get a setting by settings::type from SettingsManager + /// @param type The settingsType from enum + /// @return Data Document + /// + QJsonDocument getSetting(const settings::type& type); + + /// + /// @brief Save a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + bool saveSettings(QJsonObject config, const bool& correct = false); + /// /// Returns the number of attached leds /// unsigned getLedCount() const; + /// + /// @brief Return the size of led grid + /// QSize getLedGridSize() const { return _ledGridSize; }; /// @@ -124,11 +156,9 @@ public: /// /// @param[in] priority The priority channel /// - /// @return The information of the given + /// @return The information of the given, a not found priority will return lowest priority as fallback /// - /// @throw std::runtime_error when the priority channel does not exist - /// - const InputInfo& getPriorityInfo(const int priority) const; + const InputInfo getPriorityInfo(const int priority) const; /// Reload the list of available effects void reloadEffects(); @@ -145,27 +175,29 @@ public: /// @return The list of available effect schema files const std::list &getEffectSchemas(); - /// gets the current json config object + /// gets the current json config object from SettingsManager /// @return json config - const QJsonObject& getQJsonConfig() { return _qjsonConfig; }; + const QJsonObject& getQJsonConfig(); + + /// get path+filename of configfile + /// @return the current config path+filename + QString getConfigFilePath() { return _configFile; }; /// get filename of configfile /// @return the current config filename - QString getConfigFileName() { return _configFile; }; + QString getConfigFileName() const; - /// register a input source to a priority channel - /// @param name uniq name of input source - /// @param origin External setter - /// @param priority priority channel - void registerPriority(const QString &name, const int priority); + /// + /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() + /// A repeated call to update the base data of a known priority won't overwrite their current timeout + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] smooth_cfg The smooth id to use + /// + void registerInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0); - /// unregister a input source to a priority channel - /// @param name uniq name of input source - void unRegisterPriority(const QString &name); - - /// gets current priority register - /// @return the priority register - const PriorityRegister& getPriorityRegister() { return _priorityRegister; } /// enable/disable automatic/priorized source selection /// @param enabled the state @@ -178,12 +210,18 @@ public: /// gets current state of automatic/priorized source selection /// @return the state - bool sourceAutoSelectEnabled() { return _sourceAutoSelectEnabled; }; + bool sourceAutoSelectEnabled(); /// - /// Enable/Disable components during runtime + /// @brief Get the last untransformed/unadjusted led colors + /// @return The _rawLedBuffer leds /// - /// @param component The component [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER] + const std::vector& getRawLedBuffer() { return _rawLedBuffer; }; + + /// + /// @brief Enable/Disable components during runtime, called from external API (requests) + /// + /// @param component The component from enum /// @param state The state of the component [true | false] /// void setComponentState(const hyperion::Components component, const bool state); @@ -195,54 +233,63 @@ public: bool configWriteable() { return _configWrite; }; /// gets the methode how image is maped to leds - int getLedMappingType() { return _ledMAppingType; }; - - /// get the configuration - QJsonObject getConfig() { return _qjsonConfig; }; + const int & getLedMappingType(); /// get the root path for all hyperion user data files - QString getRootPath() { return _rootPath; }; + const QString &getRootPath() { return _rootPath; }; - /// unique id per instance - QString id; + /// get unique id per instance + const QString &getId(){ return _id; }; + /// set unique id + void setId(QString id){ _id = id; }; int getLatchTime() const; /// forward smoothing config unsigned addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); - VideoMode getCurrentVideoMode() { return _videoMode; }; + const VideoMode & getCurrentVideoMode(); + + /// + /// @brief Get the current active led device + /// @return The device nam + /// e + const QString & getActiveDevice(); public slots: + /// + /// @brief Update the current color of a priority (prev registered with registerInput()) + /// DO NOT use this together with setInputImage() at the same time! + /// @param priority The priority to update + /// @param ledColors The colors + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @param clearEffect Should be true when NOT called from an effect + /// @return True on success, false when priority is not found + /// + const bool setInput(const int priority, const std::vector& ledColors, const int timeout_ms = -1, const bool& clearEffect = true); + + /// + /// @brief Update the current image of a priority (prev registered with registerInput()) + /// DO NOT use this together with setInput() at the same time! + /// @param priority The priority to update + /// @param image The new image + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @param clearEffect Should be true when NOT called from an effect + /// @return True on success, false when priority is not found + /// + const bool setInputImage(const int priority, const Image& image, int64_t timeout_ms = -1, const bool& clearEffect = true); + /// /// Writes a single color to all the leds for the given time and priority + /// Registers comp color or provided type against muxer + /// Should be never used to update leds continuous /// /// @param[in] priority The priority of the written color /// @param[in] ledColor The color to write to the leds + /// @param[in] origin The setter /// @param[in] timeout_ms The time the leds are set to the given color [ms] /// - void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms, bool clearEffects = true); - - /// - /// Writes the given colors to all leds for the given time and priority - /// - /// @param[in] priority The priority of the written colors - /// @param[in] ledColors The colors to write to the leds - /// @param[in] timeout_ms The time the leds are set to the given colors [ms] - /// @param[in] component The current component - /// @param[in] origin Who set it - /// @param[in] smoothCfg smoothing config id - /// - void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects = true, hyperion::Components component=hyperion::COMP_INVALID, const QString origin="System", unsigned smoothCfg=SMOOTHING_MODE_DEFAULT); - - /// - /// Writes the given colors to all leds for the given time and priority - /// - /// @param[in] priority The priority of the written colors - /// @param[in] ledColors The colors to write to the leds - /// @param[in] timeout_ms The time the leds are set to the given colors [ms] - /// - void setImage(int priority, const Image & image, int duration_ms); + void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms = -1, const QString& origin = "System" ,bool clearEffects = true); /// /// Returns the list with unique adjustment identifiers @@ -270,8 +317,9 @@ public slots: /// lower priority channel (or off if no more channels are set) /// /// @param[in] priority The priority channel + /// @return True on success else false (not found) /// - void clear(int priority); + const bool clear(int priority); /// /// Clears all priority channels. This will switch the leds off until a new priority is written. @@ -292,44 +340,18 @@ public slots: int setEffect(const QString & effectName, const QJsonObject & args, int priority, int timeout = -1, const QString & pythonScript = "", const QString & origin="System"); - /// sets the methode how image is maped to leds - void setLedMappingType(int mappingType); - - /// - Hyperion::BonjourRegister getHyperionSessions(); - - /// Slot which is called, when state of hyperion has been changed - void hyperionStateChanged(); + /// sets the methode how image is maped to leds at ImageProcessor + void setLedMappingType(const int& mappingType); /// /// Set the video mode (2D/3D) /// @param[in] mode The new video mode /// - void setVideoMode(VideoMode mode); + void setVideoMode(const VideoMode& mode); public: static Hyperion *_hyperion; - static ColorOrder createColorOrder(const QJsonObject & deviceConfig); - /** - * Construct the 'led-string' with the integration area definition per led and the color - * ordering of the RGB channels - * @param ledsConfig The configuration of the led areas - * @param deviceOrder The default RGB channel ordering - * @return The constructed ledstring - */ - static LedString createLedString(const QJsonValue & ledsConfig, const ColorOrder deviceOrder); - static LedString createLedStringClone(const QJsonValue & ledsConfig, const ColorOrder deviceOrder); - - static MultiColorAdjustment * createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorAdjustmentConfig); - static ColorAdjustment * createColorAdjustment(const QJsonObject & adjustmentConfig); - static RgbTransform * createRgbTransform(const QJsonObject& colorConfig); - static RgbChannelAdjustment * createRgbChannelAdjustment(const QJsonObject & colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB); - - static LinearColorSmoothing * createColorSmoothing(const QJsonObject & smoothingConfig, LedDevice* leddevice); - static MessageForwarder * createMessageForwarder(const QJsonObject & forwarderConfig); - static QSize getLedLayoutGridSize(const QJsonValue& ledsConfig); - signals: /// Signal which is emitted when a priority channel is actively cleared /// This signal will not be emitted when a priority channel time out @@ -339,25 +361,67 @@ signals: /// This signal will not be emitted when a priority channel time out void allChannelsCleared(); + /// + /// @brief Emits whenever a user request a component state change, it's up the component to listen + /// and update the component state at the componentRegister + /// @param component The component from enum + /// @param enabled The new state of the component + /// void componentStateChanged(const hyperion::Components component, bool enabled); - void imageToLedsMappingChanged(int mappingType); - void emitImage(int priority, const Image & image, const int timeout_ms); + /// + /// @brief Emits whenever the imageToLedsMapping has changed + /// @param mappingType The new mapping type + /// + void imageToLedsMappingChanged(const int& mappingType); + + /// + /// @brief Emits whenever the visible priority delivers a image which is applied in update() + /// priorities with ledColors won't emit this signal + /// @param image The current image + /// + void currentImage(const Image & image); + void closing(); /// Signal which is emitted, when a new json message should be forwarded void forwardJsonMessage(QJsonObject); - /// Signal which is emitted, after the hyperionStateChanged has been processed with a emit count blocker (250ms interval) - void sendServerInfo(); - - /// Signal emitted when a 3D movie is detected - void videoMode(VideoMode mode); + /// + /// @brief Is emitted from clients who request a videoMode change + /// + void videoMode(const VideoMode& mode); /// - /// @brief Emits whenever new untransformed ledColos data is available, reflects the current visible device + /// @brief A new videoMode was requested (called from Daemon!) /// - void rawLedColors(const std::vector& ledValues); + void newVideoMode(const VideoMode& mode); + + /// + /// @brief Emits whenever a config part changed. SIGNAL PIPE helper for SettingsManager -> HyperionDaemon + /// @param type The settings type from enum + /// @param data The data as QJsonDocument + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + + /// + /// @brief Emits whenever the adjustments have been updated + /// + void adjustmentChanged(); + + /// + /// @brief Signal pipe from EffectEngine to external, emits when effect list has been updated + /// + void effectListUpdated(); + + /// + /// @brief systemImage from the parent HyperionDaemon SystemCapture + /// + void systemImage(const Image& image); + /// + /// @brief v4lImage from the parent HyperionDaemon V4lCapture + /// + void v4lImage(const Image & image); private slots: /// @@ -366,13 +430,23 @@ private slots: /// void update(); - void currentBonjourRecordsChanged(const QList &list); - void bonjourRecordResolved(const QHostInfo &hostInfo, int port); - void bonjourResolve(); - /// check for configWriteable and modified changes, called by _fsWatcher or fallback _cTimer void checkConfigState(QString cfile = NULL); + /// + /// @brief Apply ComponentRegister emits for COMP_ALL. Enables/Disables core timers + /// @param comp The component + /// @param state The new state of the component + /// + void updatedComponentState(const hyperion::Components comp, const bool state); + + /// + /// @brief Apply settings updates for LEDS and COLOR + /// @param type The type from enum + /// @param config The configuration + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: /// @@ -380,7 +454,16 @@ private: /// /// @param[in] qjsonConfig The Json configuration /// - Hyperion(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath); + Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath); + + /// The parent Hyperion Daemon + HyperionDaemon* _daemon; + + /// Settings manager of this instance + SettingsManager* _settingsManager; + + /// Register that holds component states + ComponentRegister _componentRegister; /// The specifiation of the led frame construction and picture integration LedString _ledString; @@ -388,6 +471,9 @@ private: /// specifiation of cloned leds LedString _ledStringClone; + /// Image Processor + ImageProcessor* _imageProcessor; + std::vector _ledStringColorOrder; /// The priority muxer @@ -408,21 +494,14 @@ private: // proto and json Message forwarder MessageForwarder * _messageForwarder; - // json configuration - const QJsonObject& _qjsonConfig; - /// the name of config file QString _configFile; /// root path for all hyperion user data files QString _rootPath; - /// The timer for handling priority channel timeouts - QTimer _timer; - QTimer _timerBonjourResolver; - - /// buffer for leds (with adjustment) - std::vector _ledBuffer; + /// unique id per instance + QString _id; /// Logger instance Logger * _log; @@ -430,32 +509,16 @@ private: /// count of hardware leds unsigned _hwLedCount; - ComponentRegister _componentRegister; - - /// register of input sources and it's prio channel - PriorityRegister _priorityRegister; - - /// flag indicates state for autoselection of input source - bool _sourceAutoSelectEnabled; - - /// holds the current priority channel that is manualy selected - int _currentSourcePriority; - QByteArray _configHash; QSize _ledGridSize; - int _ledMAppingType; - + /// Store the previous compID for smarter update() hyperion::Components _prevCompId; - BonjourServiceBrowser _bonjourBrowser; - BonjourServiceResolver _bonjourResolver; - BonjourRegister _hyperionSessions; - QString _bonjourCurrentServiceToResolve; /// Observe filesystem changes (_configFile), if failed use Timer QFileSystemWatcher _fsWatcher; - QTimer _cTimer; + QTimer* _cTimer; /// holds the prev states of configWriteable and modified bool _prevConfigMod = false; @@ -465,9 +528,16 @@ private: bool _configMod = false; bool _configWrite = true; - /// timers to handle severinfo blocking - QTimer _fsi_timer; - QTimer _fsi_blockTimer; + /// Background effect instance, kept active to react on setting changes + BGEffectHandler* _BGEffectHandler; + /// Capture control for Daemon native capture + CaptureCont* _captureCont; - VideoMode _videoMode; + // lock Hyperion::update() for exec + bool _lockUpdate = false; + + /// buffer for leds (with adjustment) + std::vector _ledBuffer; + /// buffer for leds (without adjustment) + std::vector _rawLedBuffer; }; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 935fcaea..d2c16216 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -6,14 +6,18 @@ #include // Hyperion includes -#include #include #include #include +// settings +#include + // Black border includes #include +class Hyperion; + /// /// The ImageProcessor translates an RGB-image to RGB-values for the leds. The processing is /// performed in two steps. First the average color per led-region is computed. Second a @@ -24,14 +28,16 @@ class ImageProcessor : public QObject Q_OBJECT public: + /// + /// Constructs an image-processor for translating an image to led-color values based on the + /// given led-string specification + /// @param[in] ledString LedString data + /// @param[in] hyperion Hyperion instance pointer + /// + ImageProcessor(const LedString& ledString, Hyperion* hyperion); ~ImageProcessor(); - /// - /// Returns the number of attached leds - /// - unsigned getLedCount() const; - /// /// Specifies the width and height of 'incomming' images. This will resize the buffer-image to /// match the given size. @@ -42,20 +48,39 @@ public: /// void setSize(const unsigned width, const unsigned height); + /// + /// @brief Update the led string (eg on settings change) + /// + void setLedString(const LedString& ledString); + /// Returns starte of black border detector bool blackBorderDetectorEnabled(); - /// Returns starte of black border detector - int ledMappingType(); + /// Returns the current _userMappingType, this may not be the current applied type! + const int & getUserLedMappingType() { return _userMappingType; }; + + /// Returns the current _mappingType + const int & ledMappingType() { return _mappingType; }; static int mappingTypeToInt(QString mappingType); static QString mappingTypeToStr(int mappingType); -public slots: - /// Enable or disable the black border detector - void enableBlackBorderDetector(bool enable); + /// + /// @brief Set the Hyperion::update() requestes led mapping type. This type is used in favour of type set with setLedMappingType. + /// If you don't want to force a mapType set this to -1 (user choice will be set) + /// @param mapType The new mapping type + /// + void setHardLedMappingType(int mapType); - /// Enable or disable the black border detector +public slots: + /// Enable or disable the black border detector based on component + void setBlackbarDetectDisable(bool enable); + + /// + /// @brief Set the user requested led mapping. + /// The type set with setHardLedMappingType() will be used in favour to respect comp specific settings + /// @param mapType The new mapping type + /// void setLedMappingType(int mapType); public: @@ -101,7 +126,7 @@ public: } else { - Warning(_log, "ImageProcessor::process called without image size 0"); + Warning(_log, "ImageProcessor::process called with image size 0"); } // return the computed colors @@ -134,7 +159,7 @@ public: } else { - Warning(_log, "ImageProcessor::process called without image size 0"); + Warning(_log, "Called with image size 0"); } } @@ -150,18 +175,6 @@ public: 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; - - /// - /// Constructs an image-processor for translating an image to led-color values based on the - /// given led-string specification - /// - /// @param[in] ledString The led-string specification - /// @param[in] blackborderThreshold The threshold which the blackborder detector should use - /// - ImageProcessor(const LedString &ledString, const QJsonObject &blackborderConfig); - /// /// Performs black-border detection (if enabled) on the given image /// @@ -172,7 +185,7 @@ private: { if (!_borderProcessor->enabled() && ( _imageToLeds->horizontalBorder()!=0 || _imageToLeds->verticalBorder()!=0 )) { - Debug(Logger::getInstance("BLACKBORDER"), "disabled, reset border"); + Debug(_log, "Reset border"); _borderProcessor->process(image); delete _imageToLeds; _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds()); @@ -180,8 +193,6 @@ private: if(_borderProcessor->enabled() && _borderProcessor->process(image)) { - //Debug(Logger::getInstance("BLACKBORDER"), "BORDER SWITCH REQUIRED!!"); - const hyperion::BlackBorder border = _borderProcessor->getCurrentBorder(); // Clean up the old mapping @@ -203,10 +214,13 @@ private: } } +private slots: + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: Logger * _log; /// The Led-string specification - const LedString _ledString; + LedString _ledString; /// The processor for black border detection hyperion::BlackBorderProcessor * _borderProcessor; @@ -216,4 +230,11 @@ private: /// Type of image 2 led mapping int _mappingType; + /// Type of last requested user type + int _userMappingType; + /// Type of last requested hard type + int _hardMappingType; + + /// Hyperion instance pointer + Hyperion* _hyperion; }; diff --git a/include/hyperion/ImageProcessorFactory.h b/include/hyperion/ImageProcessorFactory.h deleted file mode 100644 index e7381ad0..00000000 --- a/include/hyperion/ImageProcessorFactory.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -// STL includes -#include - -// QT includes -#include - -#include - -// Forward class declaration -class ImageProcessor; - -/// -/// The ImageProcessor is a singleton factor for creating ImageProcessors that translate images to -/// led color values. -/// -class ImageProcessorFactory -{ -public: - /// - /// Returns the 'singleton' instance (creates the singleton if it does not exist) - /// - /// @return The singleton instance of the ImageProcessorFactory - /// - static ImageProcessorFactory& getInstance(); - -public: - /// - /// Initialises this factory with the given led-configuration - /// - /// @param[in] ledString The led configuration - /// @param[in] blackborderConfig Contains the blackborder configuration - /// - void init(const LedString& ledString, const QJsonObject &blackborderConfig, int mappingType); - - /// - /// Creates a new ImageProcessor. The onwership of the processor is transferred to the caller. - /// - /// @return The newly created ImageProcessor - /// - ImageProcessor* newImageProcessor() const; - -private: - /// The Led-string specification - LedString _ledString; - - /// Reference to the blackborder json configuration values - QJsonObject _blackborderConfig; - - // image 2 led mapping type - int _mappingType; -}; diff --git a/include/hyperion/ImageToLedsMap.h b/include/hyperion/ImageToLedsMap.h index 116d4760..7896592d 100644 --- a/include/hyperion/ImageToLedsMap.h +++ b/include/hyperion/ImageToLedsMap.h @@ -7,6 +7,7 @@ // hyperion-utils includes #include +#include // hyperion includes #include @@ -58,7 +59,7 @@ namespace hyperion const unsigned horizontalBorder() const { return _horizontalBorder; }; const unsigned verticalBorder() const { return _verticalBorder; }; - + /// /// Determines the mean-color for each led using the mapping the image given /// at construction. @@ -86,7 +87,12 @@ namespace hyperion void getMeanLedColor(const Image & image, std::vector & ledColors) const { // Sanity check for the number of leds - assert(_colorsMap.size() == ledColors.size()); + //assert(_colorsMap.size() == ledColors.size()); + if(_colorsMap.size() != ledColors.size()) + { + Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } // Iterate each led and compute the mean auto led = ledColors.begin(); @@ -96,7 +102,7 @@ namespace hyperion *led = color; } } - + /// /// Determines the mean-color for each led using the mapping the image given /// at construction. @@ -124,7 +130,13 @@ namespace hyperion void getUniLedColor(const Image & image, std::vector & ledColors) const { // Sanity check for the number of leds - assert(_colorsMap.size() == ledColors.size()); + // assert(_colorsMap.size() == ledColors.size()); + if(_colorsMap.size() != ledColors.size()) + { + Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } + // calculate uni color const ColorRgb color = calcMeanColor(image); @@ -136,11 +148,11 @@ namespace hyperion const unsigned _width; /// The height of the indexed image const unsigned _height; - + const unsigned _horizontalBorder; - + const unsigned _verticalBorder; - + /// The absolute indices into the image for each led std::vector> _colorsMap; diff --git a/include/hyperion/MessageForwarder.h b/include/hyperion/MessageForwarder.h index d0d5de1a..ea247c53 100644 --- a/include/hyperion/MessageForwarder.h +++ b/include/hyperion/MessageForwarder.h @@ -10,31 +10,45 @@ #include #include #include +#include +#include +#include // Utils includes #include -class MessageForwarder +#include +#include + +class Hyperion; + +class MessageForwarder : public QObject { + Q_OBJECT public: - struct JsonSlaveAddress { - QHostAddress addr; - quint16 port; - }; - - MessageForwarder(); + MessageForwarder(Hyperion* hyperion, const QJsonDocument & config); ~MessageForwarder(); - + void addJsonSlave(QString slave); void addProtoSlave(QString slave); bool protoForwardingEnabled(); bool jsonForwardingEnabled(); bool forwardingEnabled() { return jsonForwardingEnabled() || protoForwardingEnabled(); }; - QStringList getProtoSlaves(); - QList getJsonSlaves(); + QStringList getProtoSlaves() const { return _protoSlaves; }; + QStringList getJsonSlaves() const { return _jsonSlaves; }; + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private: - QStringList _protoSlaves; - QList _jsonSlaves; + Hyperion* _hyperion; + Logger* _log; + QStringList _protoSlaves; + QStringList _jsonSlaves; }; diff --git a/libsrc/hyperion/MultiColorAdjustment.h b/include/hyperion/MultiColorAdjustment.h similarity index 98% rename from libsrc/hyperion/MultiColorAdjustment.h rename to include/hyperion/MultiColorAdjustment.h index f6568049..df101122 100644 --- a/libsrc/hyperion/MultiColorAdjustment.h +++ b/include/hyperion/MultiColorAdjustment.h @@ -26,7 +26,7 @@ public: */ void addAdjustment(ColorAdjustment * adjustment); - void setAdjustmentForLed(const QString& id, const unsigned startLed, const unsigned endLed); + void setAdjustmentForLed(const QString& id, const unsigned startLed, unsigned endLed); bool verifyAdjustments() const; diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index a3316940..21af614b 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -7,23 +7,25 @@ // QT includes #include #include -#include #include #include // Utils includes #include +#include #include // global defines #define SMOOTHING_MODE_DEFAULT 0 #define SMOOTHING_MODE_PAUSE 1 +class QTimer; +class Logger; /// -/// The PriorityMuxer handles the priority channels. Led values input is written to the priority map +/// The PriorityMuxer handles the priority channels. Led values input/ images are written to the priority map /// and the muxer keeps track of all active priorities. The current priority can be queried and per -/// priority the led colors. +/// priority the led colors. Handles also manual/auto selection mode, provides a lot of signals to hook into priority related events /// class PriorityMuxer : public QObject { @@ -36,17 +38,20 @@ public: { /// The priority of this channel int priority; - /// The absolute timeout of the channel int64_t timeoutTime_ms; /// The colors for each led of the channel std::vector ledColors; + /// The raw Image (size should be preprocessed!) + Image image; /// The component hyperion::Components componentId; /// Who set it QString origin; - /// id fo smoothing config + /// id of smoothing config unsigned smooth_cfg; + /// specific owner description + QString owner; }; /// The lowest possible priority, which is used when no priority channels are active @@ -65,6 +70,38 @@ public: /// ~PriorityMuxer(); + /// + /// @brief Start/Stop the PriorityMuxer update timer; On disabled no priority and timeout updates will be performend + /// @param enable The new state + /// + void setEnable(const bool& enable); + + /// @brief Enable or disable auto source selection + /// @param enable True if it should be enabled else false + /// @param update True to update _currentPriority - INTERNAL usage. + /// @return True if changed has been applied, false if the state is unchanged + /// + bool setSourceAutoSelectEnabled(const bool& enabel, const bool& update = true); + + /// + /// @brief Get the state of source auto selection + /// @return True if enabled, else false + /// + bool isSourceAutoSelectEnabled() const { return _sourceAutoSelectEnabled; }; + + /// + /// @brief Overwrite current lowest piority with manual selection; On success disables aito selection + /// @param priority The + /// @return True on success, false if priority not found + /// + bool setPriority(const uint8_t priority); + + /// + /// @brief Update all ledColos with min length of >= 1 to fit the new led length + /// @param[in] ledCount The count of leds + /// + void updateLedColorsLength(const int& ledCount); + /// /// Returns the current priority /// @@ -87,69 +124,134 @@ public: QList getPriorities() const; /// - /// Returns the information of a specified priority channel + /// Returns the information of a specified priority channel. + /// If a priority is no longer available the _lowestPriorityInfo (255) is returned /// /// @param priority The priority channel /// /// @return The information for the specified priority channel /// - /// @throws std::runtime_error if the priority channel does not exist - /// - const InputInfo& getInputInfo(const int priority) const; + const InputInfo getInputInfo(const int priority) const; /// - /// Sets/Updates the data for a priority channel + /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() + /// A repeated call to update the base data of a known priority won't overwrite their current timeout + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] smooth_cfg The smooth id to use /// - /// @param[in] priority The priority of the channel - /// @param[in] ledColors The led colors of the priority channel - /// @param[in] timeoutTime_ms The absolute timeout time of the channel - /// @param[in] component The component of the channel - /// @param[in] origin Who set the channel - /// - void setInput(const int priority, const std::vector& ledColors, const int64_t timeoutTime_ms=-1, hyperion::Components component=hyperion::COMP_INVALID, const QString origin="System", unsigned smooth_cfg=SMOOTHING_MODE_DEFAULT); + void registerInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = SMOOTHING_MODE_DEFAULT); /// - /// Clears the specified priority channel + /// @brief Update the current color of a priority (prev registered with registerInput()) + /// @param priority The priority to update + /// @param ledColors The colors + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @return True on success, false when priority is not found + /// + const bool setInput(const int priority, const std::vector& ledColors, int64_t timeout_ms = -1); + + /// + /// @brief Update the current image of a priority (prev registered with registerInput()) + /// @param priority The priority to update + /// @param image The new image + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @return True on success, false when priority is not found + /// + const bool setInputImage(const int priority, const Image& image, int64_t timeout_ms = -1); + + /// + /// Clears the specified priority channel and update _currentPriority on success /// /// @param[in] priority The priority of the channel to clear + /// @return True if priority has been cleared else false (not found) /// - void clearInput(const int priority); + const bool clearInput(const uint8_t priority); /// /// Clears all priority channels /// void clearAll(bool forceClearAll=false); +signals: + /// + /// @brief Signal which emits when a effect or color with timeout > -1 is running, once per second + /// + void timeRunner(); + + /// + /// @brief A priority has been added (registerInput()) or deleted, method clear or timeout clear + /// @param priority The priority which has changed + /// @param state If true it was added else it was removed! + /// + void priorityChanged(const quint8& priority, const bool& state); + + /// + /// @brief Emits whenever the visible priority has changed + /// @param priority The new visible prioritiy + /// + void visiblePriorityChanged(const quint8& priority); + + /// + /// @brief Emits whenever a priority changes active state + /// @param priority The priority who changed the active state + /// @param state The new state, state true = active else false + /// + void activeStateChanged(const quint8& priority, const bool& state); + + /// + /// @brief Emits whenever the auto selection state has been changed + /// @param state The new state of auto selection; True enabled else false + /// + void autoSelectChanged(const bool& state); + + /// + /// @brief Emits whenever something changes which influences the priorities listing + /// Emits also in 1s interval when a COLOR or EFFECT is running with a timeout > -1 + /// + void prioritiesChanged(void); + + /// + /// internal used signal to resolve treading issues with timer + /// + void signalTimeTrigger(); + +private slots: + /// + /// Slot which is called to adapt to 1s interval for signal timeRunner() / prioritiesChanged() + /// + void timeTrigger(); + /// /// Updates the current time. Channels with a configured time out will be checked and cleared if /// required. /// - /// @param[in] now The current time - /// - 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(); + void setCurrentTime(void); private: + /// Logger instance + Logger* _log; + /// The current priority (lowest value in _activeInputs) int _currentPriority; + /// The manual select priority set with setPriority + int _manualSelectedPriority; + /// The mapping from priority channel to led-information QMap _activeInputs; /// The information of the lowest priority channel InputInfo _lowestPriorityInfo; - QTimer _timer; - QTimer _blockTimer; + // Reflect the state of auto select + bool _sourceAutoSelectEnabled; + + // Timer to update Muxer times independent + QTimer* _updateTimer; + + QTimer* _timer; + QTimer* _blockTimer; }; diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h new file mode 100644 index 00000000..f3fa1d63 --- /dev/null +++ b/include/hyperion/SettingsManager.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +// qt incl +#include + +class SettingsTable; +class Hyperion; + +/// +/// @brief Manage the settings read write from/to database, on settings changed will emit a signal to update components accordingly +/// +class SettingsManager : public QObject +{ + Q_OBJECT +public: + /// + /// @brief Construct a settings manager and assign a hyperion instance + /// @params hyperion The parent hyperion instance + /// @params instance Instance number of Hyperion + /// + SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile); + + /// + /// @brief Construct a settings manager for HyperionDaemon + /// + SettingsManager(const quint8& instance, const QString& configFile); + ~SettingsManager(); + + /// + /// @brief Save a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + const bool saveSettings(QJsonObject config, const bool& correct = false); + + /// + /// @brief get a single setting json from database + /// @param type The settings::type from enum + /// @return The requested json data as QJsonDocument + /// + const QJsonDocument getSetting(const settings::type& type); + + /// + /// @brief get the full settings object of this instance (with global settings) + /// @return The requested json + /// + const QJsonObject & getSettings() { return _qconfig; }; + +signals: + /// + /// @brief Emits whenever a config part changed. Comparison of database and new data to prevent false positive + /// @param type The settings type from enum + /// @param data The data as QJsonDocument + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + +private: + /// Hyperion instance + Hyperion* _hyperion; + /// Logger instance + Logger* _log; + /// instance of database table interface + SettingsTable* _sTable; + /// the schema + static QJsonObject schemaJson; + /// the current config of this instance + QJsonObject _qconfig; +}; diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index 912e14ac..e0c64206 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -1,14 +1,20 @@ #pragma once // Qt includes -#include #include // Hyperion includes -#include +#include #include +#include +class Hyperion; +class QTcpServer; +class QTcpSocket; class JsonClientConnection; +class BonjourServiceRegister; +class ComponentRegister; +class NetOrigin; /// /// This class creates a TCP server which accepts connections wich can then send @@ -22,10 +28,9 @@ class JsonServer : public QObject public: /// /// JsonServer constructor - /// @param hyperion Hyperion instance - /// @param port port number on which to start listening for connections + /// @param The configuration /// - JsonServer(uint16_t port = 19444); + JsonServer(const QJsonDocument& config); ~JsonServer(); /// @@ -59,9 +64,16 @@ public slots: /// void sendMessage(const QJsonObject & message, QTcpSocket * socket); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// Link to Hyperion to get config state emiter Hyperion * _hyperion; @@ -72,6 +84,16 @@ private: /// the logger instance Logger * _log; - /// Flag if forwarder is enabled - bool _forwarder_enabled = true; + /// Component Register pointer + ComponentRegister* _componentRegister; + + NetOrigin* _netOrigin; + + /// port + uint16_t _port = 0; + + BonjourServiceRegister * _serviceRegister = nullptr; + + void start(); + void stop(); }; diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h index 27218e5a..8b9977e2 100644 --- a/include/leddevice/LedDevice.h +++ b/include/leddevice/LedDevice.h @@ -55,13 +55,19 @@ public: /// virtual int open(); + /// + /// @brief Get color order of device + /// @return The color order + /// + const QString & getColorOrder() { return _colorOrder; }; + static int addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr); static const LedDeviceRegistry& getDeviceMap(); - static void setActiveDevice(QString dev); - static QString activeDevice() { return _activeDevice; } + void setActiveDevice(QString dev); + const QString & getActiveDevice() { return _activeDevice; }; static QJsonObject getLedDeviceSchemas(); - static void setLedCount(int ledCount); - static int getLedCount() { return _ledCount; } + void setLedCount(int ledCount); + int getLedCount() { return _ledCount; } void setEnable(bool enable); bool enabled() { return _enabled; }; @@ -95,12 +101,12 @@ protected: bool _deviceReady; - static QString _activeDevice; + QString _activeDevice; static LedDeviceRegistry _ledDeviceMap; - static int _ledCount; - static int _ledRGBCount; - static int _ledRGBWCount; + int _ledCount; + int _ledRGBCount; + int _ledRGBWCount; /// Timer object which makes sure that led data is written at a minimum rate /// e.g. Adalight device will switch off when it does not receive data at least every 15 seconds @@ -116,4 +122,5 @@ private: std::vector _ledValues; bool _componentRegistered; bool _enabled; + QString _colorOrder; }; diff --git a/include/leddevice/LedDeviceFactory.h b/include/leddevice/LedDeviceFactory.h index 29691a98..5a26a8ed 100644 --- a/include/leddevice/LedDeviceFactory.h +++ b/include/leddevice/LedDeviceFactory.h @@ -4,7 +4,6 @@ // Leddevice includes #include - /// /// The LedDeviceFactory is responsible for constructing 'LedDevices' /// @@ -20,5 +19,5 @@ public: /// @return The constructed LedDevice or nullptr if configuration is invalid. The ownership of /// the constructed LedDevice is tranferred to the caller /// - static LedDevice * construct(const QJsonObject & deviceConfig, const int ledCount); + static LedDevice * construct(const QJsonObject & deviceConfig); }; diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h index 12daf1c8..e285a6cb 100644 --- a/include/protoserver/ProtoServer.h +++ b/include/protoserver/ProtoServer.h @@ -4,23 +4,29 @@ #include // Qt includes -#include #include #include #include - -// Hyperion includes -#include +#include // hyperion includes #include #include #include #include +#include + +// settings +#include // forward decl class ProtoClientConnection; class ProtoConnection; +class QTcpServer; +class Hyperion; +class BonjourServiceRegister; +class ComponentRegister; +class NetOrigin; namespace proto { class HyperionRequest; @@ -38,10 +44,9 @@ class ProtoServer : public QObject public: /// /// ProtoServer constructor - /// @param hyperion Hyperion instance - /// @param port port number on which to start listening for connections + /// @param config the configuration /// - ProtoServer(uint16_t port = 19445); + ProtoServer(const QJsonDocument& config); ~ProtoServer(); /// @@ -53,6 +58,13 @@ public slots: void sendImageToProtoSlaves(int priority, const Image & image, int duration_ms); void componentStateChanged(const hyperion::Components component, bool enable); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + signals: /// /// Forwarding videoMode @@ -78,7 +90,7 @@ private: Hyperion * _hyperion; /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// List with open connections QSet _openConnections; @@ -90,6 +102,22 @@ private: /// Logger instance Logger * _log; + /// Component Register + ComponentRegister* _componentRegister; + + /// Network Origin Check + NetOrigin* _netOrigin; + + /// Service register + BonjourServiceRegister * _serviceRegister = nullptr; + /// flag if forwarder is enabled bool _forwarder_enabled; + + uint16_t _port = 0; + + /// Start server + void start(); + /// Stop server + void stop(); }; diff --git a/include/python/PythonInit.h b/include/python/PythonInit.h new file mode 100644 index 00000000..97b0cb16 --- /dev/null +++ b/include/python/PythonInit.h @@ -0,0 +1,13 @@ +#pragma once + +/// +/// @brief Handle the PythonInit, module registers and DeInit +/// +class PythonInit +{ +private: + friend class HyperionDaemon; + + PythonInit(); + ~PythonInit(); +}; diff --git a/include/python/PythonUtils.h b/include/python/PythonUtils.h new file mode 100644 index 00000000..e7cd08b4 --- /dev/null +++ b/include/python/PythonUtils.h @@ -0,0 +1,15 @@ +#pragma once + +#undef slots +#include +#define slots + +// decl +extern PyThreadState* mainThreadState; + +// py path seperator +#ifdef TARGET_WINDOWS + #define PY_PATH_SEP ";"; +#else // not windows + #define PY_PATH_SEP ":"; +#endif diff --git a/include/udplistener/UDPListener.h b/include/udplistener/UDPListener.h index 2e7c9e64..bf294df6 100644 --- a/include/udplistener/UDPListener.h +++ b/include/udplistener/UDPListener.h @@ -4,16 +4,22 @@ #include // Qt includes -#include #include #include +#include // Hyperion includes -#include #include #include +// settings +#include + +class Hyperion; class UDPClientConnection; +class BonjourServiceRegister; +class QUdpSocket; +class NetOrigin; /// /// This class creates a UDP server which accepts connections from boblight clients. @@ -28,26 +34,25 @@ public: /// @param hyperion Hyperion instance /// @param port port number on which to start listening for connections /// - UDPListener(const int priority, const int timeout, const QString& address, quint16 listenPort, bool shared); + UDPListener(const QJsonDocument& config); ~UDPListener(); /// /// @return the port number on which this UDP listens for incoming connections /// uint16_t getPort() const; - + /// /// @return true if server is active (bind to a port) /// bool active() { return _isActive; }; - bool componentState() { return active(); }; public slots: /// /// bind server to network /// void start(); - + /// /// close server /// @@ -55,8 +60,12 @@ public slots: void componentStateChanged(const hyperion::Components component, bool enable); -signals: - void statusChanged(bool isActive); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// @@ -83,12 +92,18 @@ private: /// Logger instance Logger * _log; - + + /// Bonjour Service Register + BonjourServiceRegister* _bonjourService = nullptr; + /// state of connection bool _isActive; - + /// address to bind QHostAddress _listenAddress; - quint16 _listenPort; + uint16_t _listenPort; QAbstractSocket::BindFlag _bondage; + + /// Check Network Origin + NetOrigin* _netOrigin; }; diff --git a/include/utils/Components.h b/include/utils/Components.h index 6af45394..3e917d9a 100644 --- a/include/utils/Components.h +++ b/include/utils/Components.h @@ -3,12 +3,14 @@ namespace hyperion { + /** * Enumeration of components in Hyperion. */ enum Components { COMP_INVALID, + COMP_ALL, COMP_SMOOTHING, COMP_BLACKBORDER, COMP_FORWARDER, @@ -17,6 +19,7 @@ enum Components COMP_GRABBER, COMP_V4L, COMP_COLOR, + COMP_IMAGE, COMP_EFFECT, COMP_PROTOSERVER, COMP_LEDDEVICE @@ -26,6 +29,7 @@ inline const char* componentToString(Components c) { switch (c) { + case COMP_ALL: return "Hyperion"; case COMP_SMOOTHING: return "Smoothing"; case COMP_BLACKBORDER: return "Blackborder detector"; case COMP_FORWARDER: return "Json/Proto forwarder"; @@ -35,6 +39,7 @@ inline const char* componentToString(Components c) case COMP_V4L: return "V4L capture device"; case COMP_COLOR: return "Solid color"; case COMP_EFFECT: return "Effect"; + case COMP_IMAGE: return "Image"; case COMP_PROTOSERVER: return "Proto Server"; case COMP_LEDDEVICE: return "LED device"; default: return ""; @@ -45,6 +50,7 @@ inline const char* componentToIdString(Components c) { switch (c) { + case COMP_ALL: return "ALL"; case COMP_SMOOTHING: return "SMOOTHING"; case COMP_BLACKBORDER: return "BLACKBORDER"; case COMP_FORWARDER: return "FORWARDER"; @@ -54,6 +60,7 @@ inline const char* componentToIdString(Components c) case COMP_V4L: return "V4L"; case COMP_COLOR: return "COLOR"; case COMP_EFFECT: return "EFFECT"; + case COMP_IMAGE: return "IMAGE"; case COMP_PROTOSERVER: return "PROTOSERVER"; case COMP_LEDDEVICE: return "LEDDEVICE"; default: return ""; @@ -63,6 +70,7 @@ inline const char* componentToIdString(Components c) inline Components stringToComponent(QString component) { component = component.toUpper(); + if (component == "ALL") return COMP_ALL; if (component == "SMOOTHING") return COMP_SMOOTHING; if (component == "BLACKBORDER") return COMP_BLACKBORDER; if (component == "FORWARDER") return COMP_FORWARDER; @@ -72,6 +80,7 @@ inline Components stringToComponent(QString component) if (component == "V4L") return COMP_V4L; if (component == "COLOR") return COMP_COLOR; if (component == "EFFECT") return COMP_EFFECT; + if (component == "IMAGE") return COMP_IMAGE; if (component == "PROTOSERVER") return COMP_PROTOSERVER; if (component == "LEDDEVICE") return COMP_LEDDEVICE; diff --git a/include/utils/FileUtils.h b/include/utils/FileUtils.h index 52a7e569..e0d29e13 100644 --- a/include/utils/FileUtils.h +++ b/include/utils/FileUtils.h @@ -13,6 +13,11 @@ namespace FileUtils { QString getBaseName( QString sourceFile); QString getDirName( QString sourceFile); + /// + /// @brief remove directory recursive given by path + /// @param[in] path Path to directory + bool removeDir(const QString& path, Logger* log); + /// /// @brief check if the file exists /// @param[in] path The file path to check @@ -45,9 +50,10 @@ QString getDirName( QString sourceFile); /// @brief delete a file by given path /// @param[in] path The file path to delete /// @param[in] log The logger of the caller to print errors + /// @param[in] ignError Ignore errors during file delete (no log output) /// @return true on success else false /// - bool removeFile(const QString& path, Logger* log); + bool removeFile(const QString& path, Logger* log, bool ignError=false); /// /// @brief Convert a path that may contain special placeholders diff --git a/include/utils/Image.h b/include/utils/Image.h index d2c427e5..633dddf8 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -71,6 +71,38 @@ public: memcpy(_pixels, other._pixels, other._width * other._height * sizeof(Pixel_T)); } + // Define assignment operator in terms of the copy constructor + // More to read: https://stackoverflow.com/questions/255612/dynamically-allocating-an-array-of-objects?answertab=active#tab-top + Image& operator=(Image rhs) + { + rhs.swap(*this); + return *this; + } + + void swap(Image& s) noexcept + { + using std::swap; + swap(this->_width, s._width); + swap(this->_height, s._height); + swap(this->_pixels, s._pixels); + swap(this->_endOfPixels, s._endOfPixels); + } + + // C++11 + Image(Image&& src) noexcept + : _width(0) + , _height(0) + , _pixels(NULL) + , _endOfPixels(NULL) + { + src.swap(*this); + } + Image& operator=(Image&& src) noexcept + { + src.swap(*this); + return *this; + } + /// /// Destructor /// diff --git a/include/utils/JsonProcessor.h b/include/utils/JsonProcessor.h deleted file mode 100644 index e256ff79..00000000 --- a/include/utils/JsonProcessor.h +++ /dev/null @@ -1,280 +0,0 @@ -#pragma once - -// hyperion includes -#include -#include -#include -#include - -// qt includess -#include -#include -#include - -// createEffect helper -struct find_schema: std::unary_function -{ - QString pyFile; - find_schema(QString pyFile):pyFile(pyFile) { } - bool operator()(EffectSchema const& schema) const - { - return schema.pyFile == pyFile; - } -}; - -// deleteEffect helper -struct find_effect: std::unary_function -{ - QString effectName; - find_effect(QString effectName) :effectName(effectName) { } - bool operator()(EffectDefinition const& effectDefinition) const - { - return effectDefinition.name == effectName; - } -}; - -class ImageProcessor; - -class JsonProcessor : public QObject -{ - Q_OBJECT - -public: - /// - /// Constructor - /// - /// @param peerAddress provide the Address of the peer - /// @param log The Logger class of the creator - /// @param parent Parent QObject - /// @param noListener if true, this instance won't listen for hyperion push events - /// - JsonProcessor(QString peerAddress, Logger* log, QObject* parent, bool noListener = false); - - /// - /// Handle an incoming JSON message - /// - /// @param message the incoming message as string - /// - void handleMessage(const QString & message); - - /// - /// send a forced serverinfo to a client - /// - void forceServerInfo(); - -public slots: - /// - /// @brief is called whenever the current Hyperion instance pushes new led raw values (if enabled) - /// @param ledColors The current ledColors - /// - void streamLedcolorsUpdate(const std::vector& ledColors); - - /// push images whenever hyperion emits (if enabled) - void setImage(int priority, const Image & image, int duration_ms); - - /// process and push new log messages from logger (if enabled) - void incommingLogMessage(Logger::T_LOG_MESSAGE); - -signals: - /// - /// Signal which is emitted when a sendSuccessReply() has been executed - /// - void pushReq(); - /// - /// Signal emits with the reply message provided with handleMessage() - /// - void callbackMessage(QJsonObject); - - /// - /// Signal emits whenever a jsonmessage should be forwarded - /// - void forwardJsonMessage(QJsonObject); - -private: - /// The peer address of the client - QString _peerAddress; - - /// Log instance - Logger* _log; - - /// Hyperion instance - Hyperion* _hyperion; - - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - - /// holds the state before off state - static std::map _componentsPrevState; - - /// returns if hyperion is on or off - inline bool hyperionIsActive() { return JsonProcessor::_componentsPrevState.empty(); }; - - // streaming buffers - QJsonObject _streaming_leds_reply; - QJsonObject _streaming_image_reply; - QJsonObject _streaming_logging_reply; - bool _ledcolorsLedsActive = false; - - /// flag to determine state of log streaming - bool _streaming_logging_activated; - - /// mutex to determine state of image streaming - QMutex _image_stream_mutex; - /// mutex to determine state of led color streaming - QMutex _led_stream_mutex; - - /// timeout for live video refresh - volatile qint64 _image_stream_timeout; - - /// timeout for led color refresh - volatile qint64 _led_stream_timeout; - - /// - /// Handle an incoming JSON Color message - /// - /// @param message the incoming message - /// - void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Image message - /// - /// @param message the incoming message - /// - void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message - /// - /// @param message the incoming message - /// - void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Write JSON Effect) - /// - /// @param message the incoming message - /// - void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Delete JSON Effect) - /// - /// @param message the incoming message - /// - void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON System info message - /// - /// @param message the incoming message - /// - void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Server info message - /// - /// @param message the incoming message - /// - void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clear message - /// - /// @param message the incoming message - /// - void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clearall message - /// - /// @param message the incoming message - /// - void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Adjustment message - /// - /// @param message the incoming message - /// - void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON SourceSelect message - /// - /// @param message the incoming message - /// - void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message and check subcommand - /// - /// @param message the incoming message - /// - void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleConfigGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON SetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleConfigSetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Component State message - /// - /// @param message the incoming message - /// - void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Led Colors message - /// - /// @param message the incoming message - /// - void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Logging message - /// - /// @param message the incoming message - /// - void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Proccessing message - /// - /// @param message the incoming message - /// - void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON VideoMode message - /// - /// @param message the incoming message - /// - void handleVideoModeCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON message of unknown type - /// - void handleNotImplemented(); - - /// - /// Send a standard reply indicating success - /// - void sendSuccessReply(const QString &command="", const int tan=0); - - /// - /// Send an error message back to the client - /// - /// @param error String describing the error - /// - void sendErrorReply(const QString & error, const QString &command="", const int tan=0); -}; diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h index 6187b226..4f55475d 100644 --- a/include/utils/JsonUtils.h +++ b/include/utils/JsonUtils.h @@ -26,7 +26,7 @@ namespace JsonUtils{ bool readSchema(const QString& path, QJsonObject& obj, Logger* log); /// - /// @brief parse a json QString and get the result on success + /// @brief parse a json QString and get a QJsonObject. Overloaded funtion /// @param[in] path The file path/name just used for log messages /// @param[in] data Data to parse /// @param[out] obj Retuns the parsed QJsonObject @@ -35,6 +35,26 @@ namespace JsonUtils{ /// bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log); + /// + /// @brief parse a json QString and get a QJsonArray. Overloaded function + /// @param[in] path The file path/name just used for log messages + /// @param[in] data Data to parse + /// @param[out] arr Retuns the parsed QJsonArray + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log); + + /// + /// @brief parse a json QString and get a QJsonDocument + /// @param[in] path The file path/name just used for log messages + /// @param[in] data Data to parse + /// @param[out] doc Retuns the parsed QJsonDocument + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log); + /// /// @brief Validate json data against a schema /// @param[in] file The path/name of json file just used for log messages @@ -45,6 +65,16 @@ namespace JsonUtils{ /// bool validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log); + /// + /// @brief Validate json data against a schema + /// @param[in] file The path/name of json file just used for log messages + /// @param[in] json The json data + /// @param[in] schema The schema object + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log); + /// /// @brief Write json data to file /// @param[in] filenameThe file path to write diff --git a/include/utils/hyperion.h b/include/utils/hyperion.h new file mode 100644 index 00000000..3751ec49 --- /dev/null +++ b/include/utils/hyperion.h @@ -0,0 +1,320 @@ +#pragma once + +#include +#include +#include +// fg effect +#include + +/// +/// @brief Provide utility methods for Hyperion class +/// +namespace hyperion { + + void handleInitialEffect(Hyperion* hyperion, const QJsonObject& FGEffectConfig) + { + #define FGCONFIG_ARRAY fgColorConfig.toArray() + const int FG_PRIORITY = 0; + const int DURATION_INFINITY = 0; + + // initial foreground effect/color + if (FGEffectConfig["enable"].toBool(true)) + { + const QString fgTypeConfig = FGEffectConfig["type"].toString("effect"); + const QString fgEffectConfig = FGEffectConfig["effect"].toString("Rainbow swirl fast"); + const QJsonValue fgColorConfig = FGEffectConfig["color"]; + int default_fg_duration_ms = 3000; + int fg_duration_ms = FGEffectConfig["duration_ms"].toInt(default_fg_duration_ms); + if (fg_duration_ms == DURATION_INFINITY) + { + fg_duration_ms = default_fg_duration_ms; + Warning(Logger::getInstance("HYPERION"), "foreground effect duration 'infinity' is forbidden, set to default value %d ms",default_fg_duration_ms); + } + if ( fgTypeConfig.contains("color") ) + { + ColorRgb fg_color = { + (uint8_t)FGCONFIG_ARRAY.at(0).toInt(0), + (uint8_t)FGCONFIG_ARRAY.at(1).toInt(0), + (uint8_t)FGCONFIG_ARRAY.at(2).toInt(0) + }; + hyperion->setColor(FG_PRIORITY, fg_color, fg_duration_ms); + Info(Logger::getInstance("HYPERION"),"Inital foreground color set (%d %d %d)",fg_color.red,fg_color.green,fg_color.blue); + } + else + { + int result = hyperion->setEffect(fgEffectConfig, FG_PRIORITY, fg_duration_ms); + Info(Logger::getInstance("HYPERION"),"Inital foreground effect '%s' %s", QSTRING_CSTR(fgEffectConfig), ((result == 0) ? "started" : "failed")); + } + } + #undef FGCONFIG_ARRAY + } + + ColorOrder createColorOrder(const QJsonObject &deviceConfig) + { + return stringToColorOrder(deviceConfig["colorOrder"].toString("rgb")); + } + + RgbTransform* createRgbTransform(const QJsonObject& colorConfig) + { + const double backlightThreshold = colorConfig["backlightThreshold"].toDouble(0.0); + const bool backlightColored = colorConfig["backlightColored"].toBool(false); + const double brightness = colorConfig["brightness"].toInt(100); + const double brightnessComp= colorConfig["brightnessCompensation"].toInt(100); + const double gammaR = colorConfig["gammaRed"].toDouble(1.0); + const double gammaG = colorConfig["gammaGreen"].toDouble(1.0); + const double gammaB = colorConfig["gammaBlue"].toDouble(1.0); + + RgbTransform* transform = new RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, brightness, brightnessComp); + return transform; + } + + RgbChannelAdjustment* createRgbChannelAdjustment(const QJsonObject& colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB) + { + const QJsonArray& channelConfig = colorConfig[channelName].toArray(); + RgbChannelAdjustment* adjustment = new RgbChannelAdjustment( + channelConfig[0].toInt(defaultR), + channelConfig[1].toInt(defaultG), + channelConfig[2].toInt(defaultB), + "ChannelAdjust_"+channelName.toUpper() + ); + return adjustment; + } + + ColorAdjustment * createColorAdjustment(const QJsonObject & adjustmentConfig) + { + const QString id = adjustmentConfig["id"].toString("default"); + + RgbChannelAdjustment * blackAdjustment = createRgbChannelAdjustment(adjustmentConfig, "black" , 0, 0, 0); + RgbChannelAdjustment * whiteAdjustment = createRgbChannelAdjustment(adjustmentConfig, "white" , 255,255,255); + RgbChannelAdjustment * redAdjustment = createRgbChannelAdjustment(adjustmentConfig, "red" , 255, 0, 0); + RgbChannelAdjustment * greenAdjustment = createRgbChannelAdjustment(adjustmentConfig, "green" , 0,255, 0); + RgbChannelAdjustment * blueAdjustment = createRgbChannelAdjustment(adjustmentConfig, "blue" , 0, 0,255); + RgbChannelAdjustment * cyanAdjustment = createRgbChannelAdjustment(adjustmentConfig, "cyan" , 0,255,255); + RgbChannelAdjustment * magentaAdjustment = createRgbChannelAdjustment(adjustmentConfig, "magenta", 255, 0,255); + RgbChannelAdjustment * yellowAdjustment = createRgbChannelAdjustment(adjustmentConfig, "yellow" , 255,255, 0); + RgbTransform * rgbTransform = createRgbTransform(adjustmentConfig); + + ColorAdjustment * adjustment = new ColorAdjustment(); + adjustment->_id = id; + adjustment->_rgbBlackAdjustment = *blackAdjustment; + adjustment->_rgbWhiteAdjustment = *whiteAdjustment; + adjustment->_rgbRedAdjustment = *redAdjustment; + adjustment->_rgbGreenAdjustment = *greenAdjustment; + adjustment->_rgbBlueAdjustment = *blueAdjustment; + adjustment->_rgbCyanAdjustment = *cyanAdjustment; + adjustment->_rgbMagentaAdjustment = *magentaAdjustment; + adjustment->_rgbYellowAdjustment = *yellowAdjustment; + adjustment->_rgbTransform = *rgbTransform; + + // Cleanup the allocated individual adjustments + delete blackAdjustment; + delete whiteAdjustment; + delete redAdjustment; + delete greenAdjustment; + delete blueAdjustment; + delete cyanAdjustment; + delete magentaAdjustment; + delete yellowAdjustment; + delete rgbTransform; + + return adjustment; + } + + MultiColorAdjustment * createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorConfig) + { + // Create the result, the transforms are added to this + MultiColorAdjustment * adjustment = new MultiColorAdjustment(ledCnt); + + const QJsonValue adjustmentConfig = colorConfig["channelAdjustment"]; + const QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); + + const QJsonArray & adjustmentConfigArray = adjustmentConfig.toArray(); + for (signed i = 0; i < adjustmentConfigArray.size(); ++i) + { + const QJsonObject & config = adjustmentConfigArray.at(i).toObject(); + ColorAdjustment * colorAdjustment = createColorAdjustment(config); + adjustment->addAdjustment(colorAdjustment); + + const QString ledIndicesStr = config["leds"].toString("").trimmed(); + if (ledIndicesStr.compare("*") == 0) + { + // Special case for indices '*' => all leds + adjustment->setAdjustmentForLed(colorAdjustment->_id, 0, ledCnt-1); + //Info(_log, "ColorAdjustment '%s' => [0; %d]", QSTRING_CSTR(colorAdjustment->_id), ledCnt-1); + continue; + } + + if (!overallExp.exactMatch(ledIndicesStr)) + { + //Error(_log, "Given led indices %d not correct format: %s", i, QSTRING_CSTR(ledIndicesStr)); + continue; + } + + std::stringstream ss; + const QStringList ledIndexList = ledIndicesStr.split(","); + for (int i=0; i 0) + { + ss << ", "; + } + if (ledIndexList[i].contains("-")) + { + QStringList ledIndices = ledIndexList[i].split("-"); + int startInd = ledIndices[0].toInt(); + int endInd = ledIndices[1].toInt(); + + adjustment->setAdjustmentForLed(colorAdjustment->_id, startInd, endInd); + ss << startInd << "-" << endInd; + } + else + { + int index = ledIndexList[i].toInt(); + adjustment->setAdjustmentForLed(colorAdjustment->_id, index, index); + ss << index; + } + } + //Info(_log, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); + } + + return adjustment; + } + + /** + * Construct the 'led-string' with the integration area definition per led and the color + * ordering of the RGB channels + * @param ledsConfig The configuration of the led areas + * @param deviceOrder The default RGB channel ordering + * @return The constructed ledstring + */ + LedString createLedString(const QJsonArray& ledConfigArray, const ColorOrder deviceOrder) + { + LedString ledString; + const QString deviceOrderStr = colorOrderToString(deviceOrder); + int maxLedId = ledConfigArray.size(); + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + Led led; + led.index = index["index"].toInt(); + led.clone = index["clone"].toInt(-1); + if ( led.clone < -1 || led.clone >= maxLedId ) + { + //Warning(_log, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); + led.clone = -1; + } + + if ( led.clone < 0 ) + { + const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); + const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); + led.minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); + led.maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); + led.minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); + led.maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); + // Fix if the user swapped min and max + if (led.minX_frac > led.maxX_frac) + { + std::swap(led.minX_frac, led.maxX_frac); + } + if (led.minY_frac > led.maxY_frac) + { + std::swap(led.minY_frac, led.maxY_frac); + } + + // Get the order of the rgb channels for this led (default is device order) + led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); + ledString.leds().push_back(led); + } + } + + // Make sure the leds are sorted (on their indices) + std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); + return ledString; + } + + LedString createLedStringClone(const QJsonArray& ledConfigArray, const ColorOrder deviceOrder) + { + LedString ledString; + const QString deviceOrderStr = colorOrderToString(deviceOrder); + int maxLedId = ledConfigArray.size(); + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + Led led; + led.index = index["index"].toInt(); + led.clone = index["clone"].toInt(-1); + if ( led.clone < -1 || led.clone >= maxLedId ) + { + //Warning(_log, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); + led.clone = -1; + } + + if ( led.clone >= 0 ) + { + //Debug(_log, "LED %d: clone from led %d", led.index, led.clone); + led.minX_frac = 0; + led.maxX_frac = 0; + led.minY_frac = 0; + led.maxY_frac = 0; + // Get the order of the rgb channels for this led (default is device order) + led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); + + ledString.leds().push_back(led); + } + + } + + // Make sure the leds are sorted (on their indices) + std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); + return ledString; + } + + QSize getLedLayoutGridSize(const QJsonArray& ledConfigArray) + { + std::vector midPointsX; + std::vector midPointsY; + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + if (index["clone"].toInt(-1) < 0 ) + { + const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); + const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); + double minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); + double maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); + double minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); + double maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); + // Fix if the user swapped min and max + if (minX_frac > maxX_frac) + { + std::swap(minX_frac, maxX_frac); + } + if (minY_frac > maxY_frac) + { + std::swap(minY_frac, maxY_frac); + } + + // calculate mid point and make grid calculation + midPointsX.push_back( int(1000.0*(minX_frac + maxX_frac) / 2.0) ); + midPointsY.push_back( int(1000.0*(minY_frac + maxY_frac) / 2.0) ); + } + } + + // remove duplicates + std::sort(midPointsX.begin(), midPointsX.end()); + midPointsX.erase(std::unique(midPointsX.begin(), midPointsX.end()), midPointsX.end()); + std::sort(midPointsY.begin(), midPointsY.end()); + midPointsY.erase(std::unique(midPointsY.begin(), midPointsY.end()), midPointsY.end()); + + QSize gridSize( midPointsX.size(), midPointsY.size() ); + //Debug(_log, "led layout grid: %dx%d", gridSize.width(), gridSize.height()); + + return gridSize; + } +}; diff --git a/include/utils/settings.h b/include/utils/settings.h new file mode 100644 index 00000000..7af1a262 --- /dev/null +++ b/include/utils/settings.h @@ -0,0 +1,99 @@ +#pragma once +#include +#include + +/// +/// @brief Provide util methods to work with SettingsManager class +/// +namespace settings { +// all available settings sections +enum type { + BGEFFECT, + FGEFFECT, + BLACKBORDER, + BOBLSERVER, + COLOR, + DEVICE, + EFFECTS, + NETFORWARD, + SYSTEMCAPTURE, + GENERAL, + V4L2, + JSONSERVER, + LEDCONFIG, + LEDS, + LOGGER, + PROTOSERVER, + SMOOTHING, + UDPLISTENER, + WEBSERVER, + INSTCAPTURE, + NETWORK, + INVALID +}; + +/// +/// @brief Convert settings::type to string representation +/// @param type The settings::type from enum +/// @return The settings type as string +/// +inline QString typeToString(const type& type) +{ + switch (type) + { + case BGEFFECT: return "backgroundEffect"; + case FGEFFECT: return "foregroundEffect"; + case BLACKBORDER: return "blackborderdetector"; + case BOBLSERVER: return "boblightServer"; + case COLOR: return "color"; + case DEVICE: return "device"; + case EFFECTS: return "effects"; + case NETFORWARD: return "forwarder"; + case SYSTEMCAPTURE: return "framegrabber"; + case GENERAL: return "general"; + case V4L2: return "grabberV4L2"; + case JSONSERVER: return "jsonServer"; + case LEDCONFIG: return "ledConfig"; + case LEDS: return "leds"; + case LOGGER: return "logger"; + case PROTOSERVER: return "protoServer"; + case SMOOTHING: return "smoothing"; + case UDPLISTENER: return "udpListener"; + case WEBSERVER: return "webConfig"; + case INSTCAPTURE: return "instCapture"; + case NETWORK: return "network"; + default: return "invalid"; + } +} + +/// +/// @brief Convert string to settings::type representation +/// @param type The string to convert +/// @return The settings type from enum +/// +inline type stringToType(const QString& type) +{ + if (type == "backgroundEffect") return BGEFFECT; + else if (type == "foregroundEffect") return FGEFFECT; + else if (type == "blackborderdetector") return BLACKBORDER; + else if (type == "boblightServer") return BOBLSERVER; + else if (type == "color") return COLOR; + else if (type == "device") return DEVICE; + else if (type == "effects") return EFFECTS; + else if (type == "forwarder") return NETFORWARD; + else if (type == "framegrabber") return SYSTEMCAPTURE; + else if (type == "general") return GENERAL; + else if (type == "grabberV4L2") return V4L2; + else if (type == "jsonServer") return JSONSERVER; + else if (type == "ledConfig") return LEDCONFIG; + else if (type == "leds") return LEDS; + else if (type == "logger") return LOGGER; + else if (type == "protoServer") return PROTOSERVER; + else if (type == "smoothing") return SMOOTHING; + else if (type == "udpListener") return UDPLISTENER; + else if (type == "webConfig") return WEBSERVER; + else if (type == "instCapture") return INSTCAPTURE; + else if (type == "network") return NETWORK; + else return INVALID; +} +}; diff --git a/include/webconfig/WebConfig.h b/include/webconfig/WebConfig.h deleted file mode 100644 index c9b58bfe..00000000 --- a/include/webconfig/WebConfig.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WEBCONFIG_H -#define WEBCONFIG_H - -#include -#include -#include - -class StaticFileServing; - -class WebConfig : public QObject { - Q_OBJECT - -public: - WebConfig (QObject * parent = NULL); - - virtual ~WebConfig (void); - - void start(); - void stop(); - - quint16 getPort() { return _port; }; - -private: - Hyperion* _hyperion; - QString _baseUrl; - quint16 _port; - StaticFileServing* _server; - - const QString WEBCONFIG_DEFAULT_PATH = ":/webconfig"; - const quint16 WEBCONFIG_DEFAULT_PORT = 8099; -}; - -#endif // WEBCONFIG_H - diff --git a/include/webserver/WebServer.h b/include/webserver/WebServer.h new file mode 100644 index 00000000..7d0aaf39 --- /dev/null +++ b/include/webserver/WebServer.h @@ -0,0 +1,55 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include +#include +#include + +// hyperion / utils +#include +#include + +// settings +#include + +class StaticFileServing; +class QtHttpServer; + +class WebServer : public QObject { + Q_OBJECT + +public: + WebServer (const QJsonDocument& config, QObject * parent = 0); + + virtual ~WebServer (void); + + void start(); + void stop(); + + quint16 getPort() { return _port; }; + +public slots: + void onServerStopped (void); + void onServerStarted (quint16 port); + void onServerError (QString msg); + + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + +private: + Logger* _log; + Hyperion* _hyperion; + QString _baseUrl; + quint16 _port; + StaticFileServing* _staticFileServing; + QtHttpServer* _server; + + const QString WEBSERVER_DEFAULT_PATH = ":/webconfig"; + const quint16 WEBSERVER_DEFAULT_PORT = 8090; +}; + +#endif // WEBSERVER_H diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index c2f15b5a..d2d85b23 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -15,4 +15,6 @@ add_subdirectory(leddevice) add_subdirectory(utils) add_subdirectory(effectengine) add_subdirectory(grabber) -add_subdirectory(webconfig) +add_subdirectory(webserver) +add_subdirectory(api) +add_subdirectory(python) diff --git a/libsrc/utils/JSONRPC_schema/schema-adjustment.json b/libsrc/api/JSONRPC_schema/schema-adjustment.json similarity index 92% rename from libsrc/utils/JSONRPC_schema/schema-adjustment.json rename to libsrc/api/JSONRPC_schema/schema-adjustment.json index a16796b1..c095c09c 100644 --- a/libsrc/utils/JSONRPC_schema/schema-adjustment.json +++ b/libsrc/api/JSONRPC_schema/schema-adjustment.json @@ -84,17 +84,6 @@ "minItems": 3, "maxItems": 3 }, - "black": { - "type": "array", - "required": false, - "items" : { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "minItems": 3, - "maxItems": 3 - }, "white": { "type": "array", "required": false, diff --git a/libsrc/utils/JSONRPC_schema/schema-clear.json b/libsrc/api/JSONRPC_schema/schema-clear.json similarity index 94% rename from libsrc/utils/JSONRPC_schema/schema-clear.json rename to libsrc/api/JSONRPC_schema/schema-clear.json index 9d1cb9e5..c63a77f7 100644 --- a/libsrc/utils/JSONRPC_schema/schema-clear.json +++ b/libsrc/api/JSONRPC_schema/schema-clear.json @@ -12,7 +12,7 @@ }, "priority": { "type": "integer", - "minimum" : 1, + "minimum" : -1, "maximum" : 253, "required": true } diff --git a/libsrc/utils/JSONRPC_schema/schema-color.json b/libsrc/api/JSONRPC_schema/schema-color.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-color.json rename to libsrc/api/JSONRPC_schema/schema-color.json diff --git a/libsrc/utils/JSONRPC_schema/schema-componentstate.json b/libsrc/api/JSONRPC_schema/schema-componentstate.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-componentstate.json rename to libsrc/api/JSONRPC_schema/schema-componentstate.json diff --git a/libsrc/utils/JSONRPC_schema/schema-config.json b/libsrc/api/JSONRPC_schema/schema-config.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-config.json rename to libsrc/api/JSONRPC_schema/schema-config.json diff --git a/libsrc/utils/JSONRPC_schema/schema-create-effect.json b/libsrc/api/JSONRPC_schema/schema-create-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-create-effect.json rename to libsrc/api/JSONRPC_schema/schema-create-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-delete-effect.json b/libsrc/api/JSONRPC_schema/schema-delete-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-delete-effect.json rename to libsrc/api/JSONRPC_schema/schema-delete-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-effect.json b/libsrc/api/JSONRPC_schema/schema-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-effect.json rename to libsrc/api/JSONRPC_schema/schema-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-image.json b/libsrc/api/JSONRPC_schema/schema-image.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-image.json rename to libsrc/api/JSONRPC_schema/schema-image.json diff --git a/libsrc/utils/JSONRPC_schema/schema-ledcolors.json b/libsrc/api/JSONRPC_schema/schema-ledcolors.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-ledcolors.json rename to libsrc/api/JSONRPC_schema/schema-ledcolors.json diff --git a/libsrc/utils/JSONRPC_schema/schema-logging.json b/libsrc/api/JSONRPC_schema/schema-logging.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-logging.json rename to libsrc/api/JSONRPC_schema/schema-logging.json diff --git a/libsrc/utils/JSONRPC_schema/schema-processing.json b/libsrc/api/JSONRPC_schema/schema-processing.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-processing.json rename to libsrc/api/JSONRPC_schema/schema-processing.json diff --git a/libsrc/utils/JSONRPC_schema/schema-serverinfo.json b/libsrc/api/JSONRPC_schema/schema-serverinfo.json similarity index 83% rename from libsrc/utils/JSONRPC_schema/schema-serverinfo.json rename to libsrc/api/JSONRPC_schema/schema-serverinfo.json index 869d9617..990eec04 100644 --- a/libsrc/utils/JSONRPC_schema/schema-serverinfo.json +++ b/libsrc/api/JSONRPC_schema/schema-serverinfo.json @@ -7,6 +7,9 @@ "required" : true, "enum" : ["serverinfo"] }, + "subscribe" : { + "type" : "array" + }, "tan" : { "type" : "integer" } diff --git a/libsrc/utils/JSONRPC_schema/schema-sourceselect.json b/libsrc/api/JSONRPC_schema/schema-sourceselect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-sourceselect.json rename to libsrc/api/JSONRPC_schema/schema-sourceselect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-sysinfo.json b/libsrc/api/JSONRPC_schema/schema-sysinfo.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-sysinfo.json rename to libsrc/api/JSONRPC_schema/schema-sysinfo.json diff --git a/libsrc/utils/JSONRPC_schema/schema-videomode.json b/libsrc/api/JSONRPC_schema/schema-videomode.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-videomode.json rename to libsrc/api/JSONRPC_schema/schema-videomode.json diff --git a/libsrc/utils/JSONRPC_schema/schema.json b/libsrc/api/JSONRPC_schema/schema.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema.json rename to libsrc/api/JSONRPC_schema/schema.json diff --git a/libsrc/utils/JSONRPC_schemas.qrc b/libsrc/api/JSONRPC_schemas.qrc similarity index 94% rename from libsrc/utils/JSONRPC_schemas.qrc rename to libsrc/api/JSONRPC_schemas.qrc index 1c708443..925566a1 100644 --- a/libsrc/utils/JSONRPC_schemas.qrc +++ b/libsrc/api/JSONRPC_schemas.qrc @@ -6,7 +6,6 @@ JSONRPC_schema/schema-serverinfo.json JSONRPC_schema/schema-sysinfo.json JSONRPC_schema/schema-clear.json - JSONRPC_schema/schema-clearall.json JSONRPC_schema/schema-adjustment.json JSONRPC_schema/schema-effect.json JSONRPC_schema/schema-create-effect.json diff --git a/libsrc/blackborder/BlackBorderDetector.cpp b/libsrc/blackborder/BlackBorderDetector.cpp index 8d789e31..e1a5249b 100644 --- a/libsrc/blackborder/BlackBorderDetector.cpp +++ b/libsrc/blackborder/BlackBorderDetector.cpp @@ -23,7 +23,7 @@ uint8_t BlackBorderDetector::calculateThreshold(double threshold) uint8_t blackborderThreshold = uint8_t(rgbThreshold); - Debug(Logger::getInstance("BLACKBORDER"), "threshold set to %f (%d)", threshold , int(blackborderThreshold)); + //Debug(Logger::getInstance("BLACKBORDER"), "threshold set to %f (%d)", threshold , int(blackborderThreshold)); return blackborderThreshold; } diff --git a/libsrc/blackborder/BlackBorderProcessor.cpp b/libsrc/blackborder/BlackBorderProcessor.cpp index c1c12fef..608c970a 100644 --- a/libsrc/blackborder/BlackBorderProcessor.cpp +++ b/libsrc/blackborder/BlackBorderProcessor.cpp @@ -1,32 +1,105 @@ #include -#include +#include // Blackborder includes #include - using namespace hyperion; -BlackBorderProcessor::BlackBorderProcessor(const QJsonObject &blackborderConfig) - : _enabled(blackborderConfig["enable"].toBool(true)) - , _unknownSwitchCnt(blackborderConfig["unknownFrameCnt"].toInt(600)) - , _borderSwitchCnt(blackborderConfig["borderFrameCnt"].toInt(50)) - , _maxInconsistentCnt(blackborderConfig["maxInconsistentCnt"].toInt(10)) - , _blurRemoveCnt(blackborderConfig["blurRemoveCnt"].toInt(1)) - , _detectionMode(blackborderConfig["mode"].toString("default")) - , _detector(blackborderConfig["threshold"].toDouble(5.0)/100) +BlackBorderProcessor::BlackBorderProcessor(Hyperion* hyperion, QObject* parent) + : QObject(parent) + , _hyperion(hyperion) + , _enabled(false) + , _unknownSwitchCnt(600) + , _borderSwitchCnt(50) + , _maxInconsistentCnt(10) + , _blurRemoveCnt(1) + , _detectionMode("default") + , _detector(nullptr) , _currentBorder({true, -1, -1}) , _previousDetectedBorder({true, -1, -1}) , _consistentCnt(0) , _inconsistentCnt(10) + , _oldThreshold(-0.1) + , _hardDisabled(false) + , _userEnabled(false) { - if (_enabled) + // init + handleSettingsUpdate(settings::BLACKBORDER, _hyperion->getSetting(settings::BLACKBORDER)); + + // listen for settings updates + connect(_hyperion, &Hyperion::settingsChanged, this, &BlackBorderProcessor::handleSettingsUpdate); + + // listen for component state changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &BlackBorderProcessor::componentStateChanged); +} + +BlackBorderProcessor::~BlackBorderProcessor() +{ + delete _detector; +} + +void BlackBorderProcessor::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::BLACKBORDER) { - Debug(Logger::getInstance("BLACKBORDER"), "mode: %s", QSTRING_CSTR(_detectionMode)); + const QJsonObject& obj = config.object(); + _unknownSwitchCnt = obj["unknownFrameCnt"].toInt(600); + _borderSwitchCnt = obj["borderFrameCnt"].toInt(50); + _maxInconsistentCnt = obj["maxInconsistentCnt"].toInt(10); + _blurRemoveCnt = obj["blurRemoveCnt"].toInt(1); + _detectionMode = obj["mode"].toString("default"); + + if(_oldThreshold != obj["threshold"].toDouble(5.0/100)) + { + _oldThreshold = obj["threshold"].toDouble(5.0/100); + if(_detector != nullptr) delete _detector; + _detector = new BlackBorderDetector(obj["threshold"].toDouble(5.0/100)); + } + + Debug(Logger::getInstance("BLACKBORDER"), "Set mode to: %s", QSTRING_CSTR(_detectionMode)); + + // eval the comp state + componentStateChanged(hyperion::COMP_BLACKBORDER, obj["enable"].toBool(true)); } } +void BlackBorderProcessor::componentStateChanged(const hyperion::Components component, bool enable) +{ + if(component == hyperion::COMP_BLACKBORDER) + { + _userEnabled = enable; + if(enable) + { + // eg effects and probably other components don't want a BB, mimik a wrong comp state to the comp register + if(!_hardDisabled) + _enabled = enable; + } + else + { + _enabled = enable; + } + + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BLACKBORDER, enable); + } +} + +void BlackBorderProcessor::setHardDisable(const bool& disable) { + + if (disable) + { + _enabled = false; + } + else + { + // the user has the last word to enable + if(_userEnabled) + _enabled = true; + } + _hardDisabled = disable; +}; + BlackBorder BlackBorderProcessor::getCurrentBorder() const { return _currentBorder; diff --git a/libsrc/blackborder/CMakeLists.txt b/libsrc/blackborder/CMakeLists.txt index 023ebb60..e93c5e47 100644 --- a/libsrc/blackborder/CMakeLists.txt +++ b/libsrc/blackborder/CMakeLists.txt @@ -7,4 +7,7 @@ FILE ( GLOB Blackborder_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_D add_library(blackborder ${Blackborder_SOURCES} ) -target_link_libraries(blackborder hyperion-utils ) +target_link_libraries(blackborder + hyperion-utils + hyperion +) diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index 17dc3491..9997517b 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -15,8 +15,8 @@ #include // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" +//#include "hyperion/ImageProcessorFactory.h" +//#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" #include "HyperionConfig.h" @@ -27,7 +27,7 @@ BoblightClientConnection::BoblightClientConnection(QTcpSocket *socket, const int : QObject() , _locale(QLocale::C) , _socket(socket) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) + //, _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _priority(priority) @@ -167,7 +167,7 @@ void BoblightClientConnection::handleMessage(const QString & message) // send current color values to hyperion if this is the last led assuming leds values are send in order of id if ((ledIndex == _ledColors.size() -1) && _priority < 255) { - _hyperion->setColors(_priority, _ledColors, -1, true, hyperion::COMP_BOBLIGHTSERVER, _clientAddress); + _hyperion->setInput(_priority, _ledColors); } return; @@ -205,7 +205,7 @@ void BoblightClientConnection::handleMessage(const QString & message) // send current color values to hyperion if (_priority < 255) { - _hyperion->setColors(_priority, _ledColors, -1, true, hyperion::COMP_BOBLIGHTSERVER, _clientAddress); + _hyperion->setInput(_priority, _ledColors); } return; } @@ -227,11 +227,11 @@ void BoblightClientConnection::sendLightMessage() int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount()); sendMessage(QByteArray(buffer, n)); - double h0, h1, v0, v1; + //double h0, h1, v0, v1; for (unsigned i = 0; i < _hyperion->getLedCount(); ++i) { - _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(QByteArray(buffer, n)); + //_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(QByteArray(buffer, n)); } } diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h index d4baffa6..76eb1280 100644 --- a/libsrc/boblightserver/BoblightClientConnection.h +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -9,8 +9,6 @@ #include #include -class ImageProcessor; - /// /// The Connection object created by \a BoblightServer when a new connection is establshed /// @@ -76,9 +74,6 @@ private: /// 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; @@ -90,7 +85,7 @@ private: /// The latest led color data std::vector _ledColors; - + /// logger instance Logger * _log; diff --git a/libsrc/boblightserver/BoblightServer.cpp b/libsrc/boblightserver/BoblightServer.cpp index 68a45f0c..bba2c493 100644 --- a/libsrc/boblightserver/BoblightServer.cpp +++ b/libsrc/boblightserver/BoblightServer.cpp @@ -5,20 +5,31 @@ #include #include "BoblightClientConnection.h" +// hyperion includes +#include +// qt incl +#include + using namespace hyperion; -BoblightServer::BoblightServer(const int priority, uint16_t port) +BoblightServer::BoblightServer(const QJsonDocument& config) : QObject() , _hyperion(Hyperion::getInstance()) - , _server() + , _server(new QTcpServer(this)) , _openConnections() - , _priority(priority) + , _priority(0) , _log(Logger::getInstance("BOBLIGHT")) - , _isActive(false) - , _port(port) + , _port(0) { - // Set trigger for incoming connections - connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + Debug(_log, "Instance created"); + + // listen for component change + connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); + // listen new connection signal from server + connect(_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + + // init + handleSettingsUpdate(settings::BOBLSERVER, config); } BoblightServer::~BoblightServer() @@ -28,63 +39,63 @@ BoblightServer::~BoblightServer() void BoblightServer::start() { - if ( active() ) + if ( _server->isListening() ) return; - - if (!_server.listen(QHostAddress::Any, _port)) + + if (!_server->listen(QHostAddress::Any, _port)) { - throw std::runtime_error("BOBLIGHT ERROR: server could not bind to port"); + Error(_log, "Could not bind to port '%d', please use an available port", _port); + return; } - Info(_log, "Boblight server started on port %d", _port); + Info(_log, "Started on port %d", _port); - _isActive = true; - emit statusChanged(_isActive); - - _hyperion->registerPriority("Boblight", _priority); + _hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening()); } void BoblightServer::stop() { - if ( ! active() ) + if ( ! _server->isListening() ) return; - + foreach (BoblightClientConnection * connection, _openConnections) { delete connection; } - _server.close(); - _isActive = false; - emit statusChanged(_isActive); + _server->close(); - _hyperion->unRegisterPriority("Boblight"); + Info(_log, "Stopped"); + _hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening()); +} +bool BoblightServer::active() +{ + return _server->isListening(); } void BoblightServer::componentStateChanged(const hyperion::Components component, bool enable) { if (component == COMP_BOBLIGHTSERVER) { - if (_isActive != enable) + if (_server->isListening() != enable) { if (enable) start(); else stop(); - Info(_log, "change state to %s", (_isActive ? "enabled" : "disabled") ); } - _hyperion->getComponentRegister().componentStateChanged(component, _isActive); } } uint16_t BoblightServer::getPort() const { - return _server.serverPort(); + return _server->serverPort(); } void BoblightServer::newConnection() { - QTcpSocket * socket = _server.nextPendingConnection(); + QTcpSocket * socket = _server->nextPendingConnection(); if (socket != nullptr) { Info(_log, "new connection"); + _hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(socket->peerAddress().toString())); BoblightClientConnection * connection = new BoblightClientConnection(socket, _priority); _openConnections.insert(connection); @@ -101,3 +112,16 @@ void BoblightServer::closedConnection(BoblightClientConnection *connection) // schedule to delete the connection object connection->deleteLater(); } + +void BoblightServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::BOBLSERVER) + { + QJsonObject obj = config.object(); + _port = obj["port"].toInt(); + _priority = obj["priority"].toInt(); + stop(); + if(obj["enable"].toBool()) + start(); + } +} diff --git a/libsrc/bonjour/bonjourbrowserwrapper.cpp b/libsrc/bonjour/bonjourbrowserwrapper.cpp new file mode 100644 index 00000000..d3d94bbc --- /dev/null +++ b/libsrc/bonjour/bonjourbrowserwrapper.cpp @@ -0,0 +1,81 @@ +#include + +//qt incl +#include + +// bonjour +#include +#include + +BonjourBrowserWrapper* BonjourBrowserWrapper::instance = nullptr; + +BonjourBrowserWrapper::BonjourBrowserWrapper(QObject * parent) + : QObject(parent) + , _bonjourResolver(new BonjourServiceResolver(this)) + , _timerBonjourResolver( new QTimer(this)) +{ + BonjourBrowserWrapper::instance = this; + connect(_bonjourResolver, &BonjourServiceResolver::bonjourRecordResolved, this, &BonjourBrowserWrapper::bonjourRecordResolved); + + connect(_timerBonjourResolver, &QTimer::timeout, this, &BonjourBrowserWrapper::bonjourResolve); + _timerBonjourResolver->setInterval(1000); + _timerBonjourResolver->start(); + + // browse for _hyperiond-http._tcp + browseForServiceType(QLatin1String("_hyperiond-http._tcp")); +} + +bool BonjourBrowserWrapper::browseForServiceType(const QString &serviceType) +{ + if(!_browsedServices.contains(serviceType)) + { + BonjourServiceBrowser* newBrowser = new BonjourServiceBrowser(this); + connect(newBrowser, &BonjourServiceBrowser::currentBonjourRecordsChanged, this, &BonjourBrowserWrapper::currentBonjourRecordsChanged); + newBrowser->browseForServiceType(serviceType); + _browsedServices.insert(serviceType, newBrowser); + return true; + } + return false; +} + +void BonjourBrowserWrapper::currentBonjourRecordsChanged(const QList &list) +{ + _hyperionSessions.clear(); + for ( auto rec : list ) + { + _hyperionSessions.insert(rec.serviceName, rec); + } +} + +void BonjourBrowserWrapper::bonjourRecordResolved(const QHostInfo &hostInfo, int port) +{ + if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve)) + { + QString host = hostInfo.hostName(); + QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain; + if (host.endsWith("."+domain)) + { + host.remove(host.length()-domain.length()-1,domain.length()+1); + } + _hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host; + _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 browserChange(_hyperionSessions); + } +} + +void BonjourBrowserWrapper::bonjourResolve() +{ + for(auto key : _hyperionSessions.keys()) + { + if (_hyperionSessions[key].port < 0) + { + _bonjourCurrentServiceToResolve = key; + _bonjourResolver->resolveBonjourRecord(_hyperionSessions[key]); + break; + } + } +} diff --git a/libsrc/bonjour/bonjourserviceregister.cpp b/libsrc/bonjour/bonjourserviceregister.cpp index 96c5dc7f..d3124b6f 100755 --- a/libsrc/bonjour/bonjourserviceregister.cpp +++ b/libsrc/bonjour/bonjourserviceregister.cpp @@ -30,8 +30,12 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include +#include +#include + BonjourServiceRegister::BonjourServiceRegister(QObject *parent) : QObject(parent), dnssref(0), bonjourSocket(0) @@ -48,6 +52,19 @@ BonjourServiceRegister::~BonjourServiceRegister() } } +void BonjourServiceRegister::registerService(const QString& service, const int& port) +{ + // zeroconf $configname@$hostname:port + QString prettyName = Hyperion::getInstance()->getQJsonConfig()["general"].toObject()["name"].toString(); + registerService( + BonjourRecord(prettyName+"@"+QHostInfo::localHostName()+ ":" + QString::number(port), + service, + QString() + ), + port + ); +} + void BonjourServiceRegister::registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt) { if (dnssref) @@ -61,22 +78,25 @@ void BonjourServiceRegister::registerService(const BonjourRecord &record, quint1 bigEndianPort = 0 | ((servicePort & 0x00ff) << 8) | ((servicePort & 0xff00) >> 8); } #endif - + // base txtRec + std::vector > txtBase = {{"id",Hyperion::getInstance()->getId().toStdString()},{"version",HYPERION_VERSION}}; // create txt record TXTRecordRef txtRec; TXTRecordCreate(&txtRec,0,NULL); - // add txt records - if(!txt.empty()) + if(!txt.empty()) { - for(std::vector >::const_iterator it = txt.begin(); it != txt.end(); ++it) - { - //Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str()); - uint8_t txtLen = (uint8_t)strlen(it->second.c_str()); - TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str()); - } + txtBase.insert(txtBase.end(), txt.begin(), txt.end()); + } + // add txt records + for(std::vector >::const_iterator it = txtBase.begin(); it != txtBase.end(); ++it) + { + //Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str()); + uint8_t txtLen = (uint8_t)strlen(it->second.c_str()); + TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str()); } + DNSServiceErrorType err = DNSServiceRegister(&dnssref, 0, 0, record.serviceName.toUtf8().constData(), record.registeredType.toUtf8().constData(), (record.replyDomain.isEmpty() ? 0 : record.replyDomain.toUtf8().constData()), diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt index c016b4cc..aac420f7 100644 --- a/libsrc/effectengine/CMakeLists.txt +++ b/libsrc/effectengine/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(effectengine target_link_libraries(effectengine hyperion + python Qt5::Core Qt5::Gui ${PYTHON_LIBRARIES} diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index fda2f934..7e1c52c3 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -15,87 +15,34 @@ #include // effect engin eincludes -#include "Effect.h" +#include +#include #include #include -// Python method table -PyMethodDef Effect::effectMethods[] = { - {"setColor" , Effect::wrapSetColor , METH_VARARGS, "Set a new color for the leds."}, - {"setImage" , Effect::wrapSetImage , METH_VARARGS, "Set a new image to process and determine new led colors."}, - {"getImage" , Effect::wrapGetImage , METH_VARARGS, "get image data from file."}, - {"abort" , Effect::wrapAbort , METH_NOARGS, "Check if the effect should abort execution."}, - {"imageShow" , Effect::wrapImageShow , METH_VARARGS, "set current effect image to hyperion core."}, - {"imageLinearGradient" , Effect::wrapImageLinearGradient , METH_VARARGS, ""}, - {"imageConicalGradient" , Effect::wrapImageConicalGradient , METH_VARARGS, ""}, - {"imageRadialGradient" , Effect::wrapImageRadialGradient , METH_VARARGS, ""}, - {"imageSolidFill" , Effect::wrapImageSolidFill , METH_VARARGS, ""}, - {"imageDrawLine" , Effect::wrapImageDrawLine , METH_VARARGS, ""}, - {"imageDrawPoint" , Effect::wrapImageDrawPoint , METH_VARARGS, ""}, - {"imageDrawRect" , Effect::wrapImageDrawRect , METH_VARARGS, ""}, - {"imageDrawPolygon" , Effect::wrapImageDrawPolygon , METH_VARARGS, ""}, - {"imageDrawPie" , Effect::wrapImageDrawPie , METH_VARARGS, ""}, - {"imageSetPixel" , Effect::wrapImageSetPixel , METH_VARARGS, "set pixel color of image"}, - {"imageGetPixel" , Effect::wrapImageGetPixel , METH_VARARGS, "get pixel color of image"}, - {"imageSave" , Effect::wrapImageSave , METH_NOARGS, "adds a new background image"}, - {"imageMinSize" , Effect::wrapImageMinSize , METH_VARARGS, "sets minimal dimension of background image"}, - {"imageWidth" , Effect::wrapImageWidth , METH_NOARGS, "gets image width"}, - {"imageHeight" , Effect::wrapImageHeight , METH_NOARGS, "gets image height"}, - {"imageCRotate" , Effect::wrapImageCRotate , METH_VARARGS, "rotate the coordinate system by given angle"}, - {"imageCOffset" , Effect::wrapImageCOffset , METH_VARARGS, "Add offset to the coordinate system"}, - {"imageCShear" , Effect::wrapImageCShear , METH_VARARGS, "Shear of coordinate system by the given horizontal/vertical axis"}, - {"imageResetT" , Effect::wrapImageResetT , METH_NOARGS, "Resets all coords modifications (rotate,offset,shear)"}, - {NULL, NULL, 0, NULL} -}; +// python utils/ global mainthread +#include +//impl +PyThreadState* mainThreadState; - -// create the hyperion module -struct PyModuleDef Effect::moduleDef = { - PyModuleDef_HEAD_INIT, - "hyperion", /* m_name */ - "Hyperion module", /* m_doc */ - -1, /* m_size */ - Effect::effectMethods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -PyObject* Effect::PyInit_hyperion() -{ - return PyModule_Create(&moduleDef); -} - -void Effect::registerHyperionExtensionModule() -{ - PyImport_AppendInittab("hyperion", &PyInit_hyperion); -} - -Effect::Effect(PyThreadState* mainThreadState, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args, const QString & origin, unsigned smoothCfg) +Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args) : QThread() - , _mainThreadState(mainThreadState) + , _hyperion(hyperion) , _priority(priority) , _timeout(timeout) , _script(script) , _name(name) - , _smoothCfg(smoothCfg) , _args(args) , _endTime(-1) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _colors() - , _origin(origin) - , _imageSize(Hyperion::getInstance()->getLedGridSize()) + , _imageSize(hyperion->getLedGridSize()) , _image(_imageSize,QImage::Format_ARGB32_Premultiplied) { - _colors.resize(_imageProcessor->getLedCount()); + _colors.resize(_hyperion->getLedCount()); _colors.fill(ColorRgb::BLACK); _log = Logger::getInstance("EFFECTENGINE"); - // disable the black border detector for effects - _imageProcessor->enableBlackBorderDetector(false); - // init effect image for image based effects, size is based on led layout _image.fill(Qt::black); _painter = new QPainter(&_image); @@ -112,7 +59,7 @@ Effect::~Effect() void Effect::run() { // get global lock - PyEval_RestoreThread(_mainThreadState); + PyEval_RestoreThread(mainThreadState); // Initialize a new thread state PyThreadState* tstate = Py_NewInterpreter(); @@ -131,13 +78,13 @@ void Effect::run() PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr)); // add ledCount variable to the interpreter - PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount())); + PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _hyperion->getLedCount())); // add minimumWriteTime variable to the interpreter PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", Hyperion::getInstance()->getLatchTime())); // add a args variable to the interpreter - PyObject_SetAttrString(module, "args", json2python(_args)); + PyObject_SetAttrString(module, "args", EffectModule::json2python(_args)); // decref the module Py_XDECREF(module); @@ -286,1024 +233,3 @@ void Effect::run() Py_EndInterpreter(tstate); PyEval_ReleaseLock(); } - -PyObject *Effect::json2python(const QJsonValue &jsonData) const -{ - switch (jsonData.type()) - { - case QJsonValue::Null: - return Py_BuildValue(""); - case QJsonValue::Undefined: - return Py_BuildValue(""); - case QJsonValue::Double: - { - if (std::rint(jsonData.toDouble()) != jsonData.toDouble()) - { - return Py_BuildValue("d", jsonData.toDouble()); - } - return Py_BuildValue("i", jsonData.toInt()); - } - case QJsonValue::Bool: - return Py_BuildValue("i", jsonData.toBool() ? 1 : 0); - case QJsonValue::String: - return Py_BuildValue("s", jsonData.toString().toUtf8().constData()); - case QJsonValue::Object: - { - PyObject * dict= PyDict_New(); - QJsonObject objectData = jsonData.toObject(); - for (QJsonObject::iterator i = objectData.begin(); i != objectData.end(); ++i) - { - PyObject * obj = json2python(*i); - PyDict_SetItemString(dict, i.key().toStdString().c_str(), obj); - Py_XDECREF(obj); - } - return dict; - } - case QJsonValue::Array: - { - QJsonArray arrayData = jsonData.toArray(); - PyObject * list = PyList_New(arrayData.size()); - int index = 0; - for (QJsonArray::iterator i = arrayData.begin(); i != arrayData.end(); ++i, ++index) - { - PyObject * obj = json2python(*i); - Py_INCREF(obj); - PyList_SetItem(list, index, obj); - Py_XDECREF(obj); - } - return list; - } - } - - assert(false); - return nullptr; -} - -PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) -{ - // get the effect - Effect * effect = getEffect(); - - // check if we have aborted already - if (effect->isInterruptionRequested()) - { - return Py_BuildValue(""); - } - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - // check the number of arguments - int argCount = PyTuple_Size(args); - if (argCount == 3) - { - // three seperate arguments for red, green, and blue - ColorRgb color; - if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) - { - effect->_colors.fill(color); - effect->setColors(effect->_priority, effect->_colors.toStdVector(), timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - return nullptr; - } - else if (argCount == 1) - { - // bytearray of values - PyObject * bytearray = nullptr; - if (PyArg_ParseTuple(args, "O", &bytearray)) - { - if (PyByteArray_Check(bytearray)) - { - size_t length = PyByteArray_Size(bytearray); - if (length == 3 * effect->_imageProcessor->getLedCount()) - { - char * data = PyByteArray_AS_STRING(bytearray); - memcpy(effect->_colors.data(), data, length); - effect->setColors(effect->_priority, effect->_colors.toStdVector(), timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument is not a bytearray"); - return nullptr; - } - } - else - { - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Function expect 1 or 3 arguments"); - return nullptr; - } - - // error - PyErr_SetString(PyExc_RuntimeError, "Unknown error"); - return nullptr; -} - -PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) -{ - // get the effect - Effect * effect = getEffect(); - - // check if we have aborted already - if (effect->isInterruptionRequested()) - { - return Py_BuildValue(""); - } - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - // bytearray of values - int width, height; - PyObject * bytearray = nullptr; - if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray)) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length == 3 * width * height) - { - Image image(width, height); - char * data = PyByteArray_AS_STRING(bytearray); - memcpy(image.memptr(), data, length); - std::vector v = effect->_colors.toStdVector(); - effect->_imageProcessor->process(image, v); - effect->setColors(effect->_priority, v, timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*width*height"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 3 is not a bytearray"); - return nullptr; - } - } - else - { - return nullptr; - } - - // error - PyErr_SetString(PyExc_RuntimeError, "Unknown error"); - return nullptr; -} - -PyObject* Effect::wrapGetImage(PyObject *self, PyObject *args) -{ - Q_INIT_RESOURCE(EffectEngine); - - char *source; - if(!PyArg_ParseTuple(args, "s", &source)) - { - PyErr_SetString(PyExc_TypeError, "String required"); - return NULL; - } - - QString file = QString::fromUtf8(source); - - if (file.mid(0, 1) == ":") - file = ":/effects/"+file.mid(1); - - QImageReader reader(file); - - if (reader.canRead()) - { - PyObject* result = PyList_New(reader.imageCount()); - - for (int i = 0; i < reader.imageCount(); ++i) - { - reader.jumpToImage(i); - if (reader.canRead()) - { - QImage qimage = reader.read(); - - int width = qimage.width(); - int height = qimage.height(); - - QByteArray binaryImage; - for (int i = 0; i(qimage.scanLine(i)); - for (int j = 0; j< width; ++j) - { - binaryImage.append((char) qRed(scanline[j])); - binaryImage.append((char) qGreen(scanline[j])); - binaryImage.append((char) qBlue(scanline[j])); - } - } - PyList_SET_ITEM(result, i, Py_BuildValue("{s:i,s:i,s:O}", "imageWidth", width, "imageHeight", height, "imageData", PyByteArray_FromStringAndSize(binaryImage.constData(),binaryImage.size()))); - } - else - { - PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); - return NULL; - } - } - return result; - } - else - { - PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); - return NULL; - } -} - -PyObject* Effect::wrapAbort(PyObject *self, PyObject *) -{ - Effect * effect = getEffect(); - - // Test if the effect has reached it end time - if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) - { - effect->requestInterruption(); - } - - return Py_BuildValue("i", effect->isInterruptionRequested() ? 1 : 0); -} - - -PyObject* Effect::wrapImageShow(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - int argCount = PyTuple_Size(args); - int imgId = -1; - bool argsOk = (argCount == 0); - if (argCount == 1 && PyArg_ParseTuple(args, "i", &imgId)) - { - argsOk = true; - } - - if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size())) - { - return nullptr; - } - - - QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]); - int width = qimage->width(); - int height = qimage->height(); - - Image image(width, height); - QByteArray binaryImage; - - for (int i = 0; i(qimage->scanLine(i)); - for (int j = 0; j< width; ++j) - { - binaryImage.append((char) qRed(scanline[j])); - binaryImage.append((char) qGreen(scanline[j])); - binaryImage.append((char) qBlue(scanline[j])); - } - } - - memcpy(image.memptr(), binaryImage.data(), binaryImage.size()); - std::vector v = effect->_colors.toStdVector(); - effect->_imageProcessor->process(image, v); - effect->setColors(effect->_priority, v, timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - - return Py_BuildValue(""); -} - -PyObject* Effect::wrapImageLinearGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int startRX = 0; - int startRY = 0; - int startX = 0; - int startY = 0; - int endX, width = effect->_imageSize.width(); - int endY, height = effect->_imageSize.height(); - int spread = 0; - - bool argsOK = false; - - if ( argCount == 10 && PyArg_ParseTuple(args, "iiiiiiiiOi", &startRX, &startRY, &width, &height, &startX, &startY, &endX, &endY, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiOi", &startX, &startY, &endX, &endY, &bytearray, &spread) ) - { - argsOK = true; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - const int length = PyByteArray_Size(bytearray); - const unsigned arrayItemLength = 5; - if (length % arrayItemLength == 0) - { - QRect myQRect(startRX,startRY,width,height); - QLinearGradient gradient(QPoint(startX,startY), QPoint(endX,endY)); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx(spread)); - effect->_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "No bytearray properly defined"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageConicalGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int centerX, centerY, angle; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, ¢erX, ¢erY, &angle, &bytearray) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", ¢erX, ¢erY, &angle, &bytearray) ) - { - argsOK = true; - } - angle = qMax(qMin(angle,360),0); - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - const int length = PyByteArray_Size(bytearray); - const unsigned arrayItemLength = 5; - if (length % arrayItemLength == 0) - { - QRect myQRect(startX,startY,width,height); - QConicalGradient gradient(QPoint(centerX,centerY), angle ); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 8 is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - - -PyObject* Effect::wrapImageRadialGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int centerX, centerY, radius, focalX, focalY, focalRadius, spread; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 12 && PyArg_ParseTuple(args, "iiiiiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &bytearray, &spread) ) - { - argsOK = true; - focalX = centerX; - focalY = centerY; - focalRadius = radius; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiOi", ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 5 && PyArg_ParseTuple(args, "iiiOi", ¢erX, ¢erY, &radius, &bytearray, &spread) ) - { - argsOK = true; - focalX = centerX; - focalY = centerY; - focalRadius = radius; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 4 == 0) - { - - QRect myQRect(startX,startY,width,height); - QRadialGradient gradient(QPoint(centerX,centerY), qMax(radius,0) ); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx(spread)); - effect->_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPolygon(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - PyObject * bytearray = nullptr; - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - - bool argsOK = false; - - if ( argCount == 5 && PyArg_ParseTuple(args, "Oiiii", &bytearray, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "Oiii", &bytearray, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 2 == 0) - { - QVector points; - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx_painter; - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - painter->setPen(newPen); - painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); - painter->drawPolygon(points); - painter->setPen(oldPen); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 2"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 1 is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPie(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - PyObject * bytearray = nullptr; - - QString brush; - int argCount = PyTuple_Size(args); - int radius, centerX, centerY; - int startAngle = 0; - int spanAngle = 360; - int r = 0; - int g = 0; - int b = 0; - int a = 255; - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b) ) - { - argsOK = true; - } - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiisO", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &brush, &bytearray) ) - { - argsOK = true; - } - if ( argCount == 5 && PyArg_ParseTuple(args, "iiisO", ¢erX, ¢erY, &radius, &brush, &bytearray) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - startAngle = qMax(qMin(startAngle,360),0); - spanAngle = qMax(qMin(spanAngle,360),-360); - - if( argCount == 7 || argCount == 5 ) - { - a = 0; - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 5 == 0) - { - - QConicalGradient gradient(QPoint(centerX,centerY), startAngle); - - - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idxsetBrush(gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); - return nullptr; - } - } - else - { - painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); - } - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - painter->setPen(newPen); - painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16); - painter->setPen(oldPen); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageSolidFill(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &startX, &startY, &width, &height, &r, &g, &b) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "iiii",&r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 3 && PyArg_ParseTuple(args, "iii",&r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QRect myQRect(startX,startY,width,height); - effect->_painter->fillRect(myQRect, QColor(r,g,b,a)); - return Py_BuildValue(""); - } - return nullptr; -} - - -PyObject* Effect::wrapImageDrawLine(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int thick = 1; - int endX = effect->_imageSize.width(); - int endY = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QRect myQRect(startX, startY, endX, endY); - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawLine(startX, startY, endX, endY); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPoint(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b, x, y; - int a = 255; - int thick = 1; - - bool argsOK = false; - - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &x, &y, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiii", &x, &y, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawPoint(x, y); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawRect(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int thick = 1; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QRect myQRect(startX,startY,width,height); - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawRect(startX, startY, width, height); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - - -PyObject* Effect::wrapImageSetPixel(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b, x, y; - - if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) ) - { - effect->_image.setPixel(x,y,qRgb(r,g,b)); - return Py_BuildValue(""); - } - - return nullptr; -} - - -PyObject* Effect::wrapImageGetPixel(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int x, y; - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) ) - { - QRgb rgb = effect->_image.pixel(x,y); - return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb)); - } - return nullptr; -} - -PyObject* Effect::wrapImageSave(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - QImage img(effect->_image.copy()); - effect->_imageStack.append(img); - - return Py_BuildValue("i", effect->_imageStack.size()-1); -} - -PyObject* Effect::wrapImageMinSize(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int w, h; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) ) - { - if (width_painter; - - effect->_image = effect->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - effect->_imageSize = effect->_image.size(); - effect->_painter = new QPainter(&(effect->_image)); - } - return Py_BuildValue("ii", effect->_image.width(), effect->_image.height()); - } - return nullptr; -} - -PyObject* Effect::wrapImageWidth(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - return Py_BuildValue("i", effect->_imageSize.width()); -} - -PyObject* Effect::wrapImageHeight(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - return Py_BuildValue("i", effect->_imageSize.height()); -} - -PyObject* Effect::wrapImageCRotate(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int angle; - - if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) ) - { - angle = qMax(qMin(angle,360),0); - effect->_painter->rotate(angle); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageCOffset(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int offsetX = 0; - int offsetY = 0; - int argCount = PyTuple_Size(args); - - if ( argCount == 2 ) - { - PyArg_ParseTuple(args, "ii", &offsetX, &offsetY ); - } - - effect->_painter->translate(QPoint(offsetX,offsetY)); - return Py_BuildValue(""); -} - -PyObject* Effect::wrapImageCShear(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int sh,sv; - int argCount = PyTuple_Size(args); - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv )) - { - effect->_painter->shear(sh,sv); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageResetT(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - effect->_painter->resetTransform(); - return Py_BuildValue(""); -} - -Effect * Effect::getEffect() -{ - // extract the module from the runtime - PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); - - if (!PyModule_Check(module)) - { - // something is wrong - Py_XDECREF(module); - Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); - return nullptr; - } - - // retrieve the capsule with the effect - PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); - Py_XDECREF(module); - - if (!PyCapsule_CheckExact(effectCapsule)) - { - // something is wrong - Py_XDECREF(effectCapsule); - Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); - return nullptr; - } - - // Get the effect from the capsule - Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); - Py_XDECREF(effectCapsule); - return effect; -} diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index bfa35c31..26bed8a7 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -16,7 +16,8 @@ // effect engine includes #include -#include "Effect.h" +#include +#include #include "HyperionConfig.h" EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectConfig) @@ -25,11 +26,11 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectCo , _availableEffects() , _activeEffects() , _log(Logger::getInstance("EFFECTENGINE")) - , _mainThreadState(nullptr) { Q_INIT_RESOURCE(EffectEngine); qRegisterMetaType>("std::vector"); + qRegisterMetaType>("Image"); qRegisterMetaType("hyperion::Components"); // connect the Hyperion channel clear feedback @@ -38,21 +39,10 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectCo // read all effects readEffects(); - - // initialize the python interpreter - Debug(_log, "Initializing Python interpreter"); - Effect::registerHyperionExtensionModule(); - Py_InitializeEx(0); - PyEval_InitThreads(); // Create the GIL - _mainThreadState = PyEval_SaveThread(); } EffectEngine::~EffectEngine() { - // clean up the Python interpreter - Debug(_log, "Cleaning up Python interpreter"); - PyEval_RestoreThread(_mainThreadState); - Py_Finalize(); } const std::list &EffectEngine::getActiveEffects() @@ -73,6 +63,33 @@ const std::list &EffectEngine::getActiveEffects() return _availableActiveEffects; } +void EffectEngine::cacheRunningEffects() +{ + _cachedActiveEffects.clear(); + + for (Effect * effect : _activeEffects) + { + ActiveEffectDefinition activeEffectDefinition; + activeEffectDefinition.script = effect->getScript(); + activeEffectDefinition.name = effect->getName(); + activeEffectDefinition.priority = effect->getPriority(); + activeEffectDefinition.timeout = effect->getTimeout(); + activeEffectDefinition.args = effect->getArgs(); + _cachedActiveEffects.push_back(activeEffectDefinition); + channelCleared(effect->getPriority()); + } +} + +void EffectEngine::startCachedEffects() +{ + for (const auto & def : _cachedActiveEffects) + { + // the smooth cfg AND origin are ignored for this start! + runEffect(def.name, def.args, def.priority, def.timeout, def.script); + } + _cachedActiveEffects.clear(); +} + bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effectConfigFile, EffectDefinition & effectDefinition) { QString fileName = path + QDir::separator() + effectConfigFile; @@ -241,6 +258,8 @@ void EffectEngine::readEffects() } ErrorIf(_availableEffects.size()==0, _log, "no effects found, check your effect directories"); + + emit effectListUpdated(); } int EffectEngine::runEffect(const QString &effectName, int priority, int timeout, const QString &origin) @@ -266,7 +285,7 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, if (effectDefinition == nullptr) { // no such effect - Error(_log, "effect %s not found", QSTRING_CSTR(effectName)); + Error(_log, "Effect %s not found", QSTRING_CSTR(effectName)); return -1; } @@ -281,13 +300,14 @@ int EffectEngine::runEffectScript(const QString &script, const QString &name, co channelCleared(priority); // create the effect - Effect * effect = new Effect(_mainThreadState, priority, timeout, script, name, args, origin, smoothCfg); - connect(effect, SIGNAL(setColors(int,std::vector,int,bool,hyperion::Components,const QString,unsigned)), _hyperion, SLOT(setColors(int,std::vector,int,bool,hyperion::Components,const QString,unsigned)), Qt::QueuedConnection); + Effect * effect = new Effect(_hyperion, priority, timeout, script, name, args); + connect(effect, &Effect::setInput, _hyperion, &Hyperion::setInput, Qt::QueuedConnection); + connect(effect, &Effect::setInputImage, _hyperion, &Hyperion::setInputImage, Qt::QueuedConnection); connect(effect, &QThread::finished, this, &EffectEngine::effectFinished); _activeEffects.push_back(effect); // start the effect - _hyperion->registerPriority(name, priority); + _hyperion->registerInput(priority, hyperion::COMP_EFFECT, origin, name ,smoothCfg); effect->start(); return 0; @@ -299,7 +319,7 @@ void EffectEngine::channelCleared(int priority) { if (effect->getPriority() == priority) { - effect->requestInterruption(); + effect->setInteruptionFlag(); } } } @@ -310,7 +330,7 @@ void EffectEngine::allChannelsCleared() { if (effect->getPriority() != 254) { - effect->requestInterruption(); + effect->setInteruptionFlag(); } } } @@ -318,7 +338,7 @@ void EffectEngine::allChannelsCleared() void EffectEngine::effectFinished() { Effect* effect = qobject_cast(sender()); - if (!effect->isInterruptionRequested()) + if (!effect->hasInteruptionFlag()) { // effect stopped by itself. Clear the channel _hyperion->clear(effect->getPriority()); @@ -336,5 +356,4 @@ void EffectEngine::effectFinished() // cleanup the effect effect->deleteLater(); - _hyperion->unRegisterPriority(effect->getName()); } diff --git a/libsrc/effectengine/EffectModule.cpp b/libsrc/effectengine/EffectModule.cpp new file mode 100644 index 00000000..65166c19 --- /dev/null +++ b/libsrc/effectengine/EffectModule.cpp @@ -0,0 +1,1077 @@ + +#include +#include + +// hyperion +#include +#include + +// qt +#include +#include +#include + +// create the hyperion module +struct PyModuleDef EffectModule::moduleDef = { + PyModuleDef_HEAD_INIT, + "hyperion", /* m_name */ + "Hyperion module", /* m_doc */ + -1, /* m_size */ + EffectModule::effectMethods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyObject* EffectModule::PyInit_hyperion() +{ + return PyModule_Create(&moduleDef); +} + +void EffectModule::registerHyperionExtensionModule() +{ + PyImport_AppendInittab("hyperion", &PyInit_hyperion); +} + +PyObject *EffectModule::json2python(const QJsonValue &jsonData) +{ + switch (jsonData.type()) + { + case QJsonValue::Null: + return Py_BuildValue(""); + case QJsonValue::Undefined: + return Py_BuildValue(""); + case QJsonValue::Double: + { + if (std::rint(jsonData.toDouble()) != jsonData.toDouble()) + { + return Py_BuildValue("d", jsonData.toDouble()); + } + return Py_BuildValue("i", jsonData.toInt()); + } + case QJsonValue::Bool: + return Py_BuildValue("i", jsonData.toBool() ? 1 : 0); + case QJsonValue::String: + return Py_BuildValue("s", jsonData.toString().toUtf8().constData()); + case QJsonValue::Object: + { + PyObject * dict= PyDict_New(); + QJsonObject objectData = jsonData.toObject(); + for (QJsonObject::iterator i = objectData.begin(); i != objectData.end(); ++i) + { + PyObject * obj = json2python(*i); + PyDict_SetItemString(dict, i.key().toStdString().c_str(), obj); + Py_XDECREF(obj); + } + return dict; + } + case QJsonValue::Array: + { + QJsonArray arrayData = jsonData.toArray(); + PyObject * list = PyList_New(arrayData.size()); + int index = 0; + for (QJsonArray::iterator i = arrayData.begin(); i != arrayData.end(); ++i, ++index) + { + PyObject * obj = json2python(*i); + Py_INCREF(obj); + PyList_SetItem(list, index, obj); + Py_XDECREF(obj); + } + return list; + } + } + + assert(false); + return nullptr; +} + +// Python method table +PyMethodDef EffectModule::effectMethods[] = { + {"setColor" , EffectModule::wrapSetColor , METH_VARARGS, "Set a new color for the leds."}, + {"setImage" , EffectModule::wrapSetImage , METH_VARARGS, "Set a new image to process and determine new led colors."}, + {"getImage" , EffectModule::wrapGetImage , METH_VARARGS, "get image data from file."}, + {"abort" , EffectModule::wrapAbort , METH_NOARGS, "Check if the effect should abort execution."}, + {"imageShow" , EffectModule::wrapImageShow , METH_VARARGS, "set current effect image to hyperion core."}, + {"imageLinearGradient" , EffectModule::wrapImageLinearGradient , METH_VARARGS, ""}, + {"imageConicalGradient" , EffectModule::wrapImageConicalGradient , METH_VARARGS, ""}, + {"imageRadialGradient" , EffectModule::wrapImageRadialGradient , METH_VARARGS, ""}, + {"imageSolidFill" , EffectModule::wrapImageSolidFill , METH_VARARGS, ""}, + {"imageDrawLine" , EffectModule::wrapImageDrawLine , METH_VARARGS, ""}, + {"imageDrawPoint" , EffectModule::wrapImageDrawPoint , METH_VARARGS, ""}, + {"imageDrawRect" , EffectModule::wrapImageDrawRect , METH_VARARGS, ""}, + {"imageDrawPolygon" , EffectModule::wrapImageDrawPolygon , METH_VARARGS, ""}, + {"imageDrawPie" , EffectModule::wrapImageDrawPie , METH_VARARGS, ""}, + {"imageSetPixel" , EffectModule::wrapImageSetPixel , METH_VARARGS, "set pixel color of image"}, + {"imageGetPixel" , EffectModule::wrapImageGetPixel , METH_VARARGS, "get pixel color of image"}, + {"imageSave" , EffectModule::wrapImageSave , METH_NOARGS, "adds a new background image"}, + {"imageMinSize" , EffectModule::wrapImageMinSize , METH_VARARGS, "sets minimal dimension of background image"}, + {"imageWidth" , EffectModule::wrapImageWidth , METH_NOARGS, "gets image width"}, + {"imageHeight" , EffectModule::wrapImageHeight , METH_NOARGS, "gets image height"}, + {"imageCRotate" , EffectModule::wrapImageCRotate , METH_VARARGS, "rotate the coordinate system by given angle"}, + {"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)"}, + {NULL, NULL, 0, NULL} +}; + +PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(); + + // check if we have aborted already + if (effect->hasInteruptionFlag()) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // check the number of arguments + int argCount = PyTuple_Size(args); + if (argCount == 3) + { + // three seperate arguments for red, green, and blue + ColorRgb color; + if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) + { + effect->_colors.fill(color); + effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false); + return Py_BuildValue(""); + } + return nullptr; + } + else if (argCount == 1) + { + // bytearray of values + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "O", &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + size_t length = PyByteArray_Size(bytearray); + if (length == 3 * effect->_hyperion->getLedCount()) + { + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(effect->_colors.data(), data, length); + effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Function expect 1 or 3 arguments"); + return nullptr; + } +} + +PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(); + + // check if we have aborted already + if (effect->hasInteruptionFlag()) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // bytearray of values + int width, height; + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length == 3 * width * height) + { + Image image(width, height); + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(image.memptr(), data, length); + effect->setInputImage(effect->_priority, image, timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*width*height"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 3 is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + + // error + PyErr_SetString(PyExc_RuntimeError, "Unknown error"); + return nullptr; +} + +PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args) +{ + Q_INIT_RESOURCE(EffectEngine); + + char *source; + if(!PyArg_ParseTuple(args, "s", &source)) + { + PyErr_SetString(PyExc_TypeError, "String required"); + return NULL; + } + + QString file = QString::fromUtf8(source); + + if (file.mid(0, 1) == ":") + file = ":/effects/"+file.mid(1); + + QImageReader reader(file); + + if (reader.canRead()) + { + PyObject* result = PyList_New(reader.imageCount()); + + for (int i = 0; i < reader.imageCount(); ++i) + { + reader.jumpToImage(i); + if (reader.canRead()) + { + QImage qimage = reader.read(); + + int width = qimage.width(); + int height = qimage.height(); + + QByteArray binaryImage; + for (int i = 0; i(qimage.scanLine(i)); + for (int j = 0; j< width; ++j) + { + binaryImage.append((char) qRed(scanline[j])); + binaryImage.append((char) qGreen(scanline[j])); + binaryImage.append((char) qBlue(scanline[j])); + } + } + PyList_SET_ITEM(result, i, Py_BuildValue("{s:i,s:i,s:O}", "imageWidth", width, "imageHeight", height, "imageData", PyByteArray_FromStringAndSize(binaryImage.constData(),binaryImage.size()))); + } + else + { + PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); + return NULL; + } + } + return result; + } + else + { + PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); + return NULL; + } +} + +PyObject* EffectModule::wrapAbort(PyObject *self, PyObject *) +{ + Effect * effect = getEffect(); + + // Test if the effect has reached it end time + if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) + { + effect->setInteruptionFlag(); + } + + return Py_BuildValue("i", effect->hasInteruptionFlag() ? 1 : 0); +} + + +PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + int argCount = PyTuple_Size(args); + int imgId = -1; + bool argsOk = (argCount == 0); + if (argCount == 1 && PyArg_ParseTuple(args, "i", &imgId)) + { + argsOk = true; + } + + if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size())) + { + return nullptr; + } + + + QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]); + int width = qimage->width(); + int height = qimage->height(); + + Image image(width, height); + QByteArray binaryImage; + + for (int i = 0; i(qimage->scanLine(i)); + for (int j = 0; j< width; ++j) + { + binaryImage.append((char) qRed(scanline[j])); + binaryImage.append((char) qGreen(scanline[j])); + binaryImage.append((char) qBlue(scanline[j])); + } + } + + memcpy(image.memptr(), binaryImage.data(), binaryImage.size()); + effect->setInputImage(effect->_priority, image, timeout, false); + + return Py_BuildValue(""); +} + +PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int startRX = 0; + int startRY = 0; + int startX = 0; + int startY = 0; + int endX, width = effect->_imageSize.width(); + int endY, height = effect->_imageSize.height(); + int spread = 0; + + bool argsOK = false; + + if ( argCount == 10 && PyArg_ParseTuple(args, "iiiiiiiiOi", &startRX, &startRY, &width, &height, &startX, &startY, &endX, &endY, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiOi", &startX, &startY, &endX, &endY, &bytearray, &spread) ) + { + argsOK = true; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + const int length = PyByteArray_Size(bytearray); + const unsigned arrayItemLength = 5; + if (length % arrayItemLength == 0) + { + QRect myQRect(startRX,startRY,width,height); + QLinearGradient gradient(QPoint(startX,startY), QPoint(endX,endY)); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx(spread)); + effect->_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "No bytearray properly defined"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int centerX, centerY, angle; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, ¢erX, ¢erY, &angle, &bytearray) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", ¢erX, ¢erY, &angle, &bytearray) ) + { + argsOK = true; + } + angle = qMax(qMin(angle,360),0); + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + const int length = PyByteArray_Size(bytearray); + const unsigned arrayItemLength = 5; + if (length % arrayItemLength == 0) + { + QRect myQRect(startX,startY,width,height); + QConicalGradient gradient(QPoint(centerX,centerY), angle ); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 8 is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int centerX, centerY, radius, focalX, focalY, focalRadius, spread; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 12 && PyArg_ParseTuple(args, "iiiiiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &bytearray, &spread) ) + { + argsOK = true; + focalX = centerX; + focalY = centerY; + focalRadius = radius; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiOi", ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 5 && PyArg_ParseTuple(args, "iiiOi", ¢erX, ¢erY, &radius, &bytearray, &spread) ) + { + argsOK = true; + focalX = centerX; + focalY = centerY; + focalRadius = radius; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 4 == 0) + { + + QRect myQRect(startX,startY,width,height); + QRadialGradient gradient(QPoint(centerX,centerY), qMax(radius,0) ); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx(spread)); + effect->_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + PyObject * bytearray = nullptr; + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + + bool argsOK = false; + + if ( argCount == 5 && PyArg_ParseTuple(args, "Oiiii", &bytearray, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "Oiii", &bytearray, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 2 == 0) + { + QVector points; + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx_painter; + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + painter->setPen(newPen); + painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); + painter->drawPolygon(points); + painter->setPen(oldPen); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 2"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 1 is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + PyObject * bytearray = nullptr; + + QString brush; + int argCount = PyTuple_Size(args); + int radius, centerX, centerY; + int startAngle = 0; + int spanAngle = 360; + int r = 0; + int g = 0; + int b = 0; + int a = 255; + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b) ) + { + argsOK = true; + } + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiisO", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &brush, &bytearray) ) + { + argsOK = true; + } + if ( argCount == 5 && PyArg_ParseTuple(args, "iiisO", ¢erX, ¢erY, &radius, &brush, &bytearray) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + startAngle = qMax(qMin(startAngle,360),0); + spanAngle = qMax(qMin(spanAngle,360),-360); + + if( argCount == 7 || argCount == 5 ) + { + a = 0; + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 5 == 0) + { + + QConicalGradient gradient(QPoint(centerX,centerY), startAngle); + + + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idxsetBrush(gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); + return nullptr; + } + } + else + { + painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); + } + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + painter->setPen(newPen); + painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16); + painter->setPen(oldPen); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &startX, &startY, &width, &height, &r, &g, &b) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "iiii",&r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 3 && PyArg_ParseTuple(args, "iii",&r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QRect myQRect(startX,startY,width,height); + effect->_painter->fillRect(myQRect, QColor(r,g,b,a)); + return Py_BuildValue(""); + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int thick = 1; + int endX = effect->_imageSize.width(); + int endY = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QRect myQRect(startX, startY, endX, endY); + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawLine(startX, startY, endX, endY); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b, x, y; + int a = 255; + int thick = 1; + + bool argsOK = false; + + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &x, &y, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiii", &x, &y, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawPoint(x, y); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int thick = 1; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QRect myQRect(startX,startY,width,height); + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawRect(startX, startY, width, height); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b, x, y; + + if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) ) + { + effect->_image.setPixel(x,y,qRgb(r,g,b)); + return Py_BuildValue(""); + } + + return nullptr; +} + + +PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int x, y; + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) ) + { + QRgb rgb = effect->_image.pixel(x,y); + return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb)); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageSave(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + QImage img(effect->_image.copy()); + effect->_imageStack.append(img); + + return Py_BuildValue("i", effect->_imageStack.size()-1); +} + +PyObject* EffectModule::wrapImageMinSize(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int w, h; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) ) + { + if (width_painter; + + effect->_image = effect->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + effect->_imageSize = effect->_image.size(); + effect->_painter = new QPainter(&(effect->_image)); + } + return Py_BuildValue("ii", effect->_image.width(), effect->_image.height()); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageWidth(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + return Py_BuildValue("i", effect->_imageSize.width()); +} + +PyObject* EffectModule::wrapImageHeight(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + return Py_BuildValue("i", effect->_imageSize.height()); +} + +PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int angle; + + if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) ) + { + angle = qMax(qMin(angle,360),0); + effect->_painter->rotate(angle); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int offsetX = 0; + int offsetY = 0; + int argCount = PyTuple_Size(args); + + if ( argCount == 2 ) + { + PyArg_ParseTuple(args, "ii", &offsetX, &offsetY ); + } + + effect->_painter->translate(QPoint(offsetX,offsetY)); + return Py_BuildValue(""); +} + +PyObject* EffectModule::wrapImageCShear(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int sh,sv; + int argCount = PyTuple_Size(args); + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv )) + { + effect->_painter->shear(sh,sv); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageResetT(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + effect->_painter->resetTransform(); + return Py_BuildValue(""); +} + +Effect * EffectModule::getEffect() +{ + // extract the module from the runtime + PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); + + if (!PyModule_Check(module)) + { + // something is wrong + Py_XDECREF(module); + Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); + return nullptr; + } + + // retrieve the capsule with the effect + PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); + Py_XDECREF(module); + + if (!PyCapsule_CheckExact(effectCapsule)) + { + // something is wrong + Py_XDECREF(effectCapsule); + Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); + return nullptr; + } + + // Get the effect from the capsule + Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); + Py_XDECREF(effectCapsule); + return effect; +} diff --git a/libsrc/grabber/amlogic/AmlogicWrapper.cpp b/libsrc/grabber/amlogic/AmlogicWrapper.cpp index 8db02591..205787e8 100644 --- a/libsrc/grabber/amlogic/AmlogicWrapper.cpp +++ b/libsrc/grabber/amlogic/AmlogicWrapper.cpp @@ -1,7 +1,7 @@ #include -AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("AmLogic", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("AmLogic", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(grabWidth, grabHeight) {} diff --git a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp index ec7aacc3..c1222ef7 100644 --- a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp +++ b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp @@ -7,7 +7,7 @@ #include "grabber/DispmanxFrameGrabber.h" DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned height) - : Grabber("DISPMANXGRABBER", width, height) + : Grabber("DISPMANXGRABBER", 0, 0) , _vc_display(0) , _vc_resource(0) , _vc_flags(0) @@ -20,48 +20,60 @@ DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned // Initiase BCM bcm_host_init(); - { - // Check if the display can be opened and display the current resolution - // Open the connection to the display - _vc_display = vc_dispmanx_display_open(0); - assert(_vc_display > 0); + // Check if the display can be opened and display the current resolution + // Open the connection to the display + _vc_display = vc_dispmanx_display_open(0); + assert(_vc_display > 0); - // Obtain the display information - DISPMANX_MODEINFO_T vc_info; - int result = vc_dispmanx_display_get_info(_vc_display, &vc_info); - // Keep compiler happy in 'release' mode - (void)result; - assert(result == 0); - Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); + // Obtain the display information + DISPMANX_MODEINFO_T vc_info; + int result = vc_dispmanx_display_get_info(_vc_display, &vc_info); + // Keep compiler happy in 'release' mode + (void)result; + assert(result == 0); + Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); - // Close the displaye - vc_dispmanx_display_close(_vc_display); - } + // Close the displaye + vc_dispmanx_display_close(_vc_display); - // Create the resources for capturing image - uint32_t vc_nativeImageHandle; - _vc_resource = vc_dispmanx_resource_create( - VC_IMAGE_RGBA32, - width, - height, - &vc_nativeImageHandle); - assert(_vc_resource); - - // Define the capture rectangle with the same size - vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); + // init the resource and capture rectangle + setWidthHeight(width, height); } DispmanxFrameGrabber::~DispmanxFrameGrabber() { - delete[] _captureBuffer; - - // Clean up resources - vc_dispmanx_resource_delete(_vc_resource); + freeResources(); // De-init BCM bcm_host_deinit(); } +void DispmanxFrameGrabber::freeResources() +{ + delete[] _captureBuffer; + // Clean up resources + vc_dispmanx_resource_delete(_vc_resource); +} + +void DispmanxFrameGrabber::setWidthHeight(int width, int height) +{ + if(_width != width || _height != height) + { + freeResources(); + // Create the resources for capturing image + uint32_t vc_nativeImageHandle; + _vc_resource = vc_dispmanx_resource_create( + VC_IMAGE_RGBA32, + width, + height, + &vc_nativeImageHandle); + assert(_vc_resource); + + // Define the capture rectangle with the same size + vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); + } +} + void DispmanxFrameGrabber::setFlags(const int vc_flags) { _vc_flags = vc_flags; @@ -143,8 +155,8 @@ int DispmanxFrameGrabber::grabFrame(Image & image) unsigned capturePitch = (_rectangle.width * sizeof(ColorRgba) + 63) & (~63); // grab to temp buffer if image pitch isn't valid or if we are cropping - if (imagePitch != capturePitch - || (unsigned)_rectangle.width != imageWidth + if (imagePitch != capturePitch + || (unsigned)_rectangle.width != imageWidth || (unsigned)_rectangle.height != imageHeight) { // check if we need to resize the capture buffer diff --git a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp index 633fa60d..8a12ed27 100644 --- a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp +++ b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp @@ -1,7 +1,7 @@ #include -DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("Dispmanx", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("Dispmanx", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(grabWidth, grabHeight) { setImageProcessorEnabled(false); diff --git a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp index 3eec042c..9cf2675b 100755 --- a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp +++ b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp @@ -16,31 +16,9 @@ FramebufferFrameGrabber::FramebufferFrameGrabber(const QString & device, const u : Grabber("FRAMEBUFFERGRABBER", width, height) , _fbfd(0) , _fbp(0) - , _fbDevice(device) + , _fbDevice() { - int result; - struct fb_var_screeninfo vinfo; - - // Check if the framebuffer device can be opened and display the current resolution - _fbfd = open(QSTRING_CSTR(_fbDevice), O_RDONLY); - if (_fbfd == 0) - { - Error(_log, "Error openning %s", QSTRING_CSTR(_fbDevice)); - } - else - { - // get variable screen information - result = ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); - if (result != 0) - { - Error(_log, "Could not get screen information"); - } - else - { - Info(_log, "Display opened with resolution: %dx%d@%dbit", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); - } - close(_fbfd); - } + setDevicePath(device); } FramebufferFrameGrabber::~FramebufferFrameGrabber() @@ -63,7 +41,7 @@ int FramebufferFrameGrabber::grabFrame(Image & image) bytesPerPixel = vinfo.bits_per_pixel / 8; capSize = vinfo.xres * vinfo.yres * bytesPerPixel; - + switch (vinfo.bits_per_pixel) { case 16: pixelFormat = PIXELFORMAT_BGR16; break; @@ -76,7 +54,7 @@ int FramebufferFrameGrabber::grabFrame(Image & image) } /* map the device to memory */ - _fbp = (unsigned char*)mmap(0, capSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, _fbfd, 0); + _fbp = (unsigned char*)mmap(0, capSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, _fbfd, 0); _imageResampler.setHorizontalPixelDecimation(vinfo.xres/_width); _imageResampler.setVerticalPixelDecimation(vinfo.yres/_height); @@ -86,9 +64,41 @@ int FramebufferFrameGrabber::grabFrame(Image & image) vinfo.xres * bytesPerPixel, pixelFormat, image); - + munmap(_fbp, capSize); close(_fbfd); return 0; } + +void FramebufferFrameGrabber::setDevicePath(const QString& path) +{ + if(_fbDevice != path) + { + _fbDevice = path; + int result; + struct fb_var_screeninfo vinfo; + + // Check if the framebuffer device can be opened and display the current resolution + _fbfd = open(QSTRING_CSTR(_fbDevice), O_RDONLY); + if (_fbfd == 0) + { + Error(_log, "Error openning %s", QSTRING_CSTR(_fbDevice)); + } + else + { + // get variable screen information + result = ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); + if (result != 0) + { + Error(_log, "Could not get screen information"); + } + else + { + Info(_log, "Display opened with resolution: %dx%d@%dbit", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); + } + close(_fbfd); + } + + } +} diff --git a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp index 9a7cc367..4a88bbee 100644 --- a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp +++ b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp @@ -1,7 +1,7 @@ #include -FramebufferWrapper::FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("FrameBuffer", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +FramebufferWrapper::FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("FrameBuffer", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(device, grabWidth, grabHeight) {} diff --git a/libsrc/grabber/osx/OsxFrameGrabber.cpp b/libsrc/grabber/osx/OsxFrameGrabber.cpp index 97862f0f..37b384ef 100755 --- a/libsrc/grabber/osx/OsxFrameGrabber.cpp +++ b/libsrc/grabber/osx/OsxFrameGrabber.cpp @@ -7,30 +7,10 @@ OsxFrameGrabber::OsxFrameGrabber(const unsigned display, const unsigned width, const unsigned height) : Grabber("OSXGRABBER", width, height) - , _screenIndex(display) + , _screenIndex(100) { - CGImageRef image; - CGDisplayCount displayCount; - CGDirectDisplayID displays[8]; - - // get list of displays - CGGetActiveDisplayList(8, displays, &displayCount); - if (_screenIndex + 1 > displayCount) - { - Error(_log, "Display with index %d is not available. Using main display", _screenIndex); - _display = kCGDirectMainDisplay; - } - else - { - _display = displays[_screenIndex]; - } - - image = CGDisplayCreateImage(_display); - assert(image != NULL); - - Info(_log, "Display opened with resolution: %dx%d@%dbit", CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerPixel(image)); - - CGImageRelease(image); + // check if display is available + setDisplayIndex(display); } OsxFrameGrabber::~OsxFrameGrabber() @@ -43,11 +23,11 @@ int OsxFrameGrabber::grabFrame(Image & image) CGImageRef dispImage; CFDataRef imgData; - unsigned char * pImgData; + unsigned char * pImgData; unsigned dspWidth, dspHeight; - + dispImage = CGDisplayCreateImage(_display); - + // display lost, use main if (dispImage == NULL && _display) { @@ -63,7 +43,7 @@ int OsxFrameGrabber::grabFrame(Image & image) pImgData = (unsigned char*) CFDataGetBytePtr(imgData); dspWidth = CGImageGetWidth(dispImage); dspHeight = CGImageGetHeight(dispImage); - + _imageResampler.setHorizontalPixelDecimation(dspWidth/_width); _imageResampler.setVerticalPixelDecimation(dspHeight/_height); _imageResampler.processImage( pImgData, @@ -72,9 +52,40 @@ int OsxFrameGrabber::grabFrame(Image & image) CGImageGetBytesPerRow(dispImage), PIXELFORMAT_BGR32, image); - + CFRelease(imgData); CGImageRelease(dispImage); return 0; } + +void OsxFrameGrabber::setDisplayIndex(int index) +{ + if(_screenIndex != index) + { + _screenIndex = index; + + CGImageRef image; + CGDisplayCount displayCount; + CGDirectDisplayID displays[8]; + + // get list of displays + CGGetActiveDisplayList(8, displays, &displayCount); + if (_screenIndex + 1 > displayCount) + { + Error(_log, "Display with index %d is not available. Using main display", _screenIndex); + _display = kCGDirectMainDisplay; + } + else + { + _display = displays[_screenIndex]; + } + + image = CGDisplayCreateImage(_display); + assert(image != NULL); + + Info(_log, "Display opened with resolution: %dx%d@%dbit", CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerPixel(image)); + + CGImageRelease(image); + } +} diff --git a/libsrc/grabber/osx/OsxWrapper.cpp b/libsrc/grabber/osx/OsxWrapper.cpp index f543eada..6324532c 100644 --- a/libsrc/grabber/osx/OsxWrapper.cpp +++ b/libsrc/grabber/osx/OsxWrapper.cpp @@ -1,7 +1,7 @@ #include -OsxWrapper::OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("OSX FrameGrabber", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +OsxWrapper::OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("OSX FrameGrabber", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(display, grabWidth, grabHeight) {} diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 16df0b17..64f4d418 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -27,13 +27,9 @@ V4L2Grabber::V4L2Grabber(const QString & device , int input , VideoStandard videoStandard , PixelFormat pixelFormat - , unsigned width - , unsigned height - , int frameDecimation - , int horizontalPixelDecimation - , int verticalPixelDecimation + , int pixelDecimation ) - : Grabber("V4L2:"+device, width, height) + : Grabber("V4L2:"+device) , _deviceName(device) , _input(input) , _videoStandard(videoStandard) @@ -41,9 +37,9 @@ V4L2Grabber::V4L2Grabber(const QString & device , _fileDescriptor(-1) , _buffers() , _pixelFormat(pixelFormat) + , _pixelDecimation(pixelDecimation) , _lineLength(-1) , _frameByteSize(-1) - , _frameDecimation(qMax(1, frameDecimation)) , _noSignalCounterThreshold(50) , _noSignalThresholdColor(ColorRgb{0,0,0}) , _signalDetectionEnabled(true) @@ -53,14 +49,13 @@ V4L2Grabber::V4L2Grabber(const QString & device , _y_frac_min(0.25) , _x_frac_max(0.75) , _y_frac_max(0.75) - , _currentFrame(0) , _streamNotifier(nullptr) , _initialized(false) , _deviceAutoDiscoverEnabled(false) { - _imageResampler.setHorizontalPixelDecimation(qMax(1, horizontalPixelDecimation)); - _imageResampler.setVerticalPixelDecimation(qMax(1, verticalPixelDecimation)); + //_imageResampler.setHorizontalPixelDecimation(pixelDecimation); + //_imageResampler.setVerticalPixelDecimation(pixelDecimation); getV4Ldevices(); } @@ -245,11 +240,13 @@ void V4L2Grabber::open_device() if (-1 == stat(QSTRING_CSTR(_deviceName), &st)) { throw_errno_exception("Cannot identify '" + _deviceName + "'"); + return; } if (!S_ISCHR(st.st_mode)) { throw_exception("'" + _deviceName + "' is no device"); + return; } _fileDescriptor = open(QSTRING_CSTR(_deviceName), O_RDWR | O_NONBLOCK, 0); @@ -257,6 +254,7 @@ void V4L2Grabber::open_device() if (-1 == _fileDescriptor) { throw_errno_exception("Cannot open '" + _deviceName + "'"); + return; } // create the notifier for when a new frame is available @@ -268,7 +266,10 @@ void V4L2Grabber::open_device() void V4L2Grabber::close_device() { if (-1 == close(_fileDescriptor)) + { throw_errno_exception("close"); + return; + } _fileDescriptor = -1; @@ -288,6 +289,7 @@ void V4L2Grabber::init_read(unsigned int buffer_size) if (!_buffers[0].start) { throw_exception("Out of memory"); + return; } } @@ -304,13 +306,16 @@ void V4L2Grabber::init_mmap() if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { throw_exception("'" + _deviceName + "' does not support memory mapping"); + return; } else { throw_errno_exception("VIDIOC_REQBUFS"); + return; } } if (req.count < 2) { throw_exception("Insufficient buffer memory on " + _deviceName); + return; } _buffers.resize(req.count); @@ -325,7 +330,10 @@ void V4L2Grabber::init_mmap() buf.index = n_buffers; if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) + { throw_errno_exception("VIDIOC_QUERYBUF"); + return; + } _buffers[n_buffers].length = buf.length; _buffers[n_buffers].start = @@ -336,7 +344,10 @@ void V4L2Grabber::init_mmap() _fileDescriptor, buf.m.offset); if (MAP_FAILED == _buffers[n_buffers].start) + { throw_errno_exception("mmap"); + return; + } } } @@ -354,8 +365,10 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) if (EINVAL == errno) { throw_exception("'" + _deviceName + "' does not support user pointer"); + return; } else { throw_errno_exception("VIDIOC_REQBUFS"); + return; } } @@ -367,6 +380,7 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) if (!_buffers[n_buffers].start) { throw_exception("Out of memory"); + return; } } } @@ -378,14 +392,17 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { if (EINVAL == errno) { throw_exception("'" + _deviceName + "' is no V4L2 device"); + return; } else { throw_errno_exception("VIDIOC_QUERYCAP"); + return; } } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { throw_exception("'" + _deviceName + "' is no video capture device"); + return; } switch (_ioMethod) { @@ -393,6 +410,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (!(cap.capabilities & V4L2_CAP_READWRITE)) { throw_exception("'" + _deviceName + "' does not support read i/o"); + return; } break; @@ -401,6 +419,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (!(cap.capabilities & V4L2_CAP_STREAMING)) { throw_exception("'" + _deviceName + "' does not support streaming i/o"); + return; } break; } @@ -438,6 +457,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_INPUT, &input)) { throw_errno_exception("VIDIOC_S_INPUT"); + return; } } @@ -450,6 +470,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -459,6 +480,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -468,6 +490,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -485,6 +508,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { throw_errno_exception("VIDIOC_G_FMT"); + return; } // set the requested pixel format @@ -505,19 +529,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; } - // set the requested withd and height - if (_width > 0 || _height > 0) - { - if (_width > 0) - { - fmt.fmt.pix.width = _width; - } - - if (fmt.fmt.pix.height > 0) - { - fmt.fmt.pix.height = _height; - } - } + // calc the size based on pixelDecimation + fmt.fmt.pix.width = fmt.fmt.pix.width / _pixelDecimation; + fmt.fmt.pix.height = fmt.fmt.pix.height / _pixelDecimation; // set the line length _lineLength = fmt.fmt.pix.bytesperline; @@ -526,6 +540,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) { throw_errno_exception("VIDIOC_S_FMT"); + return; } // get the format settings again @@ -533,6 +548,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { throw_errno_exception("VIDIOC_G_FMT"); + return; } // store width & height @@ -563,6 +579,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; default: throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); + return; } switch (_ioMethod) { @@ -590,7 +607,10 @@ void V4L2Grabber::uninit_device() case IO_METHOD_MMAP: for (size_t i = 0; i < _buffers.size(); ++i) if (-1 == munmap(_buffers[i].start, _buffers[i].length)) + { throw_errno_exception("munmap"); + return; + } break; case IO_METHOD_USERPTR: @@ -620,11 +640,17 @@ void V4L2Grabber::start_capturing() buf.index = i; if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { throw_errno_exception("VIDIOC_QBUF"); + return; + } } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) + { throw_errno_exception("VIDIOC_STREAMON"); + return; + } break; } case IO_METHOD_USERPTR: @@ -640,11 +666,17 @@ void V4L2Grabber::start_capturing() buf.length = _buffers[i].length; if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { throw_errno_exception("VIDIOC_QBUF"); + return; + } } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) + { throw_errno_exception("VIDIOC_STREAMON"); + return; + } break; } } @@ -692,6 +724,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("read"); + return 0; } } @@ -718,6 +751,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("VIDIOC_DQBUF"); + return 0; } } @@ -728,6 +762,7 @@ int V4L2Grabber::read_frame() if (-1 == xioctl(VIDIOC_QBUF, &buf)) { throw_errno_exception("VIDIOC_QBUF"); + return 0; } break; @@ -752,6 +787,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("VIDIOC_DQBUF"); + return 0; } } @@ -768,6 +804,7 @@ int V4L2Grabber::read_frame() if (-1 == xioctl(VIDIOC_QBUF, &buf)) { throw_errno_exception("VIDIOC_QBUF"); + return 0; } break; } @@ -783,19 +820,15 @@ int V4L2Grabber::read_frame() bool V4L2Grabber::process_image(const void *p, int size) { - if (++_currentFrame >= _frameDecimation) + // We do want a new frame... + if (size != _frameByteSize) { - // We do want a new frame... - if (size != _frameByteSize) - { - Error(_log, "Frame too small: %d != %d", size, _frameByteSize); - } - else - { - process_image(reinterpret_cast(p)); - _currentFrame = 0; // restart counting - return true; - } + Error(_log, "Frame too small: %d != %d", size, _frameByteSize); + } + else + { + process_image(reinterpret_cast(p)); + return true; } return false; @@ -874,20 +907,44 @@ int V4L2Grabber::xioctl(int request, void *arg) void V4L2Grabber::throw_exception(const QString & error) { - throw std::runtime_error(error.toStdString()); + Error(_log, "Throws error: %s", QSTRING_CSTR(error)); } void V4L2Grabber::throw_errno_exception(const QString & error) { - throw std::runtime_error(QString(error + " error code " + QString::number(errno) + ", " + strerror(errno)).toStdString()); + Error(_log, "Throws error nr: %s", QSTRING_CSTR(QString(error + " error code " + QString::number(errno) + ", " + strerror(errno)))); } void V4L2Grabber::setSignalDetectionEnable(bool enable) { - _signalDetectionEnabled = enable; + if(_signalDetectionEnabled != enable) + { + _signalDetectionEnabled = enable; + Info(_log, "Signal detection is now %s", enable ? "enabled" : "disabled"); + } } bool V4L2Grabber::getSignalDetectionEnabled() { return _signalDetectionEnabled; } + +void V4L2Grabber::setPixelDecimation(int pixelDecimation) +{ + if(_pixelDecimation != pixelDecimation) + { + uninit(); + init(); + } +} + +void V4L2Grabber::setInputVideoStandard(int input, VideoStandard videoStandard) +{ + if(_input != input || _videoStandard != videoStandard) + { + _input = input; + _videoStandard = videoStandard; + uninit(); + init(); + } +} diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index aef95648..809b1e09 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -2,45 +2,29 @@ #include -#include +// qt +#include V4L2Wrapper::V4L2Wrapper(const QString &device, int input, VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int pixelDecimation, - double redSignalThreshold, - double greenSignalThreshold, - double blueSignalThreshold, - const int priority) - : GrabberWrapper("V4L2:"+device, &_grabber, width, height, 8, priority, hyperion::COMP_V4L) + int pixelDecimation ) + : GrabberWrapper("V4L2:"+device, &_grabber, 0, 0, 10) , _grabber(device, input, videoStandard, pixelFormat, - width, - height, - frameDecimation, - pixelDecimation, pixelDecimation) { - // set the signal detection threshold of the grabber - _grabber.setSignalThreshold( redSignalThreshold, greenSignalThreshold, blueSignalThreshold, 50); _ggrabber = &_grabber; // register the image type qRegisterMetaType>("Image"); - qRegisterMetaType>("std::vector"); - qRegisterMetaType("hyperion::Components"); // Handle the image in the captured thread using a direct connection QObject::connect(&_grabber, SIGNAL(newFrame(Image)), this, SLOT(newFrame(Image)), Qt::DirectConnection); QObject::connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection); - - _timer.setInterval(500); } bool V4L2Wrapper::start() @@ -54,6 +38,11 @@ void V4L2Wrapper::stop() GrabberWrapper::stop(); } +void V4L2Wrapper::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold) +{ + _grabber.setSignalThreshold( redSignalThreshold, greenSignalThreshold, blueSignalThreshold, 50); +} + void V4L2Wrapper::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) { _grabber.setCropping(cropLeft, cropRight, cropTop, cropBottom); @@ -66,11 +55,7 @@ void V4L2Wrapper::setSignalDetectionOffset(double verticalMin, double horizontal void V4L2Wrapper::newFrame(const Image &image) { - emit emitImage(_priority, image, _timeout_ms); - - // process the new image - _processor->process(image, _ledColors); - setColors(_ledColors, _timeout_ms); + emit systemImage(image); } void V4L2Wrapper::readError(const char* err) @@ -79,21 +64,9 @@ void V4L2Wrapper::readError(const char* err) stop(); } -void V4L2Wrapper::checkSources() -{ - if ( _hyperion->isCurrentPriority(_priority)) - { - _grabber.start(); - } - else - { - _grabber.stop(); - } -} - void V4L2Wrapper::action() { - checkSources(); + } void V4L2Wrapper::setSignalDetectionEnable(bool enable) diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp index 2756f055..f4bc62d0 100755 --- a/libsrc/grabber/x11/X11Grabber.cpp +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -1,17 +1,15 @@ #include #include -X11Grabber::X11Grabber(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) +X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) : Grabber("X11GRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) - , _useXGetImage(useXGetImage) , _x11Display(nullptr) , _pixmap(None) , _srcFormat(nullptr) , _dstFormat(nullptr) , _srcPicture(None) , _dstPicture(None) - , _horizontalDecimation(horizontalPixelDecimation) - , _verticalDecimation(verticalPixelDecimation) + , _pixelDecimation(pixelDecimation) , _screenWidth(0) , _screenHeight(0) , _src_x(cropLeft) @@ -37,13 +35,13 @@ void X11Grabber::freeResources() { // Cleanup allocated resources of the X11 grab XDestroyImage(_xImage); - if(_XShmAvailable && !_useXGetImage) + if(_XShmAvailable) { XShmDetach(_x11Display, &_shminfo); shmdt(_shminfo.shmaddr); shmctl(_shminfo.shmid, IPC_RMID, 0); } - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { XRenderFreePicture(_x11Display, _srcPicture); XRenderFreePicture(_x11Display, _dstPicture); @@ -53,7 +51,7 @@ void X11Grabber::freeResources() void X11Grabber::setupResources() { - if(_XShmAvailable && !_useXGetImage) + if(_XShmAvailable) { _xImage = XShmCreateImage(_x11Display, _windowAttr.visual, _windowAttr.depth, ZPixmap, NULL, &_shminfo, _width, _height); _shminfo.shmid = shmget(IPC_PRIVATE, _xImage->bytes_per_line * _xImage->height, IPC_CREAT|0777); @@ -62,7 +60,7 @@ void X11Grabber::setupResources() _shminfo.readOnly = False; XShmAttach(_x11Display, &_shminfo); } - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { if(_XShmPixmapAvailable) { @@ -96,19 +94,19 @@ bool X11Grabber::Setup() } return false; } - + _window = DefaultRootWindow(_x11Display); int dummy, pixmaps_supported; - + _XRenderAvailable = XRenderQueryExtension(_x11Display, &dummy, &dummy); _XShmAvailable = XShmQueryExtension(_x11Display); XShmQueryVersion(_x11Display, &dummy, &dummy, &pixmaps_supported); _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display) == ZPixmap; - + // Image scaling is performed by XRender when available, otherwise by ImageResampler - _imageResampler.setHorizontalPixelDecimation(_XRenderAvailable ? 1 : _horizontalDecimation); - _imageResampler.setVerticalPixelDecimation(_XRenderAvailable ? 1 : _verticalDecimation); + _imageResampler.setHorizontalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); + _imageResampler.setVerticalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); bool result = (updateScreenDimensions(true) >=0); ErrorIf(!result, _log, "X11 Grabber start failed"); @@ -119,14 +117,15 @@ int X11Grabber::grabFrame(Image & image, bool forceUpdate) { if (!_enabled) return 0; - updateScreenDimensions(forceUpdate); - - if (_XRenderAvailable && !_useXGetImage) + if (forceUpdate) + updateScreenDimensions(forceUpdate); + + if (_XRenderAvailable) { - double scale_x = static_cast(_windowAttr.width / _horizontalDecimation) / static_cast(_windowAttr.width); - double scale_y = static_cast(_windowAttr.height / _verticalDecimation) / static_cast(_windowAttr.height); + double scale_x = static_cast(_windowAttr.width / _pixelDecimation) / static_cast(_windowAttr.width); + double scale_y = static_cast(_windowAttr.height / _pixelDecimation) / static_cast(_windowAttr.height); double scale = qMin(scale_y, scale_x); - + _transform = { { @@ -147,27 +146,27 @@ int X11Grabber::grabFrame(Image & image, bool forceUpdate) } } }; - + XRenderSetPictureTransform (_x11Display, _srcPicture, &_transform); - + // display, op, src, mask, dest, src_x = cropLeft, - // src_y = cropTop, mask_x, mask_y, dest_x, dest_y, width, height + // src_y = cropTop, mask_x, mask_y, dest_x, dest_y, width, height XRenderComposite( - _x11Display, PictOpSrc, _srcPicture, None, _dstPicture, ( _src_x/_horizontalDecimation), - (_src_y/_verticalDecimation), 0, 0, 0, 0, _width, _height); - + _x11Display, PictOpSrc, _srcPicture, None, _dstPicture, ( _src_x/_pixelDecimation), + (_src_y/_pixelDecimation), 0, 0, 0, 0, _width, _height); + XSync(_x11Display, False); - + if (_XShmAvailable) { XShmGetImage(_x11Display, _pixmap, _xImage, 0, 0, AllPlanes); } else { - _xImage = XGetImage(_x11Display, _pixmap, 0, 0, _width, _height, AllPlanes, ZPixmap); + _xImage = XGetImage(_x11Display, _pixmap, 0, 0, _width, _height, AllPlanes, ZPixmap); } } - else if (_XShmAvailable && !_useXGetImage) + else if (_XShmAvailable) { // use xshm XShmGetImage(_x11Display, _window, _xImage, _src_x, _src_y, AllPlanes); @@ -212,20 +211,20 @@ int X11Grabber::updateScreenDimensions(bool force) Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, _windowAttr.width, _windowAttr.height); _screenWidth = _windowAttr.width; _screenHeight = _windowAttr.height; - + int width=0, height=0; - + // Image scaling is performed by XRender when available, otherwise by ImageResampler - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { width = (_screenWidth > unsigned(_cropLeft + _cropRight)) - ? ((_screenWidth - _cropLeft - _cropRight) / _horizontalDecimation) - : _screenWidth / _horizontalDecimation; - + ? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) + : _screenWidth / _pixelDecimation; + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) - ? ((_screenHeight - _cropTop - _cropBottom) / _verticalDecimation) - : _screenHeight / _verticalDecimation; - + ? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) + : _screenHeight / _pixelDecimation; + Info(_log, "Using XRender for grabbing"); } else @@ -233,11 +232,11 @@ int X11Grabber::updateScreenDimensions(bool force) width = (_screenWidth > unsigned(_cropLeft + _cropRight)) ? (_screenWidth - _cropLeft - _cropRight) : _screenWidth; - + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) ? (_screenHeight - _cropTop - _cropBottom) : _screenHeight; - + Info(_log, "Using XGetImage for grabbing"); } @@ -264,7 +263,7 @@ int X11Grabber::updateScreenDimensions(bool force) _src_y = _cropTop; break; } - + Info(_log, "Update output image resolution: [%dx%d] to [%dx%d]", _image.width(), _image.height(), _width, _height); _image.resize(_width, _height); @@ -275,7 +274,26 @@ int X11Grabber::updateScreenDimensions(bool force) void X11Grabber::setVideoMode(VideoMode mode) { - Info(_log, "a %d", mode); Grabber::setVideoMode(mode); updateScreenDimensions(true); } + +void X11Grabber::setWidthHeight(int width, int height) +{ + // empty overwrite +} + +void X11Grabber::setPixelDecimation(int pixelDecimation) +{ + if(_pixelDecimation != pixelDecimation) + { + _pixelDecimation = pixelDecimation; + updateScreenDimensions(true); + } +} + +void X11Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) +{ + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + if(_x11Display != nullptr) updateScreenDimensions(true); // segfault on init +} diff --git a/libsrc/grabber/x11/X11Wrapper.cpp b/libsrc/grabber/x11/X11Wrapper.cpp index 75f5f117..ee445ea2 100644 --- a/libsrc/grabber/x11/X11Wrapper.cpp +++ b/libsrc/grabber/x11/X11Wrapper.cpp @@ -1,8 +1,8 @@ #include -X11Wrapper::X11Wrapper(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("X11", &_grabber, 0, 0, updateRate_Hz, priority, hyperion::COMP_GRABBER) - , _grabber(useXGetImage, cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation) +X11Wrapper::X11Wrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz) + : GrabberWrapper("X11", &_grabber, 0, 0, updateRate_Hz) + , _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) , _init(false) {} diff --git a/libsrc/hyperion/CaptureCont.cpp b/libsrc/hyperion/CaptureCont.cpp new file mode 100644 index 00000000..cc9a2a1f --- /dev/null +++ b/libsrc/hyperion/CaptureCont.cpp @@ -0,0 +1,106 @@ +#include + +#include + +CaptureCont::CaptureCont(Hyperion* hyperion) + : QObject() + , _hyperion(hyperion) + , _systemCaptEnabled(false) + , _v4lCaptEnabled(false) +{ + // settings changes + connect(_hyperion, &Hyperion::settingsChanged, this, &CaptureCont::handleSettingsUpdate); + + // comp changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &CaptureCont::componentStateChanged); + + // init + handleSettingsUpdate(settings::INSTCAPTURE, _hyperion->getSetting(settings::INSTCAPTURE)); +} + +CaptureCont::~CaptureCont() +{ + +} + +void CaptureCont::handleV4lImage(const Image & image) +{ + _hyperion->setInputImage(_v4lCaptPrio, image); +} + +void CaptureCont::handleSystemImage(const Image& image) +{ + _hyperion->setInputImage(_systemCaptPrio, image); +} + + +void CaptureCont::setSystemCaptureEnable(const bool& enable) +{ + if(_systemCaptEnabled != enable) + { + if(enable) + { + _hyperion->registerInput(_systemCaptPrio, hyperion::COMP_GRABBER, "System", "DoNotKnow"); + connect(_hyperion, &Hyperion::systemImage, this, &CaptureCont::handleSystemImage); + } + else + { + disconnect(_hyperion, &Hyperion::systemImage, this, &CaptureCont::handleSystemImage); + _hyperion->clear(_systemCaptPrio); + } + _systemCaptEnabled = enable; + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, enable); + } +} + +void CaptureCont::setV4LCaptureEnable(const bool& enable) +{ + if(_v4lCaptEnabled != enable) + { + if(enable) + { + _hyperion->registerInput(_v4lCaptPrio, hyperion::COMP_V4L, "System", "DoNotKnow"); + connect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); + } + else + { + disconnect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); + _hyperion->clear(_v4lCaptPrio); + } + _v4lCaptEnabled = enable; + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, enable); + } +} + +void CaptureCont::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::INSTCAPTURE) + { + const QJsonObject& obj = config.object(); + if(_v4lCaptPrio != obj["v4lPriority"].toInt(240)) + { + setV4LCaptureEnable(false); // clear prio + _v4lCaptPrio = obj["v4lPriority"].toInt(240); + } + if(_systemCaptPrio != obj["systemPriority"].toInt(250)) + { + setSystemCaptureEnable(false); // clear prio + _systemCaptPrio = obj["systemPriority"].toInt(250); + } + + setV4LCaptureEnable(obj["v4lEnable"].toBool(true)); + setSystemCaptureEnable(obj["systemEnable"].toBool(true)); + } +} + +void CaptureCont::componentStateChanged(const hyperion::Components component, bool enable) +{ + if(component == hyperion::COMP_GRABBER) + { + setSystemCaptureEnable(enable); + } + else if(component == hyperion::COMP_V4L) + { + setV4LCaptureEnable(enable); + } +} diff --git a/libsrc/hyperion/ComponentRegister.cpp b/libsrc/hyperion/ComponentRegister.cpp index 41f52c75..baa573d7 100644 --- a/libsrc/hyperion/ComponentRegister.cpp +++ b/libsrc/hyperion/ComponentRegister.cpp @@ -1,27 +1,72 @@ #include #include -ComponentRegister::ComponentRegister() - : _log(Logger::getInstance("ComponentRegister")) +#include + +using namespace hyperion; + +ComponentRegister::ComponentRegister(Hyperion* hyperion) + : _hyperion(hyperion) + , _log(Logger::getInstance("ComponentRegister")) { + // init all comps to false + QVector vect; + vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_UDPLISTENER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE; + for(auto e : vect) + { + _componentStates.emplace(e, ((e == COMP_ALL) ? true : false)); + } } ComponentRegister::~ComponentRegister() { } +bool ComponentRegister::setHyperionEnable(const bool& state) +{ + if(!state && _prevComponentStates.empty()) + { + Debug(_log,"Disable Hyperion, store current component states"); + for(const auto comp : _componentStates) + { + // save state + _prevComponentStates.emplace(comp.first, comp.second); + // disable if enabled + if(comp.second) + _hyperion->setComponentState(comp.first, false); + } + componentStateChanged(COMP_ALL, false); + return true; + } + else if(state && !_prevComponentStates.empty()) + { + Debug(_log,"Enable Hyperion, recover previous component states"); + for(const auto comp : _prevComponentStates) + { + // if comp was enabled, enable again + if(comp.second) + _hyperion->setComponentState(comp.first, true); + + } + _prevComponentStates.clear(); + componentStateChanged(COMP_ALL, true); + return true; + } + return false; +} + +bool ComponentRegister::isComponentEnabled(const hyperion::Components& comp) const +{ + return _componentStates.at(comp); +} void ComponentRegister::componentStateChanged(const hyperion::Components comp, const bool activated) { - Info(_log, "%s: %s", componentToString(comp), (activated? "activated" : "off")); - _componentStates.emplace(comp,activated); - _componentStates[comp] = activated; - -/* for(auto comp : _componentStates) + if(_componentStates[comp] != activated) { - std::cout << hyperion::componentToIdString(comp.first) << " " << comp.second << std::endl; + Debug( _log, "%s: %s", componentToString(comp), (activated? "enabled" : "disabled")); + _componentStates[comp] = activated; + // emit component has changed state + emit updatedComponentState(comp, activated); } - std::cout << "\n"; - */ } - diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index bf0edd1b..9811f79f 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -13,7 +13,6 @@ Grabber::Grabber(QString grabberName, int width, int height, int cropLeft, int c , _cropBottom(0) , _enabled(true) , _log(Logger::getInstance(grabberName)) - { setVideoMode(VIDEO_2D); setCropping(cropLeft, cropRight, cropTop, cropBottom); @@ -44,7 +43,7 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo { if (cropLeft + cropRight >= (unsigned)_width || cropTop + cropBottom >= (unsigned)_height) { - Error(_log, "Rejecting invalid crop values: left: %d, right: %d, top: %d, bottom: %d", cropLeft, cropRight, cropTop, cropBottom); + Error(_log, "Rejecting invalid crop values: left: %d, right: %d, top: %d, bottom: %d, higher than height/width %d/%d", cropLeft, cropRight, cropTop, cropBottom, _height, _width); return; } } @@ -68,3 +67,19 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo Info(_log, "Cropping image: width=%d height=%d; crop: left=%d right=%d top=%d bottom=%d ", _width, _height, cropLeft, cropRight, cropTop, cropBottom); } } + +void Grabber::setWidthHeight(int width, int height) +{ + // eval changes with crop + if (width>0 && height>0) + { + if (_cropLeft + _cropRight >= width || _cropTop + _cropBottom >= height) + { + Error(_log, "Rejecting invalid width/height values as it collides with image cropping: width: %d, height: %d", width, height); + return; + } + _width = width; + _height = height; + } + +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 9af13779..08e77fa7 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -1,111 +1,51 @@ // Hyperion includes -#include -#include #include #include #include -GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz, const int priority, hyperion::Components grabberComponentId) +//forwarder +#include + +// qt +#include + +GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz) : _grabberName(grabberName) , _hyperion(Hyperion::getInstance()) - , _priority(priority) - , _timer() + , _timer(new QTimer(this)) , _updateInterval_ms(1000/updateRate_Hz) - , _timeout_ms(2 * _updateInterval_ms) , _log(Logger::getInstance(grabberName)) , _forward(true) - , _processor(ImageProcessorFactory::getInstance().newImageProcessor()) - , _grabberComponentId(grabberComponentId) , _ggrabber(ggrabber) , _image(0,0) - , _ledColors(Hyperion::getInstance()->getLedCount(), ColorRgb{0,0,0}) - , _imageProcessorEnabled(true) { - _timer.setSingleShot(false); // Configure the timer to generate events every n milliseconds - _timer.setInterval(_updateInterval_ms); + _timer->setInterval(_updateInterval_ms); _image.resize(width, height); - _processor->setSize(width, height); _forward = _hyperion->getForwarder()->protoForwardingEnabled(); - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BLACKBORDER, _processor->blackBorderDetectorEnabled()); - qRegisterMetaType("hyperion::Components"); - - connect(_hyperion, SIGNAL(imageToLedsMappingChanged(int)), _processor, SLOT(setLedMappingType(int))); - connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); - connect(_hyperion, SIGNAL(videoMode(VideoMode)), this, SLOT(setVideoMode(VideoMode))); - connect(this, SIGNAL(emitImage(int, const Image&, const int)), _hyperion, SLOT(setImage(int, const Image&, const int)) ); - connect(&_timer, SIGNAL(timeout()), this, SLOT(actionWrapper())); + connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action); } GrabberWrapper::~GrabberWrapper() { stop(); Debug(_log,"Close grabber: %s", QSTRING_CSTR(_grabberName)); - delete _processor; } bool GrabberWrapper::start() { // Start the timer with the pre configured interval - _timer.start(); - _hyperion->registerPriority(_grabberName, _priority); - return _timer.isActive(); - + _timer->start(); + return _timer->isActive(); } void GrabberWrapper::stop() { // Stop the timer, effectivly stopping the process - _timer.stop(); - _hyperion->unRegisterPriority(_grabberName); -} - -void GrabberWrapper::actionWrapper() -{ - _ggrabber->setEnabled(_hyperion->isCurrentPriority(_priority)); - action(); -} - -void GrabberWrapper::componentStateChanged(const hyperion::Components component, bool enable) -{ - if (component == _grabberComponentId) - { - if (_timer.isActive() != enable) - { - if (enable) start(); - else stop(); - - _forward = _hyperion->getForwarder()->protoForwardingEnabled(); - - if ( enable == _timer.isActive() ) - { - Info(_log, "grabber change state to %s", (_timer.isActive() ? "enabled" : "disabled") ); - } - else - { - WarningIf( enable, _log, "enable grabber failed"); - } - } - _hyperion->getComponentRegister().componentStateChanged(component, _timer.isActive()); - } - - if (component == hyperion::COMP_BLACKBORDER) - { - if (_processor->blackBorderDetectorEnabled() != enable) - { - _processor->enableBlackBorderDetector(enable); - Info(_log, "bb detector change state to %s", (_processor->blackBorderDetectorEnabled() ? "enabled" : "disabled") ); - } - _hyperion->getComponentRegister().componentStateChanged(component, _processor->blackBorderDetectorEnabled()); - } -} - -void GrabberWrapper::setColors(const std::vector &ledColors, const int timeout_ms) -{ - _hyperion->setColors(_priority, ledColors, timeout_ms, true, _grabberComponentId); + _timer->stop(); } QStringList GrabberWrapper::availableGrabbers() @@ -140,7 +80,7 @@ QStringList GrabberWrapper::availableGrabbers() } -void GrabberWrapper::setVideoMode(const VideoMode mode) +void GrabberWrapper::setVideoMode(const VideoMode& mode) { if (_ggrabber != nullptr) { @@ -154,7 +94,77 @@ void GrabberWrapper::setCropping(unsigned cropLeft, unsigned cropRight, unsigned _ggrabber->setCropping(cropLeft, cropRight, cropTop, cropBottom); } -void GrabberWrapper::setImageProcessorEnabled(bool enable) +void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - _imageProcessorEnabled = enable; + if(type == settings::V4L2 || type == settings::SYSTEMCAPTURE) + { + // extract settings + QJsonObject obj; + if(config.isArray() && !config.isEmpty()) + obj = config.array().at(0).toObject(); + else + obj = config.object(); + + if(type == settings::SYSTEMCAPTURE) + { + // width/height + _ggrabber->setWidthHeight(obj["width"].toInt(96), obj["height"].toInt(96)); + + // display index for MAC + _ggrabber->setDisplayIndex(obj["display"].toInt(0)); + + // device path for Framebuffer + _ggrabber->setDevicePath(obj["device"].toString("/dev/fb0")); + + // pixel decimation for x11 + _ggrabber->setPixelDecimation(obj["pixelDecimation"].toInt(8)); + + // crop for system capture + _ggrabber->setCropping( + obj["cropLeft"].toInt(0), + obj["cropRight"].toInt(0), + obj["cropTop"].toInt(0), + obj["cropBottom"].toInt(0)); + + // eval new update timer (not for v4l) + if(_updateInterval_ms != 1000/obj["frequency_Hz"].toInt(10)) + { + _updateInterval_ms = 1000/obj["frequency_Hz"].toInt(10); + const bool& timerWasActive = _timer->isActive(); + _timer->stop(); + _timer->setInterval(_updateInterval_ms); + if(timerWasActive) + _timer->start(); + } + } + + if(type == settings::V4L2) + { + // pixel decimation for v4l + _ggrabber->setPixelDecimation(obj["sizeDecimation"].toInt(8)); + + // crop for v4l + _ggrabber->setCropping( + obj["cropLeft"].toInt(0), + obj["cropRight"].toInt(0), + obj["cropTop"].toInt(0), + obj["cropBottom"].toInt(0)); + + _ggrabber->setSignalDetectionEnable(obj["signalDetection"].toBool(true)); + _ggrabber->setSignalDetectionOffset( + obj["sDHOffsetMin"].toDouble(0.25), + obj["sDVOffsetMin"].toDouble(0.25), + obj["sDHOffsetMax"].toDouble(0.75), + obj["sDVOffsetMax"].toDouble(0.75)); + _ggrabber->setSignalThreshold( + obj["redSignalThreshold"].toDouble(0.0)/100.0, + obj["greenSignalThreshold"].toDouble(0.0)/100.0, + obj["blueSignalThreshold"].toDouble(0.0)/100.0); + _ggrabber->setInputVideoStandard( + obj["input"].toInt(0), + parseVideoStandard(obj["standard"].toString("no-change"))); + + } + } + } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index c626b7db..669994a1 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -11,35 +11,49 @@ #include #include #include +#include #include #include #include // hyperion include #include -#include +#include #include #include +// utils +#include + // Leddevice includes #include #include -#include "MultiColorAdjustment.h" +#include #include "LinearColorSmoothing.h" // effect engine includes #include -#define CORE_LOGGER Logger::getInstance("Core") +// Hyperion Daemon +#include <../src/hyperiond/hyperiond.h> + +// settingsManagaer +#include + +// BGEffectHandler +#include + +// CaptureControl (Daemon capture) +#include Hyperion* Hyperion::_hyperion = nullptr; -Hyperion* Hyperion::initInstance(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath) +Hyperion* Hyperion::initInstance( HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath) { if ( Hyperion::_hyperion != nullptr ) throw std::runtime_error("Hyperion::initInstance can be called only one time"); - Hyperion::_hyperion = new Hyperion(qjsonConfig, configFile, rootPath); + Hyperion::_hyperion = new Hyperion(daemon, instance, configFile, rootPath); return Hyperion::_hyperion; } @@ -52,400 +66,74 @@ Hyperion* Hyperion::getInstance() return Hyperion::_hyperion; } -ColorOrder Hyperion::createColorOrder(const QJsonObject &deviceConfig) -{ - return stringToColorOrder(deviceConfig["colorOrder"].toString("rgb")); -} - -ColorAdjustment * Hyperion::createColorAdjustment(const QJsonObject & adjustmentConfig) -{ - const QString id = adjustmentConfig["id"].toString("default"); - - RgbChannelAdjustment * blackAdjustment = createRgbChannelAdjustment(adjustmentConfig, "black" , 0, 0, 0); - RgbChannelAdjustment * whiteAdjustment = createRgbChannelAdjustment(adjustmentConfig, "white" , 255,255,255); - RgbChannelAdjustment * redAdjustment = createRgbChannelAdjustment(adjustmentConfig, "red" , 255, 0, 0); - RgbChannelAdjustment * greenAdjustment = createRgbChannelAdjustment(adjustmentConfig, "green" , 0,255, 0); - RgbChannelAdjustment * blueAdjustment = createRgbChannelAdjustment(adjustmentConfig, "blue" , 0, 0,255); - RgbChannelAdjustment * cyanAdjustment = createRgbChannelAdjustment(adjustmentConfig, "cyan" , 0,255,255); - RgbChannelAdjustment * magentaAdjustment = createRgbChannelAdjustment(adjustmentConfig, "magenta", 255, 0,255); - RgbChannelAdjustment * yellowAdjustment = createRgbChannelAdjustment(adjustmentConfig, "yellow" , 255,255, 0); - RgbTransform * rgbTransform = createRgbTransform(adjustmentConfig); - - ColorAdjustment * adjustment = new ColorAdjustment(); - adjustment->_id = id; - adjustment->_rgbBlackAdjustment = *blackAdjustment; - adjustment->_rgbWhiteAdjustment = *whiteAdjustment; - adjustment->_rgbRedAdjustment = *redAdjustment; - adjustment->_rgbGreenAdjustment = *greenAdjustment; - adjustment->_rgbBlueAdjustment = *blueAdjustment; - adjustment->_rgbCyanAdjustment = *cyanAdjustment; - adjustment->_rgbMagentaAdjustment = *magentaAdjustment; - adjustment->_rgbYellowAdjustment = *yellowAdjustment; - adjustment->_rgbTransform = *rgbTransform; - - // Cleanup the allocated individual adjustments - delete blackAdjustment; - delete whiteAdjustment; - delete redAdjustment; - delete greenAdjustment; - delete blueAdjustment; - delete cyanAdjustment; - delete magentaAdjustment; - delete yellowAdjustment; - delete rgbTransform; - - return adjustment; -} - - -MultiColorAdjustment * Hyperion::createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorConfig) -{ - // Create the result, the transforms are added to this - MultiColorAdjustment * adjustment = new MultiColorAdjustment(ledCnt); - - const QJsonValue adjustmentConfig = colorConfig["channelAdjustment"]; - const QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); - - const QJsonArray & adjustmentConfigArray = adjustmentConfig.toArray(); - for (signed i = 0; i < adjustmentConfigArray.size(); ++i) - { - const QJsonObject & config = adjustmentConfigArray.at(i).toObject(); - ColorAdjustment * colorAdjustment = createColorAdjustment(config); - adjustment->addAdjustment(colorAdjustment); - - const QString ledIndicesStr = config["leds"].toString("").trimmed(); - if (ledIndicesStr.compare("*") == 0) - { - // Special case for indices '*' => all leds - adjustment->setAdjustmentForLed(colorAdjustment->_id, 0, ledCnt-1); - Info(CORE_LOGGER, "ColorAdjustment '%s' => [0; %d]", QSTRING_CSTR(colorAdjustment->_id), ledCnt-1); - continue; - } - - if (!overallExp.exactMatch(ledIndicesStr)) - { - Error(CORE_LOGGER, "Given led indices %d not correct format: %s", i, QSTRING_CSTR(ledIndicesStr)); - continue; - } - - std::stringstream ss; - const QStringList ledIndexList = ledIndicesStr.split(","); - for (int i=0; i 0) - { - ss << ", "; - } - if (ledIndexList[i].contains("-")) - { - QStringList ledIndices = ledIndexList[i].split("-"); - int startInd = ledIndices[0].toInt(); - int endInd = ledIndices[1].toInt(); - - adjustment->setAdjustmentForLed(colorAdjustment->_id, startInd, endInd); - ss << startInd << "-" << endInd; - } - else - { - int index = ledIndexList[i].toInt(); - adjustment->setAdjustmentForLed(colorAdjustment->_id, index, index); - ss << index; - } - } - Info(CORE_LOGGER, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); - } - - return adjustment; -} - -RgbTransform* Hyperion::createRgbTransform(const QJsonObject& colorConfig) -{ - const double backlightThreshold = colorConfig["backlightThreshold"].toDouble(0.0); - const bool backlightColored = colorConfig["backlightColored"].toBool(false); - const double brightness = colorConfig["brightness"].toInt(100); - const double brightnessComp= colorConfig["brightnessCompensation"].toInt(100); - const double gammaR = colorConfig["gammaRed"].toDouble(1.0); - const double gammaG = colorConfig["gammaGreen"].toDouble(1.0); - const double gammaB = colorConfig["gammaBlue"].toDouble(1.0); - - RgbTransform* transform = new RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, brightness, brightnessComp); - return transform; -} - -RgbChannelAdjustment* Hyperion::createRgbChannelAdjustment(const QJsonObject& colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB) -{ - const QJsonArray& channelConfig = colorConfig[channelName].toArray(); - RgbChannelAdjustment* adjustment = new RgbChannelAdjustment( - channelConfig[0].toInt(defaultR), - channelConfig[1].toInt(defaultG), - channelConfig[2].toInt(defaultB), - "ChannelAdjust_"+channelName.toUpper()); - return adjustment; -} - -LedString Hyperion::createLedString(const QJsonValue& ledsConfig, const ColorOrder deviceOrder) -{ - LedString ledString; - const QString deviceOrderStr = colorOrderToString(deviceOrder); - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - int maxLedId = ledConfigArray.size(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - Led led; - led.index = index["index"].toInt(); - led.clone = index["clone"].toInt(-1); - if ( led.clone < -1 || led.clone >= maxLedId ) - { - Warning(CORE_LOGGER, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); - led.clone = -1; - } - - if ( led.clone < 0 ) - { - const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); - const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); - led.minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); - led.maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); - led.minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); - led.maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); - // Fix if the user swapped min and max - if (led.minX_frac > led.maxX_frac) - { - std::swap(led.minX_frac, led.maxX_frac); - } - if (led.minY_frac > led.maxY_frac) - { - std::swap(led.minY_frac, led.maxY_frac); - } - - // Get the order of the rgb channels for this led (default is device order) - led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); - ledString.leds().push_back(led); - } - } - - // Make sure the leds are sorted (on their indices) - std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); - return ledString; -} - -LedString Hyperion::createLedStringClone(const QJsonValue& ledsConfig, const ColorOrder deviceOrder) -{ - LedString ledString; - const QString deviceOrderStr = colorOrderToString(deviceOrder); - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - int maxLedId = ledConfigArray.size(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - Led led; - led.index = index["index"].toInt(); - led.clone = index["clone"].toInt(-1); - if ( led.clone < -1 || led.clone >= maxLedId ) - { - Warning(CORE_LOGGER, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); - led.clone = -1; - } - - if ( led.clone >= 0 ) - { - Debug(CORE_LOGGER, "LED %d: clone from led %d", led.index, led.clone); - led.minX_frac = 0; - led.maxX_frac = 0; - led.minY_frac = 0; - led.maxY_frac = 0; - // Get the order of the rgb channels for this led (default is device order) - led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); - - ledString.leds().push_back(led); - } - - } - - // Make sure the leds are sorted (on their indices) - std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); - return ledString; -} - -QSize Hyperion::getLedLayoutGridSize(const QJsonValue& ledsConfig) -{ - std::vector midPointsX; - std::vector midPointsY; - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - if (index["clone"].toInt(-1) < 0 ) - { - const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); - const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); - double minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); - double maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); - double minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); - double maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); - // Fix if the user swapped min and max - if (minX_frac > maxX_frac) - { - std::swap(minX_frac, maxX_frac); - } - if (minY_frac > maxY_frac) - { - std::swap(minY_frac, maxY_frac); - } - - // calculate mid point and make grid calculation - midPointsX.push_back( int(1000.0*(minX_frac + maxX_frac) / 2.0) ); - midPointsY.push_back( int(1000.0*(minY_frac + maxY_frac) / 2.0) ); - } - } - - // remove duplicates - std::sort(midPointsX.begin(), midPointsX.end()); - midPointsX.erase(std::unique(midPointsX.begin(), midPointsX.end()), midPointsX.end()); - std::sort(midPointsY.begin(), midPointsY.end()); - midPointsY.erase(std::unique(midPointsY.begin(), midPointsY.end()), midPointsY.end()); - - QSize gridSize( midPointsX.size(), midPointsY.size() ); - Debug(CORE_LOGGER, "led layout grid: %dx%d", gridSize.width(), gridSize.height()); - - return gridSize; -} - - - -LinearColorSmoothing * Hyperion::createColorSmoothing(const QJsonObject & smoothingConfig, LedDevice* leddevice){ - QString type = smoothingConfig["type"].toString("linear").toLower(); - LinearColorSmoothing * device = nullptr; - type = "linear"; // TODO currently hardcoded type, delete it if we have more types - - if (type == "linear") - { - Info( CORE_LOGGER, "Creating linear smoothing"); - device = new LinearColorSmoothing( - leddevice, - smoothingConfig["updateFrequency"].toDouble(25.0), - smoothingConfig["time_ms"].toInt(200), - smoothingConfig["updateDelay"].toInt(0), - smoothingConfig["continuousOutput"].toBool(true) - ); - } - else - { - Error(CORE_LOGGER, "Smoothing disabled, because of unknown type '%s'.", QSTRING_CSTR(type)); - } - - device->setEnable(smoothingConfig["enable"].toBool(true)); - InfoIf(!device->enabled(), CORE_LOGGER,"Smoothing disabled"); - - Q_ASSERT(device != nullptr); - return device; -} - -MessageForwarder * Hyperion::createMessageForwarder(const QJsonObject & forwarderConfig) -{ - MessageForwarder * forwarder = new MessageForwarder(); - if ( !forwarderConfig.isEmpty() && forwarderConfig["enable"].toBool(true) ) - { - if ( !forwarderConfig["json"].isNull() && forwarderConfig["json"].isArray() ) - { - const QJsonArray & addr = forwarderConfig["json"].toArray(); - for (signed i = 0; i < addr.size(); ++i) - { - Info(CORE_LOGGER, "Json forward to %s", addr.at(i).toString().toStdString().c_str()); - forwarder->addJsonSlave(addr[i].toString()); - } - } - - if ( !forwarderConfig["proto"].isNull() && forwarderConfig["proto"].isArray() ) - { - const QJsonArray & addr = forwarderConfig["proto"].toArray(); - for (signed i = 0; i < addr.size(); ++i) - { - Info(CORE_LOGGER, "Proto forward to %s", addr.at(i).toString().toStdString().c_str()); - forwarder->addProtoSlave(addr[i].toString()); - } - } - } - - return forwarder; -} - MessageForwarder * Hyperion::getForwarder() { return _messageForwarder; } -Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile, const QString rootPath) - : _ledString(createLedString(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) - , _ledStringClone(createLedStringClone(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) +Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath) + : _daemon(daemon) + , _settingsManager(new SettingsManager(this, instance, configFile)) + , _componentRegister(this) + , _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) + , _ledStringClone(hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) + , _imageProcessor(new ImageProcessor(_ledString, this)) , _muxer(_ledString.leds().size()) - , _raw2ledAdjustment(createLedColorsAdjustment(_ledString.leds().size(), qjsonConfig["color"].toObject())) + , _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object())) , _effectEngine(nullptr) - , _messageForwarder(createMessageForwarder(qjsonConfig["forwarder"].toObject())) - , _qjsonConfig(qjsonConfig) + , _messageForwarder(new MessageForwarder(this, getSetting(settings::NETFORWARD))) , _configFile(configFile) , _rootPath(rootPath) - , _timer() - , _timerBonjourResolver() - , _log(CORE_LOGGER) - , _hwLedCount(_ledString.leds().size()) - , _sourceAutoSelectEnabled(true) + , _log(Logger::getInstance("HYPERION")) + , _hwLedCount() , _configHash() - , _ledGridSize(getLedLayoutGridSize(qjsonConfig["leds"])) + , _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array())) , _prevCompId(hyperion::COMP_INVALID) - , _bonjourBrowser(this) - , _bonjourResolver(this) - , _videoMode(VIDEO_2D) + , _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK) { - if (!_raw2ledAdjustment->verifyAdjustments()) { - throw std::runtime_error("Color adjustment incorrectly set"); + Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!"); } + + // handle hwLedCount + _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // init colororder vector + for (Led& led : _ledString.leds()) + { + _ledStringColorOrder.push_back(led.colorOrder); + } + for (Led& led : _ledStringClone.leds()) + { + _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + } + + // connect Hyperion::update with Muxer visible priority changes as muxer updates independent + connect(&_muxer, &PriorityMuxer::visiblePriorityChanged, this, &Hyperion::update); + + // listens for ComponentRegister changes of COMP_ALL to perform core enable/disable actions + connect(&_componentRegister, &ComponentRegister::updatedComponentState, this, &Hyperion::updatedComponentState); + + // listen for settings updates of this instance (LEDS & COLOR) + connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate); + // set color correction activity state - const QJsonObject& color = qjsonConfig["color"].toObject(); - - _bonjourBrowser.browseForServiceType(QLatin1String("_hyperiond-http._tcp")); - connect(&_bonjourBrowser, SIGNAL(currentBonjourRecordsChanged(const QList&)),this, SLOT(currentBonjourRecordsChanged(const QList &))); - connect(&_bonjourResolver, SIGNAL(bonjourRecordResolved(const QHostInfo &, int)), this, SLOT(bonjourRecordResolved(const QHostInfo &, int))); - - // initialize the image processor factory - _ledMAppingType = ImageProcessor::mappingTypeToInt(color["imageToLedMappingType"].toString()); - ImageProcessorFactory::getInstance().init(_ledString, qjsonConfig["blackborderdetector"].toObject(),_ledMAppingType ); - - getComponentRegister().componentStateChanged(hyperion::COMP_FORWARDER, _messageForwarder->forwardingEnabled()); + const QJsonObject color = getSetting(settings::COLOR).object(); // initialize leddevices - _device = LedDeviceFactory::construct(qjsonConfig["device"].toObject(),_hwLedCount); - _deviceSmooth = createColorSmoothing(qjsonConfig["smoothing"].toObject(), _device); - getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, _deviceSmooth->componentState()); + const QJsonObject ledDevice = getSetting(settings::DEVICE).object(); + ledDevice["currentLedCount"] = int(_hwLedCount); // Inject led count info + + _device = LedDeviceFactory::construct(ledDevice); + _deviceSmooth = new LinearColorSmoothing(_device, getSetting(settings::SMOOTHING), this); + connect(this, &Hyperion::settingsChanged, _deviceSmooth, &LinearColorSmoothing::handleSettingsUpdate); + getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); - _deviceSmooth->addConfig(true); // add pause to config 1 - - // setup the timer - _timer.setSingleShot(true); - QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(update())); - - _timerBonjourResolver.setSingleShot(false); - _timerBonjourResolver.setInterval(1000); - QObject::connect(&_timerBonjourResolver, SIGNAL(timeout()), this, SLOT(bonjourResolve())); - _timerBonjourResolver.start(); - - // create the effect engine, must be initialized after smoothing! - _effectEngine = new EffectEngine(this,qjsonConfig["effects"].toObject()); - - const QJsonObject& device = qjsonConfig["device"].toObject(); - unsigned int hwLedCount = device["ledCount"].toInt(getLedCount()); - _hwLedCount = qMax(hwLedCount, getLedCount()); - Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount); - WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section"); + // create the effect engine and pipe the updateEmit; must be initialized after smoothing! + _effectEngine = new EffectEngine(this,getSetting(settings::EFFECTS).object()); + connect(_effectEngine, &EffectEngine::effectListUpdated, this, &Hyperion::effectListUpdated); // setup config state checks and initial shot checkConfigState(); @@ -455,31 +143,27 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile, con } else { + _cTimer = new QTimer(this); Warning(_log,"Filesystem Observer failed for file: %s, use fallback timer", _configFile.toStdString().c_str()); - QObject::connect(&_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); - _cTimer.start(2000); + connect(_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); + _cTimer->start(2000); } - // pipe muxer signal for effect/color timerunner to hyperionStateChanged slot - QObject::connect(&_muxer, &PriorityMuxer::timerunner, this, &Hyperion::hyperionStateChanged); + // initial startup effect + hyperion::handleInitialEffect(this, getSetting(settings::FGEFFECT).object()); + // handle background effect + _BGEffectHandler = new BGEffectHandler(this); - // prepare processing of hyperionStateChanged for forced serverinfo - connect(&_fsi_timer, SIGNAL(timeout()), this, SLOT(hyperionStateChanged())); - _fsi_timer.setSingleShot(true); - _fsi_blockTimer.setSingleShot(true); + // create the Daemon capture interface + _captureCont = new CaptureCont(this); - // initialize the leds + // if there is no startup / background eff and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a prioritiy change) update(); } -int Hyperion::getLatchTime() const +Hyperion::~Hyperion() { - return _device->getLatchTime(); -} - -unsigned Hyperion::addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) -{ - return _deviceSmooth->addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); + freeObjects(false); } void Hyperion::freeObjects(bool emitCloseSignal) @@ -493,17 +177,138 @@ void Hyperion::freeObjects(bool emitCloseSignal) emit closing(); } - // delete components on exit of hyperion core + delete _captureCont; delete _effectEngine; + //delete _deviceSmooth; delete _device; delete _raw2ledAdjustment; delete _messageForwarder; + delete _settingsManager; } -Hyperion::~Hyperion() +void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - freeObjects(false); + if(type == settings::COLOR) + { + const QJsonObject obj = config.object(); + // change in color recreate ledAdjustments + delete _raw2ledAdjustment; + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), obj); + + if (!_raw2ledAdjustment->verifyAdjustments()) + { + Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!"); + } + } + else if(type == settings::LEDS) + { + const QJsonArray leds = config.array(); + + // lock update() + _lockUpdate = true; + // stop and cache all running effects, as effects depend heavily on ledlayout + _effectEngine->cacheRunningEffects(); + + // ledstring, clone, img processor, muxer, ledGridSize (eff engine image based effects), _ledBuffer and ByteOrder of ledstring + _ledString = hyperion::createLedString(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object())); + _ledStringClone = hyperion::createLedStringClone(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object())); + _imageProcessor->setLedString(_ledString); + _muxer.updateLedColorsLength(_ledString.leds().size()); + _ledGridSize = hyperion::getLedLayoutGridSize(leds); + + std::vector color(_ledString.leds().size(), ColorRgb{0,0,0}); + _ledBuffer = color; + + _ledStringColorOrder.clear(); + for (Led& led : _ledString.leds()) + { + _ledStringColorOrder.push_back(led.colorOrder); + } + for (Led& led : _ledStringClone.leds()) + { + _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + } + + // handle hwLedCount update + _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // update led count in device + _device->setLedCount(_hwLedCount); + + // change in leds are also reflected in adjustment + delete _raw2ledAdjustment; + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()); + + // start cached effects + _effectEngine->startCachedEffects(); + + // unlock + _lockUpdate = false; + } + else if(type == settings::DEVICE) + { + _lockUpdate = true; + const QJsonObject dev = config.object(); + + // handle hwLedCount update + _hwLedCount = qMax(unsigned(dev["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // force ledString update, if device ByteOrder changed + if(_device->getColorOrder() != dev["colorOrder"].toString("rgb")) + { + _ledString = hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(dev)); + _ledStringClone = hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(dev)); + _imageProcessor->setLedString(_ledString); + } + + /* // reinit led device type on change + if(_device->getActiveDevice() != dev["type"].toString("file").toLower()) + { + } + // update led count + _device->setLedCount(_hwLedCount); + */ + // do always reinit + // TODO segfaulting in LinearColorSmoothing::queueColor triggert from QTimer because of device->setLEdValues (results from gdb debugging and testing) + bool wasEnabled = _deviceSmooth->enabled(); + _deviceSmooth->stopTimer(); + delete _device; + dev["currentLedCount"] = int(_hwLedCount); // Inject led count info + _device = LedDeviceFactory::construct(dev); + getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); + if(wasEnabled) + _deviceSmooth->startTimerDelayed(); + _lockUpdate = false; + } + // update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer + update(); +} + +QJsonDocument Hyperion::getSetting(const settings::type& type) +{ + return _settingsManager->getSetting(type); +} + +bool Hyperion::saveSettings(QJsonObject config, const bool& correct) +{ + return _settingsManager->saveSettings(config, correct); +} + +QString Hyperion::getConfigFileName() const +{ + QFileInfo cF(_configFile); + return cF.fileName(); +} + +int Hyperion::getLatchTime() const +{ + return _device->getLatchTime(); +} + +unsigned Hyperion::addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) +{ + return _deviceSmooth->addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); } unsigned Hyperion::getLedCount() const @@ -511,53 +316,6 @@ unsigned Hyperion::getLedCount() const return _ledString.leds().size(); } -void Hyperion::currentBonjourRecordsChanged(const QList &list) -{ - _hyperionSessions.clear(); - for ( auto rec : list ) - { - _hyperionSessions.insert(rec.serviceName, rec); - } -} - -void Hyperion::bonjourRecordResolved(const QHostInfo &hostInfo, int port) -{ - if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve)) - { - QString host = hostInfo.hostName(); - QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain; - if (host.endsWith("."+domain)) - { - host.remove(host.length()-domain.length()-1,domain.length()+1); - } - _hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host; - _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(); - } -} - -void Hyperion::bonjourResolve() -{ - for(auto key : _hyperionSessions.keys()) - { - if (_hyperionSessions[key].port < 0) - { - _bonjourCurrentServiceToResolve = key; - _bonjourResolver.resolveBonjourRecord(_hyperionSessions[key]); - break; - } - } -} - -Hyperion::BonjourRegister Hyperion::getHyperionSessions() -{ - return _hyperionSessions; -} - void Hyperion::checkConfigState(QString cfile) { // Check config modifications @@ -578,7 +336,6 @@ void Hyperion::checkConfigState(QString cfile) if(_prevConfigMod != _configMod) { - emit hyperionStateChanged(); _prevConfigMod = _configMod; } @@ -589,67 +346,30 @@ void Hyperion::checkConfigState(QString cfile) 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 ); - - for(auto key : _priorityRegister.keys()) - { - WarningIf( ( key != name && _priorityRegister.value(key) == priority), _log, - "Input source '%s' uses same priority channel (%d) as '%s'.", QSTRING_CSTR(name), priority, QSTRING_CSTR(key)); - } - - _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) { - _sourceAutoSelectEnabled = enabled; - if (! _sourceAutoSelectEnabled) - { - setCurrentSourcePriority(_muxer.getCurrentPriority()); - } - update(); - DebugIf( !_sourceAutoSelectEnabled, _log, "source auto select is disabled"); - InfoIf(_sourceAutoSelectEnabled, _log, "set current input source to auto select"); + if(_muxer.setSourceAutoSelectEnabled(enabled)) + update(); } bool Hyperion::setCurrentSourcePriority(int priority ) { - bool priorityValid = _muxer.hasPriority(priority); - if (priorityValid) - { - DebugIf(_sourceAutoSelectEnabled, _log, "source auto select is disabled"); - _sourceAutoSelectEnabled = false; - _currentSourcePriority = priority; - Info(_log, "set current input source to priority channel %d", _currentSourcePriority); - } + return _muxer.setPriority(priority); +} - return priorityValid; +bool Hyperion::sourceAutoSelectEnabled() +{ + return _muxer.isSourceAutoSelectEnabled(); } void Hyperion::setComponentState(const hyperion::Components component, const bool state) { switch (component) { - case hyperion::COMP_SMOOTHING: - _deviceSmooth->setEnable(state); - getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, _deviceSmooth->componentState()); - break; - case hyperion::COMP_LEDDEVICE: _device->setEnable(state); getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); @@ -660,45 +380,61 @@ void Hyperion::setComponentState(const hyperion::Components component, const boo } } -void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, bool clearEffects) +void Hyperion::registerInput(const int priority, const hyperion::Components& component, const QString& origin, const QString& owner, unsigned smooth_cfg) { - // create led output + _muxer.registerInput(priority, component, origin, owner, smooth_cfg); +} + +const bool Hyperion::setInput(const int priority, const std::vector& ledColors, int timeout_ms, const bool& clearEffect) +{ + if(_muxer.setInput(priority, ledColors, timeout_ms)) + { + // clear effect if this call does not come from an effect + if(clearEffect) + _effectEngine->channelCleared(priority); + + // if this priority is visible, update immediately + if(priority == _muxer.getCurrentPriority()) + update(); + + return true; + } + return false; +} + +const bool Hyperion::setInputImage(const int priority, const Image& image, int64_t timeout_ms, const bool& clearEffect) +{ + if(_muxer.setInputImage(priority, image, timeout_ms)) + { + // clear effect if this call does not come from an effect + if(clearEffect) + _effectEngine->channelCleared(priority); + + // if this priority is visible, update immediately + if(priority == _muxer.getCurrentPriority()) + { + update(); + } + + return true; + } + return false; +} + +void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, const QString& origin, bool clearEffects) +{ + // clear effect if this call does not come from an effect + if(clearEffects) + _effectEngine->channelCleared(priority); + + // create led vector from single color std::vector ledColors(_ledString.leds().size(), color); - // set colors - setColors(priority, ledColors, timeout_ms, clearEffects, hyperion::COMP_COLOR); -} + // register color + registerInput(priority, hyperion::COMP_COLOR, origin); -void Hyperion::setColors(int priority, const std::vector& ledColors, const int timeout_ms, bool clearEffects, hyperion::Components component, const QString origin, unsigned smoothCfg) -{ - // clear effects if this call does not come from an effect - if (clearEffects) - { - _effectEngine->channelCleared(priority); - } - - if (timeout_ms > 0) - { - const uint64_t timeoutTime = QDateTime::currentMSecsSinceEpoch() + timeout_ms; - _muxer.setInput(priority, ledColors, timeoutTime, component, origin, smoothCfg); - } - else - { - _muxer.setInput(priority, ledColors, -1, component, origin, smoothCfg); - } - - if (! _sourceAutoSelectEnabled || priority == _muxer.getCurrentPriority()) - { - update(); - } -} - -void Hyperion::setImage(int priority, const Image & image, int duration_ms) -{ - if (priority == getCurrentPriority()) - { - emit emitImage(priority, image, duration_ms); - } + // write color to muxer + setInput(priority, ledColors, timeout_ms); } const QStringList & Hyperion::getAdjustmentIds() const @@ -713,38 +449,25 @@ ColorAdjustment * Hyperion::getAdjustment(const QString& id) void Hyperion::adjustmentsUpdated() { + emit adjustmentChanged(); update(); } -void Hyperion::clear(int priority) +const bool Hyperion::clear(int priority) { - if (_muxer.hasPriority(priority)) - { - _muxer.clearInput(priority); - if (!_sourceAutoSelectEnabled && _currentSourcePriority == priority ) - { - setSourceAutoSelectEnabled(true); - } - - // update leds if necessary - if (priority < _muxer.getCurrentPriority()) - { - update(); - } - } - // send clear signal to the effect engine // (outside the check so the effect gets cleared even when the effect is not sending colors) _effectEngine->channelCleared(priority); + + if(_muxer.clearInput(priority)) + return true; + + return false; } void Hyperion::clearall(bool forceClearAll) { _muxer.clearAll(forceClearAll); - setSourceAutoSelectEnabled(true); - - // update leds - update(); // send clearall signal to the effect engine _effectEngine->allChannelsCleared(); @@ -752,8 +475,7 @@ void Hyperion::clearall(bool forceClearAll) int Hyperion::getCurrentPriority() const { - - return _sourceAutoSelectEnabled || !_muxer.hasPriority(_currentSourcePriority) ? _muxer.getCurrentPriority() : _currentSourcePriority; + return _muxer.getCurrentPriority(); } bool Hyperion::isCurrentPriority(const int priority) const @@ -766,7 +488,7 @@ QList Hyperion::getActivePriorities() const return _muxer.getPriorities(); } -const Hyperion::InputInfo &Hyperion::getPriorityInfo(const int priority) const +const Hyperion::InputInfo Hyperion::getPriorityInfo(const int priority) const { return _muxer.getInputInfo(priority); } @@ -791,6 +513,11 @@ const std::list & Hyperion::getEffectSchemas() return _effectEngine->getEffectSchemas(); } +const QJsonObject& Hyperion::getQJsonConfig() +{ + return _settingsManager->getSettings(); +} + int Hyperion::setEffect(const QString &effectName, int priority, int timeout, const QString & origin) { return _effectEngine->runEffect(effectName, priority, timeout, origin); @@ -801,68 +528,96 @@ int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin); } -void Hyperion::setLedMappingType(int mappingType) +void Hyperion::setLedMappingType(const int& mappingType) { - _ledMAppingType = mappingType; - emit imageToLedsMappingChanged(mappingType); + if(mappingType != _imageProcessor->getUserLedMappingType()) + { + _imageProcessor->setLedMappingType(mappingType); + emit imageToLedsMappingChanged(mappingType); + } } -void Hyperion::setVideoMode(VideoMode mode) +const int & Hyperion::getLedMappingType() +{ + return _imageProcessor->getUserLedMappingType(); +} + +void Hyperion::setVideoMode(const VideoMode& mode) { - _videoMode = mode; emit videoMode(mode); } - -void Hyperion::hyperionStateChanged() +const VideoMode & Hyperion::getCurrentVideoMode() { - if(_fsi_blockTimer.isActive()) + return _daemon->getVideoMode(); +} + +const QString & Hyperion::getActiveDevice() +{ + return _device->getActiveDevice(); +} + +void Hyperion::updatedComponentState(const hyperion::Components comp, const bool state) +{ + if(comp == hyperion::COMP_ALL) { - _fsi_timer.start(300); - } - else - { - emit sendServerInfo(); - _fsi_blockTimer.start(250); + if(state) + { + // first muxer to update all inputs + _muxer.setEnable(state); + } + else + { + _muxer.setEnable(state); + } } } void Hyperion::update() { - // Update the muxer, cleaning obsolete priorities - _muxer.setCurrentTime(QDateTime::currentMSecsSinceEpoch()); + if(_lockUpdate) + return; + + // the ledbuffer resize for hwledcount needs to be reverted + if(_hwLedCount > _ledBuffer.size()) + _ledBuffer.resize(getLedCount()); // Obtain the current priority channel - int priority = _sourceAutoSelectEnabled || !_muxer.hasPriority(_currentSourcePriority) ? _muxer.getCurrentPriority() : _currentSourcePriority; - const PriorityMuxer::InputInfo & priorityInfo = _muxer.getInputInfo(priority); - - // copy ledcolors to local buffer - _ledBuffer.reserve(_hwLedCount); - _ledBuffer = priorityInfo.ledColors; - - // emit rawLedColors before transform - emit rawLedColors(_ledBuffer); + int priority = _muxer.getCurrentPriority(); + const PriorityMuxer::InputInfo priorityInfo = _muxer.getInputInfo(priority); + // eval comp change + bool compChanged = false; if (priorityInfo.componentId != _prevCompId) { - bool backlightEnabled = (priorityInfo.componentId != hyperion::COMP_COLOR && priorityInfo.componentId != hyperion::COMP_EFFECT); - _raw2ledAdjustment->setBacklightEnabled(backlightEnabled); + compChanged = true; _prevCompId = priorityInfo.componentId; } - _raw2ledAdjustment->applyAdjustment(_ledBuffer); - // init colororder vector, if empty - if (_ledStringColorOrder.empty()) + // copy image & process OR copy ledColors from muxer + Image image = priorityInfo.image; + if(image.size() > 3) { - for (Led& led : _ledString.leds()) + emit currentImage(image); + // disable the black border detector for effects and ledmapping to 0 + if(compChanged) { - _ledStringColorOrder.push_back(led.colorOrder); - } - for (Led& led : _ledStringClone.leds()) - { - _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + _imageProcessor->setBlackbarDetectDisable((_prevCompId == hyperion::COMP_EFFECT || _prevCompId == hyperion::COMP_GRABBER)); + _imageProcessor->setHardLedMappingType((_prevCompId == hyperion::COMP_EFFECT) ? 0 : -1); } + _imageProcessor->process(image, _ledBuffer); } + else + { + _ledBuffer = priorityInfo.ledColors; + } + // copy rawLedColors before adjustments + _rawLedBuffer = _ledBuffer; + + // apply adjustments + if(compChanged) + _raw2ledAdjustment->setBacklightEnabled((_prevCompId != hyperion::COMP_COLOR && _prevCompId != hyperion::COMP_EFFECT)); + _raw2ledAdjustment->applyAdjustment(_ledBuffer); // insert cloned leds into buffer for (Led& led : _ledStringClone.leds()) @@ -873,8 +628,6 @@ void Hyperion::update() int i = 0; for (ColorRgb& color : _ledBuffer) { - //const ColorOrder ledColorOrder = leds.at(i).colorOrder; - // correct the color byte order switch (_ledStringColorOrder.at(i)) { @@ -902,7 +655,7 @@ void Hyperion::update() } i++; } - + // fill aditional hw leds with black if ( _hwLedCount > _ledBuffer.size() ) { _ledBuffer.resize(_hwLedCount, ColorRgb::BLACK); @@ -920,17 +673,4 @@ void Hyperion::update() if (! _deviceSmooth->enabled()) _device->setLedValues(_ledBuffer); } - - // Start the timeout-timer - if (priorityInfo.timeoutTime_ms <= 0) - { - _timer.stop(); - } - else - { - int timeout_ms = qMax(0, int(priorityInfo.timeoutTime_ms - QDateTime::currentMSecsSinceEpoch())); - // qMin() 200ms forced refresh if color is active to update priorityMuxer properly for forced serverinfo push - _timer.start(qMin(timeout_ms, 200)); - } - } diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index 1bebddc5..426328f8 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -9,27 +9,56 @@ using namespace hyperion; -ImageProcessor::ImageProcessor(const LedString& ledString, const QJsonObject & blackborderConfig) - : QObject() +// global transform method +int ImageProcessor::mappingTypeToInt(QString mappingType) +{ + if (mappingType == "unicolor_mean" ) + return 1; + + return 0; +} +// global transform method +QString ImageProcessor::mappingTypeToStr(int mappingType) +{ + if (mappingType == 1 ) + return "unicolor_mean"; + + return "multicolor_mean"; +} + +ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion) + : QObject(hyperion) , _log(Logger::getInstance("BLACKBORDER")) , _ledString(ledString) - , _borderProcessor(new BlackBorderProcessor(blackborderConfig) ) + , _borderProcessor(new BlackBorderProcessor(hyperion, this)) , _imageToLeds(nullptr) , _mappingType(0) + , _userMappingType(0) + , _hardMappingType(0) + , _hyperion(hyperion) { -// this is when we want to change the mapping for all input sources -// connect(Hyperion::getInstance(), SIGNAL(imageToLedsMappingChanged(int)), this, SLOT(setLedMappingType(int))); + // init + handleSettingsUpdate(settings::COLOR, _hyperion->getSetting(settings::COLOR)); + // listen for changes in color - ledmapping + connect(_hyperion, &Hyperion::settingsChanged, this, &ImageProcessor::handleSettingsUpdate); } ImageProcessor::~ImageProcessor() { delete _imageToLeds; - delete _borderProcessor; } -unsigned ImageProcessor::getLedCount() const +void ImageProcessor::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - return _ledString.leds().size(); + if(type == settings::COLOR) + { + const QJsonObject& obj = config.object(); + int newType = mappingTypeToInt(obj["imageToLedMappingType"].toString()); + if(_userMappingType != newType) + { + setLedMappingType(newType); + } + } } void ImageProcessor::setSize(const unsigned width, const unsigned height) @@ -47,9 +76,24 @@ void ImageProcessor::setSize(const unsigned width, const unsigned height) _imageToLeds = (width>0 && height>0) ? (new ImageToLedsMap(width, height, 0, 0, _ledString.leds())) : nullptr; } -void ImageProcessor::enableBlackBorderDetector(bool enable) +void ImageProcessor::setLedString(const LedString& ledString) { - _borderProcessor->setEnabled(enable); + _ledString = ledString; + + // get current width/height + const unsigned width = _imageToLeds->width(); + const unsigned height = _imageToLeds->height(); + + // Clean up the old buffer and mapping + delete _imageToLeds; + + // Construct a new buffer and mapping + _imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds()); +} + +void ImageProcessor::setBlackbarDetectDisable(bool enable) +{ + _borderProcessor->setHardDisable(enable); } bool ImageProcessor::blackBorderDetectorEnabled() @@ -59,29 +103,23 @@ bool ImageProcessor::blackBorderDetectorEnabled() void ImageProcessor::setLedMappingType(int mapType) { - Debug(_log, "set led mapping to type %d", mapType); - _mappingType = mapType; + // if the _hardMappingType is >-1 we aren't allowed to overwrite it + _userMappingType = mapType; + Debug(_log, "set user led mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType))); + if(_hardMappingType == -1) + { + _mappingType = mapType; + } } -int ImageProcessor::ledMappingType() +void ImageProcessor::setHardLedMappingType(int mapType) { - return _mappingType; -} - -int ImageProcessor::mappingTypeToInt(QString mappingType) -{ - if (mappingType == "unicolor_mean" ) - return 1; - - return 0; -} - -QString ImageProcessor::mappingTypeToStr(int mappingType) -{ - if (mappingType == 1 ) - return "unicolor_mean"; - - return "multicolor_mean"; + // force the maptype, if set to -1 we use the last requested _userMappingType + _hardMappingType = mapType; + if(mapType == -1) + _mappingType = _userMappingType; + else + _mappingType = mapType; } bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &hscanEnd, double &vscanBegin, double &vscanEnd) const @@ -97,4 +135,3 @@ bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &h return false; } - diff --git a/libsrc/hyperion/ImageProcessorFactory.cpp b/libsrc/hyperion/ImageProcessorFactory.cpp deleted file mode 100644 index 7b7cfbbe..00000000 --- a/libsrc/hyperion/ImageProcessorFactory.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Hyperion includes -#include -#include - -ImageProcessorFactory& ImageProcessorFactory::getInstance() -{ - static ImageProcessorFactory instance; - // Return the singleton instance - return instance; -} - -void ImageProcessorFactory::init(const LedString& ledString, const QJsonObject & blackborderConfig, int mappingType) -{ - _ledString = ledString; - _blackborderConfig = blackborderConfig; - _mappingType = mappingType; -} - -ImageProcessor* ImageProcessorFactory::newImageProcessor() const -{ - ImageProcessor* ip = new ImageProcessor(_ledString, _blackborderConfig); - ip->setLedMappingType(_mappingType); - - return ip; -} diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index b4404be9..c243de17 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -1,5 +1,6 @@ // Qt includes #include +#include #include "LinearColorSmoothing.h" #include @@ -8,38 +9,60 @@ using namespace hyperion; -// ledUpdateFrequency_hz = 0 > cause divide by zero! -LinearColorSmoothing::LinearColorSmoothing( LedDevice * ledDevice, double ledUpdateFrequency_hz, int settlingTime_ms, unsigned updateDelay, bool continuousOutput) +LinearColorSmoothing::LinearColorSmoothing( LedDevice * ledDevice, const QJsonDocument& config, Hyperion* hyperion) : LedDevice() , _ledDevice(ledDevice) - , _updateInterval(1000 / ledUpdateFrequency_hz) - , _settlingTime(settlingTime_ms) - , _timer() - , _outputDelay(updateDelay) + , _log(Logger::getInstance("SMOOTHING")) + , _hyperion(hyperion) + , _updateInterval(1000) + , _settlingTime(200) + , _timer(new QTimer(this)) + , _outputDelay(0) , _writeToLedsEnable(true) - , _continuousOutput(continuousOutput) + , _continuousOutput(false) , _pause(false) , _currentConfigId(0) { - _log = Logger::getInstance("Smoothing"); - _timer.setSingleShot(false); - _timer.setInterval(_updateInterval); + Debug(_log, "Instance created"); + + // set initial state to true, as LedDevice::enabled() is true by default + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, true); + + // init cfg 0 (default) + _cfgList.append({false, 200, 25, 0}); + handleSettingsUpdate(settings::SMOOTHING, config); - selectConfig( addConfig(_settlingTime, ledUpdateFrequency_hz, updateDelay) ); - // add pause on cfg 1 - SMOOTHING_CFG cfg = {true, 100, 50, 0}; + SMOOTHING_CFG cfg = {true}; _cfgList.append(cfg); - Info( _log, "smoothing cfg %d: pause", _cfgList.count()-1); - connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); + // listen for comp changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &LinearColorSmoothing::componentStateChange); + // timer + connect(_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); } LinearColorSmoothing::~LinearColorSmoothing() { // Make sure to switch off the underlying led-device (because switchOff is no longer forwarded) _ledDevice->switchOff(); - delete _ledDevice; +} + +void LinearColorSmoothing::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::SMOOTHING) + { + QJsonObject obj = config.object(); + _continuousOutput = obj["continuousOutput"].toBool(true); + SMOOTHING_CFG cfg = {false, obj["time_ms"].toInt(200), unsigned(1000.0/obj["updateFrequency"].toDouble(25.0)), unsigned(obj["updateDelay"].toInt(0))}; + _cfgList[0] = cfg; + // if current id is 0, we need to apply the settings (forced) + if(!_currentConfigId) + selectConfig(0, true); + + if(enabled() != obj["enable"].toBool(true)) + setEnable(obj["enable"].toBool(true)); + } } int LinearColorSmoothing::write(const std::vector &ledValues) @@ -53,7 +76,7 @@ int LinearColorSmoothing::write(const std::vector &ledValues) _previousTime = QDateTime::currentMSecsSinceEpoch(); _previousValues = ledValues; - _timer.start(); + _timer->start(); } else { @@ -150,6 +173,11 @@ void LinearColorSmoothing::queueColors(const std::vector & ledColors) } } +void LinearColorSmoothing::componentStateChange(const hyperion::Components component, const bool state) +{ + if(component == hyperion::COMP_SMOOTHING) + setEnable(state); +} void LinearColorSmoothing::setEnable(bool enable) { @@ -157,9 +185,11 @@ void LinearColorSmoothing::setEnable(bool enable) if (!enable) { - _timer.stop(); + _timer->stop(); _previousValues.clear(); } + // update comp register + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, enable); } void LinearColorSmoothing::setPause(bool pause) @@ -171,14 +201,14 @@ unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFr { SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay}; _cfgList.append(cfg); - - Info( _log, "smoothing cfg %d: interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _cfgList.count()-1, cfg.updateInterval, cfg.settlingTime, cfg.outputDelay ); + + //Debug( _log, "smoothing cfg %d: interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _cfgList.count()-1, cfg.updateInterval, cfg.settlingTime, cfg.outputDelay ); return _cfgList.count() - 1; } -bool LinearColorSmoothing::selectConfig(unsigned cfg) +bool LinearColorSmoothing::selectConfig(unsigned cfg, const bool& force) { - if (_currentConfigId == cfg) + if (_currentConfigId == cfg && !force) { return true; } @@ -191,20 +221,34 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg) if (_cfgList[cfg].updateInterval != _updateInterval) { - _timer.stop(); + _timer->stop(); _updateInterval = _cfgList[cfg].updateInterval; - _timer.setInterval(_updateInterval); - _timer.start(); + _timer->setInterval(_updateInterval); + _timer->start(); } _currentConfigId = cfg; - InfoIf( enabled() && !_pause, _log, "set smoothing cfg: %d, interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _currentConfigId, _updateInterval, _settlingTime, _outputDelay ); + //DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %d, interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _currentConfigId, _updateInterval, _settlingTime, _outputDelay ); InfoIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); return true; } - + // reset to default _currentConfigId = 0; return false; } - + +void LinearColorSmoothing::startTimerDelayed() +{ + QTimer::singleShot(500, this, SLOT(delayStartTimer())); +} + +void LinearColorSmoothing::stopTimer() +{ + _timer->stop(); +} + +void LinearColorSmoothing::delayStartTimer() +{ + _timer->start(); +} diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/libsrc/hyperion/LinearColorSmoothing.h index 34aff620..97bb846c 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/libsrc/hyperion/LinearColorSmoothing.h @@ -5,13 +5,19 @@ // Qt includes -#include #include // hyperion incluse #include #include +// settings +#include + +class QTimer; +class Logger; +class Hyperion; + /// Linear Smooting class /// /// This class processes the requested led values and forwards them to the device after applying @@ -23,10 +29,10 @@ class LinearColorSmoothing : public LedDevice public: /// Constructor /// @param LedDevice the led device - /// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz) - /// @param settingTime The time after which the updated led values have been fully applied (sec) - /// @param updateDelay The number of frames to delay outgoing led updates - LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime, unsigned updateDelay, bool continuousOutput); + /// @param config The configuration document smoothing + /// @param hyperion The hyperion parent instance + /// + LinearColorSmoothing(LedDevice *ledDevice, const QJsonDocument& config, Hyperion* hyperion); /// Destructor virtual ~LinearColorSmoothing(); @@ -46,13 +52,53 @@ public: bool pause() { return _pause; } ; bool enabled() { return LedDevice::enabled() && !_pause; }; + /// + /// @brief Add a new smoothing cfg which can be used with selectConfig() + /// @param settlingTime_ms The buffer time + /// @param ledUpdateFrequency_hz The frequency of update + /// @param updateDelay The delay + /// + /// @return The index of the cfg which can be passed to selectConfig() + /// unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); - bool selectConfig(unsigned cfg); + + /// + /// @brief select a smoothing cfg given by cfg index from addConfig() + /// @param cfg The index to use + /// @param force Overwrite in any case the current values (used for cfg 0 settings udpate) + /// + /// @return On success return else false (and falls back to cfg 0) + /// + bool selectConfig(unsigned cfg, const bool& force = false); + + /// + /// @ Helper methods to start the timer with delay (see delayStartSmooth()) + /// + void startTimerDelayed(); + void stopTimer(); + +public slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// Timer callback which writes updated led values to the led device void updateLeds(); + /// Delay timer slot to workaround the leddevice reconstruction segfault (dangling pointer) + void delayStartTimer(); + + /// + /// @brief Handle component state changes + /// @param component The component + /// @param state The requested state + /// + void componentStateChange(const hyperion::Components component, const bool state); + private: /** * Pushes the colors into the output queue and popping the head to the led-device @@ -64,6 +110,12 @@ private: /// The led device LedDevice * _ledDevice; + /// Logger instance + Logger* _log; + + /// Hyperion instance + Hyperion* _hyperion; + /// The interval at which to update the leds (msec) int64_t _updateInterval; @@ -71,7 +123,7 @@ private: int64_t _settlingTime; /// The Qt timer object - QTimer _timer; + QTimer * _timer; /// The timestamp at which the target data should be fully applied int64_t _targetTime; @@ -92,7 +144,7 @@ private: /// Prevent sending data to device when no intput data is sent bool _writeToLedsEnable; - + /// Flag for dis/enable continuous output to led device regardless there is new data or not bool _continuousOutput; @@ -107,8 +159,8 @@ private: unsigned outputDelay; }; - /// config list + /// smooth config list QVector _cfgList; - + unsigned _currentConfigId; }; diff --git a/libsrc/hyperion/MessageForwarder.cpp b/libsrc/hyperion/MessageForwarder.cpp index 9ddb9863..abc0ed12 100644 --- a/libsrc/hyperion/MessageForwarder.cpp +++ b/libsrc/hyperion/MessageForwarder.cpp @@ -3,48 +3,110 @@ #include +#include +#include -MessageForwarder::MessageForwarder() +MessageForwarder::MessageForwarder(Hyperion* hyperion, const QJsonDocument & config) + : QObject() + , _hyperion(hyperion) + , _log(Logger::getInstance("NETFORWARDER")) { + handleSettingsUpdate(settings::NETFORWARD, config); + // get settings updates + connect(_hyperion, &Hyperion::settingsChanged, this, &MessageForwarder::handleSettingsUpdate); } MessageForwarder::~MessageForwarder() { } +void MessageForwarder::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::NETFORWARD) + { + // clear the current targets + _jsonSlaves.clear(); + _protoSlaves.clear(); + // build new one + const QJsonObject &obj = config.object(); + if ( !obj["json"].isNull() ) + { + const QJsonArray & addr = obj["json"].toArray(); + for (const auto& entry : addr) + { + addJsonSlave(entry.toString()); + } + } + + if ( !obj["proto"].isNull() ) + { + const QJsonArray & addr = obj["proto"].toArray(); + for (const auto& entry : addr) + { + addProtoSlave(entry.toString()); + } + } + InfoIf(obj["enable"].toBool(true), _log, "Forward now to json targets '%s' and proto targets '%s'", QSTRING_CSTR(_jsonSlaves.join(", ")), QSTRING_CSTR(_protoSlaves.join(", "))) + // update comp state + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_FORWARDER, obj["enable"].toBool(true)); + } +} void MessageForwarder::addJsonSlave(QString slave) { QStringList parts = slave.split(":"); if (parts.size() != 2) - throw std::runtime_error(QString("HYPERION (forwarder) ERROR: Wrong address: unable to parse address (%1)").arg(slave).toStdString()); + { + Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); + return; + } bool ok; - quint16 port = parts[1].toUShort(&ok); + parts[1].toUShort(&ok); if (!ok) - throw std::runtime_error(QString("HYPERION (forwarder) ERROR: Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + { + Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); + return; + } - JsonSlaveAddress c; - c.addr = QHostAddress(parts[0]); - c.port = port; - _jsonSlaves << c; + // verify loop with jsonserver + const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object(); + if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) + { + Error(_log, "Loop between JsonServer and Forwarder! (%s)",QSTRING_CSTR(slave)); + return; + } + + _jsonSlaves << slave; } void MessageForwarder::addProtoSlave(QString slave) { + QStringList parts = slave.split(":"); + if (parts.size() != 2) + { + Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); + return; + } + + bool ok; + parts[1].toUShort(&ok); + if (!ok) + { + Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); + return; + } + + // verify loop with protoserver + const QJsonObject& obj = _hyperion->getSetting(settings::PROTOSERVER).object(); + if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) + { + Error(_log, "Loop between ProtoServer and Forwarder! (%s)",QSTRING_CSTR(slave)); + return; + } _protoSlaves << slave; } -QStringList MessageForwarder::getProtoSlaves() -{ - return _protoSlaves; -} - -QList MessageForwarder::getJsonSlaves() -{ - return _jsonSlaves; -} - bool MessageForwarder::protoForwardingEnabled() { return ! _protoSlaves.empty(); diff --git a/libsrc/hyperion/MultiColorAdjustment.cpp b/libsrc/hyperion/MultiColorAdjustment.cpp index 32985dba..1c5ff16c 100644 --- a/libsrc/hyperion/MultiColorAdjustment.cpp +++ b/libsrc/hyperion/MultiColorAdjustment.cpp @@ -1,10 +1,10 @@ // Hyperion includes #include -#include "MultiColorAdjustment.h" +#include MultiColorAdjustment::MultiColorAdjustment(const unsigned ledCnt) : _ledAdjustments(ledCnt, nullptr) - , _log(Logger::getInstance("ColorAdjust")) + , _log(Logger::getInstance("ADJUSTMENT")) { } @@ -23,10 +23,20 @@ void MultiColorAdjustment::addAdjustment(ColorAdjustment * adjustment) _adjustment.push_back(adjustment); } -void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned startLed, const unsigned endLed) +void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned startLed, unsigned endLed) { - Q_ASSERT(startLed <= endLed); - Q_ASSERT(endLed < _ledAdjustments.size()); + // abort + if(startLed >= endLed) + { + Error(_log,"startLed >= endLed -> %d >= %d", startLed, endLed); + return; + } + // catch wrong values + if(endLed > _ledAdjustments.size()) + { + Warning(_log,"The color calibration 'LED index' field has leds specified which aren't part of your led layout"); + endLed = _ledAdjustments.size(); + } // Get the identified adjustment (don't care if is nullptr) ColorAdjustment * adjustment = getAdjustment(id); @@ -38,17 +48,18 @@ void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned bool MultiColorAdjustment::verifyAdjustments() const { + bool ok = true; for (unsigned iLed=0; iLed<_ledAdjustments.size(); ++iLed) { ColorAdjustment * adjustment = _ledAdjustments[iLed]; if (adjustment == nullptr) { - Error(_log, "No adjustment set for %d", iLed); - return false; + Warning(_log, "No calibration set for led %d", iLed); + ok = false; } } - return true; + return ok; } const QStringList & MultiColorAdjustment::getAdjustmentIds() @@ -96,7 +107,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint8_t ogreen = color.green; uint8_t oblue = color.blue; uint8_t B_RGB, B_CMY, B_W; - + adjustment->_rgbTransform.transform(ored,ogreen,oblue); adjustment->_rgbTransform.getBrightnessComponents(B_RGB, B_CMY, B_W); @@ -104,7 +115,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint32_t rng = (uint32_t) (ored) *(255-ogreen); uint32_t nrg = (uint32_t) (255-ored)*(ogreen); uint32_t rg = (uint32_t) (ored) *(ogreen); - + uint8_t black = nrng*(255-oblue)/65025; uint8_t red = rng *(255-oblue)/65025; uint8_t green = nrg *(255-oblue)/65025; @@ -113,7 +124,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint8_t magenta = rng *(oblue) /65025; uint8_t yellow = rg *(255-oblue)/65025; uint8_t white = rg *(oblue) /65025; - + uint8_t OR, OG, OB, RR, RG, RB, GR, GG, GB, BR, BG, BB; uint8_t CR, CG, CB, MR, MG, MB, YR, YG, YB, WR, WG, WB; diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index d8443b92..79ed8ed1 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -1,36 +1,113 @@ // STL includes #include -#include #include +// qt incl +#include +#include + // Hyperion includes #include +// utils +#include + const int PriorityMuxer::LOWEST_PRIORITY = std::numeric_limits::max(); PriorityMuxer::PriorityMuxer(int ledCount) - : _currentPriority(PriorityMuxer::LOWEST_PRIORITY) + : QObject() + , _log(Logger::getInstance("HYPERION")) + , _currentPriority(PriorityMuxer::LOWEST_PRIORITY) + , _manualSelectedPriority(256) , _activeInputs() , _lowestPriorityInfo() + , _sourceAutoSelectEnabled(true) + , _updateTimer(new QTimer(this)) + , _timer(new QTimer(this)) + , _blockTimer(new QTimer(this)) { + // init lowest priority info _lowestPriorityInfo.priority = PriorityMuxer::LOWEST_PRIORITY; - _lowestPriorityInfo.timeoutTime_ms = 0; + _lowestPriorityInfo.timeoutTime_ms = -1; _lowestPriorityInfo.ledColors = std::vector(ledCount, {0, 0, 0}); _lowestPriorityInfo.componentId = hyperion::COMP_COLOR; _lowestPriorityInfo.origin = "System"; + _lowestPriorityInfo.owner = ""; - _activeInputs[_currentPriority] = _lowestPriorityInfo; + _activeInputs[PriorityMuxer::LOWEST_PRIORITY] = _lowestPriorityInfo; - // do a reuqest after blocking timer runs out - connect(&_timer, SIGNAL(timeout()), this, SLOT(emitReq())); - _timer.setSingleShot(true); - _blockTimer.setSingleShot(true); + // adapt to 1s interval for COLOR and EFFECT timeouts > -1 + connect(_timer, &QTimer::timeout, this, &PriorityMuxer::timeTrigger); + _timer->setSingleShot(true); + _blockTimer->setSingleShot(true); + // forward timeRunner signal to prioritiesChanged signal & threading workaround + connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged); + connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger); + + // start muxer timer + connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime); + _updateTimer->setInterval(250); + _updateTimer->start(); + InputInfo ninfo; } PriorityMuxer::~PriorityMuxer() { } +void PriorityMuxer::setEnable(const bool& enable) +{ + enable ? _updateTimer->start() : _updateTimer->stop(); +} + +bool PriorityMuxer::setSourceAutoSelectEnabled(const bool& enable, const bool& update) +{ + if(_sourceAutoSelectEnabled != enable) + { + // on disable we need to make sure the last priority call to setPriority is still valid + if(!enable && !_activeInputs.contains(_manualSelectedPriority)) + { + Warning(_log, "Can't disable auto selection, as the last manual selected priority (%d) is no longer available", _manualSelectedPriority); + return false; + } + + _sourceAutoSelectEnabled = enable; + Debug(_log, "Source auto select is now %s", enable ? "enabled" : "disabled"); + + // update _currentPriority if called from external + if(update) + setCurrentTime(); + + emit autoSelectChanged(enable); + return true; + } + return false; +} + +bool PriorityMuxer::setPriority(const uint8_t priority) +{ + if(_activeInputs.contains(priority)) + { + _manualSelectedPriority = priority; + // update auto select state -> update _currentPriority + setSourceAutoSelectEnabled(false); + return true; + } + return false; +} + +void PriorityMuxer::updateLedColorsLength(const int& ledCount) +{ + for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();) + { + if (infoIt->ledColors.size() >= 1) + { + infoIt->ledColors.resize(ledCount, infoIt->ledColors.at(0)); + } + ++infoIt; + } +} + int PriorityMuxer::getCurrentPriority() const { return _currentPriority; @@ -46,7 +123,7 @@ bool PriorityMuxer::hasPriority(const int priority) const return (priority == PriorityMuxer::LOWEST_PRIORITY) ? true : _activeInputs.contains(priority); } -const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority) const +const PriorityMuxer::InputInfo PriorityMuxer::getInputInfo(const int priority) const { auto elemIt = _activeInputs.find(priority); if (elemIt == _activeInputs.end()) @@ -54,35 +131,127 @@ const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority) elemIt = _activeInputs.find(PriorityMuxer::LOWEST_PRIORITY); if (elemIt == _activeInputs.end()) { - throw std::runtime_error("HYPERION (prioritymuxer) ERROR: no such priority"); + // fallback + return _lowestPriorityInfo; } } return elemIt.value(); } -void PriorityMuxer::setInput(const int priority, const std::vector& ledColors, const int64_t timeoutTime_ms, hyperion::Components component, const QString origin, unsigned smooth_cfg) +void PriorityMuxer::registerInput(const int priority, const hyperion::Components& component, const QString& origin, const QString& owner, unsigned smooth_cfg) { + // detect new registers + bool newInput = false; + if(!_activeInputs.contains(priority)) + newInput = true; + InputInfo& input = _activeInputs[priority]; input.priority = priority; - input.timeoutTime_ms = timeoutTime_ms; - input.ledColors = ledColors; + input.timeoutTime_ms = newInput ? -100 : input.timeoutTime_ms; input.componentId = component; input.origin = origin; input.smooth_cfg = smooth_cfg; - _currentPriority = qMin(_currentPriority, priority); + input.owner = owner; + + if(newInput) + { + Debug(_log,"Register new input '%s/%s' with priority %d as inactive", QSTRING_CSTR(origin), hyperion::componentToIdString(component), priority); + emit priorityChanged(priority, true); + emit prioritiesChanged(); + return; + } } -void PriorityMuxer::clearInput(const int priority) +const bool PriorityMuxer::setInput(const int priority, const std::vector& ledColors, int64_t timeout_ms) { - if (priority < PriorityMuxer::LOWEST_PRIORITY) + if(!_activeInputs.contains(priority)) { - _activeInputs.remove(priority); - if (_currentPriority == priority) - { - QList keys = _activeInputs.keys(); - _currentPriority = *std::min_element(keys.begin(), keys.end()); - } + Error(_log,"setInput() used without registerInput() for priority '%d', probably the priority reached timeout",priority); + return false; } + + // calc final timeout + if(timeout_ms > 0) + timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms; + + InputInfo& input = _activeInputs[priority]; + // detect active <-> inactive changes + bool activeChange = false; + bool active = true; + if(input.timeoutTime_ms == -100 && timeout_ms != -100) + { + activeChange = true; + } + else if(timeout_ms == -100 && input.timeoutTime_ms != -100) + { + active = false; + activeChange = true; + } + // update input + input.timeoutTime_ms = timeout_ms; + input.ledColors = ledColors; + + // emit active change + if(activeChange) + { + Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); + emit activeStateChanged(priority, active); + setCurrentTime(); + } + return true; +} + +const bool PriorityMuxer::setInputImage(const int priority, const Image& image, int64_t timeout_ms) +{ + if(!_activeInputs.contains(priority)) + { + Error(_log,"setInputImage() used without registerInput() for priority '%d', probably the priority reached timeout",priority); + return false; + } + + // calc final timeout + if(timeout_ms > 0) + timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms; + + InputInfo& input = _activeInputs[priority]; + // detect active <-> inactive changes + bool activeChange = false; + bool active = true; + if(input.timeoutTime_ms == -100 && timeout_ms != -100) + { + activeChange = true; + } + else if(timeout_ms == -100 && input.timeoutTime_ms != -100) + { + active = false; + activeChange = true; + } + // update input + input.timeoutTime_ms = timeout_ms; + input.image = image; + + // emit active change + if(activeChange) + { + Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); + emit activeStateChanged(priority, active); + setCurrentTime(); + } + return true; +} + +const bool PriorityMuxer::clearInput(const uint8_t priority) +{ + if (priority < PriorityMuxer::LOWEST_PRIORITY && _activeInputs.remove(priority)) + { + Debug(_log,"Removed source priority %d",priority); + // on clear success update _currentPriority + setCurrentTime(); + emit priorityChanged(priority, false); + emit prioritiesChanged(); + return true; + } + return false; } void PriorityMuxer::clearAll(bool forceClearAll) @@ -97,47 +266,82 @@ void PriorityMuxer::clearAll(bool forceClearAll) { for(auto key : _activeInputs.keys()) { - if (key < PriorityMuxer::LOWEST_PRIORITY-1) + const InputInfo info = getInputInfo(key); + if ((info.componentId == hyperion::COMP_COLOR || info.componentId == hyperion::COMP_EFFECT) && key < PriorityMuxer::LOWEST_PRIORITY-1) { - _activeInputs.remove(key); + clearInput(key); } } } } -void PriorityMuxer::setCurrentTime(const int64_t& now) +void PriorityMuxer::setCurrentTime(void) { - _currentPriority = PriorityMuxer::LOWEST_PRIORITY; + const int64_t now = QDateTime::currentMSecsSinceEpoch(); + int newPriority = PriorityMuxer::LOWEST_PRIORITY; for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();) { if (infoIt->timeoutTime_ms > 0 && infoIt->timeoutTime_ms <= now) { + quint8 tPrio = infoIt->priority; infoIt = _activeInputs.erase(infoIt); + Debug(_log,"Timeout clear for priority %d",tPrio); + emit priorityChanged(tPrio, false); + emit prioritiesChanged(); } else { - _currentPriority = qMin(_currentPriority, infoIt->priority); - - // call emitReq when effect or color is running with timeout > -1, blacklist prio 255 + // timeoutTime of -100 is awaiting data (inactive); skip + if(infoIt->timeoutTime_ms >= -1) + newPriority = qMin(newPriority, infoIt->priority); + + // call timeTrigger 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(); + emit signalTimeTrigger(); // as signal to prevent Threading issues } ++infoIt; } } + // eval if manual selected prio is still available + if(!_sourceAutoSelectEnabled) + { + if(_activeInputs.contains(_manualSelectedPriority)) + { + newPriority = _manualSelectedPriority; + } + else + { + Debug(_log, "The manual selected priority '%d' is no longer available, switching to auto selection", _manualSelectedPriority); + // update state, but no _currentPriority re-eval + setSourceAutoSelectEnabled(true, false); + } + } + // apply & emit on change (after apply!) + bool changed = false; + if(_currentPriority != newPriority) + changed = true; + + _currentPriority = newPriority; + + if(changed) + { + Debug(_log, "Set visible priority to %d", newPriority); + emit visiblePriorityChanged(newPriority); + emit prioritiesChanged(); + } } -void PriorityMuxer::emitReq() +void PriorityMuxer::timeTrigger() { - if(_blockTimer.isActive()) + if(_blockTimer->isActive()) { - _timer.start(500); + _timer->start(500); } else { - emit timerunner(); - _blockTimer.start(1000); + emit timeRunner(); + _blockTimer->start(1000); } } diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp new file mode 100644 index 00000000..3eeec8d3 --- /dev/null +++ b/libsrc/hyperion/SettingsManager.cpp @@ -0,0 +1,175 @@ +// proj +#include + +// util +#include + +// json schema process +#include +#include + +// write config to filesystem +#include + +// hyperion +#include + +QJsonObject SettingsManager::schemaJson; + +SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile) + : _hyperion(hyperion) + , _log(Logger::getInstance("SettingsManager")) +{ + Q_INIT_RESOURCE(resource); + connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged); + // get schema + if(schemaJson.isEmpty()) + { + try + { + schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); + } + catch(const std::runtime_error& error) + { + throw std::runtime_error(error.what()); + } + } + // get default config + QJsonObject defaultConfig; + if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + throw std::runtime_error("Failed to read default config"); + + Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); + QJsonSchemaChecker schemaCheckerT; + + if(!JsonUtils::readFile(configFile, _qconfig, _log)) + throw std::runtime_error("Failed to load config!"); + + // validate config with schema and correct it if required + QPair validate = schemaCheckerT.validate(_qconfig); + + // errors in schema syntax, abort + if (!validate.second) + { + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); + + throw std::runtime_error("ERROR: Hyperion schema has syntax errors!"); + } + // errors in configuration, correct it! + if (!validate.first) + { + Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); + _qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig); + + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + + if (!JsonUtils::write(configFile, _qconfig, _log)) + throw std::runtime_error("ERROR: Can't save configuration file, aborting"); + } + + Debug(_log,"Settings database initialized") +} + +SettingsManager::SettingsManager(const quint8& instance, const QString& configFile) + : _hyperion(nullptr) + , _log(Logger::getInstance("SettingsManager")) +{ + Q_INIT_RESOURCE(resource); + // get schema + if(schemaJson.isEmpty()) + { + try + { + schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); + } + catch(const std::runtime_error& error) + { + throw std::runtime_error(error.what()); + } + } + // get default config + QJsonObject defaultConfig; + if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + throw std::runtime_error("Failed to read default config"); + + Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); + QJsonSchemaChecker schemaCheckerT; + + if(!JsonUtils::readFile(configFile, _qconfig, _log)) + throw std::runtime_error("Failed to load config!"); + + // validate config with schema and correct it if required + QPair validate = schemaCheckerT.validate(_qconfig); + + // errors in schema syntax, abort + if (!validate.second) + { + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); + + throw std::runtime_error("ERROR: Hyperion schema has syntax errors!"); + } + // errors in configuration, correct it! + if (!validate.first) + { + Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); + _qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig); + + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + + if (!JsonUtils::write(configFile, _qconfig, _log)) + throw std::runtime_error("ERROR: Can't save configuration file, aborting"); + } + + Debug(_log,"Settings database initialized") +} + +SettingsManager::~SettingsManager() +{ +} + +const QJsonDocument SettingsManager::getSetting(const settings::type& type) +{ + //return _sTable->getSettingsRecord(settings::typeToString(type)); + + QString key = settings::typeToString(type); + if(_qconfig[key].isObject()) + return QJsonDocument(_qconfig[key].toObject()); + else + return QJsonDocument(_qconfig[key].toArray()); +} + +const bool SettingsManager::saveSettings(QJsonObject config, const bool& correct) +{ + // we need to validate data against schema + QJsonSchemaChecker schemaChecker; + schemaChecker.setSchema(schemaJson); + if (!schemaChecker.validate(config).first) + { + if(!correct) + { + Error(_log,"Failed to save configuration, errors during validation"); + return false; + } + Warning(_log,"Fixing json data!"); + config = schemaChecker.getAutoCorrectedConfig(config); + + foreach (auto & schemaError, schemaChecker.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + } + + // save data to file + if(_hyperion != nullptr) + { + if(!JsonUtils::write(_hyperion->getConfigFilePath(), config, _log)) + return false; + } + + // store the current state + _qconfig = config; + + return true; +} diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 501335f3..b49d2f44 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -71,6 +71,10 @@ { "$ref": "schema-effects.json" }, + "instCapture": + { + "$ref": "schema-instCapture.json" + }, "ledConfig": { "$ref": "schema-ledConfig.json" diff --git a/libsrc/hyperion/resource.qrc b/libsrc/hyperion/resource.qrc index 8616fa79..717d548a 100644 --- a/libsrc/hyperion/resource.qrc +++ b/libsrc/hyperion/resource.qrc @@ -22,5 +22,6 @@ schema/schema-effects.json schema/schema-ledConfig.json schema/schema-leds.json + schema/schema-instCapture.json diff --git a/libsrc/hyperion/schema/schema-color.json b/libsrc/hyperion/schema/schema-color.json index fe8181d5..1bcb1705 100644 --- a/libsrc/hyperion/schema/schema-color.json +++ b/libsrc/hyperion/schema/schema-color.json @@ -46,22 +46,6 @@ "default" : "*", "propertyOrder" : 2 }, - "black" : - { - "type" : "array", - "title" : "edt_conf_color_black_title", - "format" : "colorpicker", - "required" : true, - "default": [0,0,0], - "items" : { - "type" : "integer", - "minimum" : 0, - "maximum" : 255 - }, - "minItems" : 3, - "maxItems" : 3, - "propertyOrder" : 3 - }, "white" : { "type" : "array", diff --git a/libsrc/hyperion/schema/schema-framegrabber.json b/libsrc/hyperion/schema/schema-framegrabber.json index 366d05b6..e1f4ea98 100644 --- a/libsrc/hyperion/schema/schema-framegrabber.json +++ b/libsrc/hyperion/schema/schema-framegrabber.json @@ -3,13 +3,6 @@ "title" : "edt_conf_fg_heading_title", "properties" : { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "default" : true, - "propertyOrder" : 1 - }, "type" : { "type" : "string", @@ -45,15 +38,6 @@ "append" : "edt_append_hz", "propertyOrder" : 4 }, - "priority" : - { - "type" : "integer", - "title" : "edt_conf_general_priority_title", - "minimum" : 100, - "maximum" : 254, - "default" : 250, - "propertyOrder" : 5 - }, "cropLeft" : { "type" : "integer", @@ -90,42 +74,28 @@ "append" : "edt_append_pixel", "propertyOrder" : 9 }, - "useXGetImage" : + "pixelDecimation" : { - "type" : "boolean", - "title" : "edt_conf_fg_useXGetImage_title", - "default" : false, + "type" : "integer", + "title" : "edt_conf_fg_pixelDecimation_title", + "minimum" : 1, + "maximum" : 30, + "default" : 8, "propertyOrder" : 10 }, - "horizontalPixelDecimation" : - { - "type" : "integer", - "title" : "edt_conf_fg_horizontalPixelDecimation_title", - "minimum" : 0, - "default" : 8, - "propertyOrder" : 11 - }, - "verticalPixelDecimation" : - { - "type" : "integer", - "title" : "edt_conf_fg_verticalPixelDecimation_title", - "minimum" : 0, - "default" : 8, - "propertyOrder" : 12 - }, "device" : { "type" : "string", "title" : "edt_conf_fg_device_title", "default" : "/dev/fb0", - "propertyOrder" : 13 + "propertyOrder" : 11 }, "display" : { "type" : "integer", "title" : "edt_conf_fg_display_title", "minimum" : 0, - "propertyOrder" : 14 + "propertyOrder" : 12 } }, "additionalProperties" : false diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index e7356006..bd928614 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -11,14 +11,6 @@ "title" : "edt_conf_v4l2_heading_title", "properties" : { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "default" : false, - "required" : true, - "propertyOrder" : 1 - }, "device" : { "type" : "string", @@ -40,62 +32,24 @@ { "type" : "string", "title" : "edt_conf_v4l2_standard_title", - "enum" : ["PAL","NTSC","SECAM"], - "default" : "PAL", + "enum" : ["PAL","NTSC","SECAM","NO_CHANGE"], + "default" : "NO_CHANGE", "options" : { - "enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM"] + "enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM", "edt_conf_enum_NO_CHANGE"] }, "required" : true, "propertyOrder" : 4 }, - "width" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_width_title", - "minimum" : 0, - "default" : 0, - "append" : "edt_append_pixel", - "required" : true, - "propertyOrder" : 5 - }, - "height" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_height_title", - "minimum" : 0, - "default" : 0, - "append" : "edt_append_pixel", - "required" : true, - "propertyOrder" : 6 - }, - "frameDecimation" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_frameDecimation_title", - "minimum" : 0, - "default" : 2, - "required" : true, - "propertyOrder" : 7 - }, "sizeDecimation" : { "type" : "integer", - "title" : "Size decimation", - "minimum" : 0, + "title" : "edt_conf_v4l2_sizeDecimation_title", + "minimum" : 1, + "maximum" : 30, "default" : 6, "required" : true, "propertyOrder" : 8 }, - "priority" : - { - "type" : "integer", - "minimum" : 100, - "maximum" : 253, - "title" : "edt_conf_general_priority_title", - "default" : 240, - "required" : true, - "propertyOrder" : 9 - }, "cropLeft" : { "type" : "integer", diff --git a/libsrc/hyperion/schema/schema-instCapture.json b/libsrc/hyperion/schema/schema-instCapture.json new file mode 100644 index 00000000..2e59df10 --- /dev/null +++ b/libsrc/hyperion/schema/schema-instCapture.json @@ -0,0 +1,45 @@ +{ + "type" : "object", + "required" : true, + "title" : "edt_conf_instC_heading_title", + "properties" : + { + "systemEnable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_instC_systemEnable", + "default" : true, + "propertyOrder" : 1 + }, + "systemPriority" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_priority_title", + "minimum" : 100, + "maximum" : 253, + "default" : 250, + "propertyOrder" : 2 + }, + "v4lEnable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_instC_v4lEnable", + "default" : false, + "propertyOrder" : 3 + }, + "v4lPriority" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_priority_title", + "minimum" : 100, + "maximum" : 253, + "default" : 240, + "propertyOrder" : 4 + } + }, + "additionalProperties" : false +} diff --git a/libsrc/jsonserver/CMakeLists.txt b/libsrc/jsonserver/CMakeLists.txt index dfddffa5..1f0bc310 100644 --- a/libsrc/jsonserver/CMakeLists.txt +++ b/libsrc/jsonserver/CMakeLists.txt @@ -8,6 +8,7 @@ FILE ( GLOB JsonServer_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DI add_library(jsonserver ${JsonServer_SOURCES} ) target_link_libraries(jsonserver + hyperion-api hyperion Qt5::Network Qt5::Gui diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 554361b2..8fd2b630 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -1,7 +1,10 @@ // project includes #include "JsonClientConnection.h" -#include +#include + +// qt inc #include +#include JsonClientConnection::JsonClientConnection(QTcpSocket *socket) : QObject() @@ -11,10 +14,10 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket) { connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected); connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest); - // create a new instance of JsonProcessor - _jsonProcessor = new JsonProcessor(socket->peerAddress().toString(), _log, this); - // get the callback messages from JsonProcessor and send it to the client - connect(_jsonProcessor,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject))); + // create a new instance of JsonAPI + _jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this); + // get the callback messages from JsonAPI and send it to the client + connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject))); } void JsonClientConnection::readRequest() @@ -31,7 +34,7 @@ void JsonClientConnection::readRequest() _receiveBuffer = _receiveBuffer.mid(bytes); // handle message - _jsonProcessor->handleMessage(message); + _jsonAPI->handleMessage(message); // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 1195e7c5..57944275 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -3,11 +3,12 @@ // Qt includes #include #include +#include // util includes #include -class JsonProcessor; +class JsonAPI; class QTcpSocket; /// @@ -40,8 +41,8 @@ private slots: private: QTcpSocket* _socket; - /// new instance of JsonProcessor - JsonProcessor * _jsonProcessor; + /// new instance of JsonAPI + JsonAPI * _jsonAPI; /// The buffer used for reading data from the socket QByteArray _receiveBuffer; diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 3c8e9e73..42deda01 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -6,41 +6,41 @@ #include "JsonClientConnection.h" // hyperion include +#include #include +#include +#include // qt includes +#include #include #include #include -JsonServer::JsonServer(uint16_t port) +JsonServer::JsonServer(const QJsonDocument& config) : QObject() - , _server() + , _server(new QTcpServer(this)) , _hyperion(Hyperion::getInstance()) , _openConnections() , _log(Logger::getInstance("JSONSERVER")) + , _componentRegister( & _hyperion->getComponentRegister()) { - if (!_server.listen(QHostAddress::Any, port)) - { - throw std::runtime_error("JSONSERVER ERROR: could not bind to port"); - } - - QList list = Hyperion::getInstance()->getForwarder()->getJsonSlaves(); - for ( int i=0; iisComponentEnabled(hyperion::COMP_FORWARDER)); } JsonServer::~JsonServer() @@ -50,16 +50,58 @@ JsonServer::~JsonServer() } } +void JsonServer::start() +{ + if(_server->isListening()) + return; + + if (!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Could not bind to port '%d', please use an available port", _port); + return; + } + Info(_log, "Started on port %d", _port); + + if(_serviceRegister == nullptr) + { + _serviceRegister = new BonjourServiceRegister(); + _serviceRegister->registerService("_hyperiond-json._tcp", _port); + } +} + +void JsonServer::stop() +{ + if(!_server->isListening()) + return; + + _server->close(); + Info(_log, "Stopped"); +} + +void JsonServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::JSONSERVER) + { + QJsonObject obj = config.object(); + if(_port != obj["port"].toInt()) + { + _port = obj["port"].toInt(); + stop(); + start(); + } + } +} + uint16_t JsonServer::getPort() const { - return _server.serverPort(); + return _port; } void JsonServer::newConnection() { - while(_server.hasPendingConnections()) + while(_server->hasPendingConnections()) { - if (QTcpSocket * socket = _server.nextPendingConnection()) + if (QTcpSocket * socket = _server->nextPendingConnection()) { Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str()); JsonClientConnection * connection = new JsonClientConnection(socket); @@ -83,11 +125,9 @@ void JsonServer::closedConnection(void) void JsonServer::componentStateChanged(const hyperion::Components component, bool enable) { - if (component == hyperion::COMP_FORWARDER && _forwarder_enabled != enable) + if (component == hyperion::COMP_FORWARDER) { - _forwarder_enabled = enable; - Info(_log, "forwarder change state to %s", (enable ? "enabled" : "disabled") ); - if(_forwarder_enabled) + if(enable) { connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); } @@ -101,11 +141,12 @@ void JsonServer::componentStateChanged(const hyperion::Components component, boo void JsonServer::forwardJsonMessage(const QJsonObject &message) { QTcpSocket client; - QList list = _hyperion->getForwarder()->getJsonSlaves(); + QStringList list = _hyperion->getForwarder()->getJsonSlaves(); - for ( int i=0; i LedDeviceRegistry LedDevice::_ledDeviceMap = LedDeviceRegistry(); -QString LedDevice::_activeDevice = ""; -int LedDevice::_ledCount = 0; -int LedDevice::_ledRGBCount = 0; -int LedDevice::_ledRGBWCount= 0; LedDevice::LedDevice() : QObject() - , _log(Logger::getInstance("LedDevice")) + , _log(Logger::getInstance("LEDDEVICE")) , _ledBuffer(0) , _deviceReady(true) , _refresh_timer() @@ -77,6 +73,10 @@ void LedDevice::setActiveDevice(QString dev) bool LedDevice::init(const QJsonObject &deviceConfig) { + _colorOrder = deviceConfig["colorOrder"].toString("RGB"); + _activeDevice = deviceConfig["type"].toString("file").toLower(); + setLedCount(deviceConfig["currentLedCount"].toInt(1)); // property injected to reflect real led count + _latchTime_ms = deviceConfig["latchTime"].toInt(_latchTime_ms); _refresh_timer.setInterval( deviceConfig["rewriteTime"].toInt( _refresh_timer_interval) ); if (_refresh_timer.interval() <= (signed)_latchTime_ms ) diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index 85505451..8deaaf96 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -13,20 +13,17 @@ // following file is auto generated by cmake! it contains all available leddevice headers #include "LedDevice_headers.h" -LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const int ledCount) +LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig) { - Logger * log = Logger::getInstance("LedDevice"); + Logger * log = Logger::getInstance("LEDDEVICE"); QJsonDocument config(deviceConfig); QString ss(config.toJson(QJsonDocument::Indented)); QString type = deviceConfig["type"].toString("UNSPECIFIED").toLower(); - // set amount of led to leddevice - LedDevice::setLedCount(ledCount); - #define REGISTER(className) LedDevice::addToDeviceMap(QString(#className).toLower(), LedDevice##className::construct); - // the REGISTER() calls are autogenerated by cmake. + // the REGISTER() calls are autogenerated by cmake. #include "LedDevice_register.cpp" #undef REGISTER @@ -40,12 +37,11 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const if (dev.first == type) { device = dev.second(deviceConfig); - LedDevice::setActiveDevice(dev.first); Info(log,"LedDevice '%s' configured.", QSTRING_CSTR(dev.first)); break; } } - + if (device == nullptr) { Error(log, "Dummy device used, because configured device '%s' is unknown", QSTRING_CSTR(type) ); @@ -54,13 +50,13 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const } catch(std::exception& e) { - + Error(log, "Dummy device used, because configured device '%s' throws error '%s'", QSTRING_CSTR(type), e.what()); const QJsonObject dummyDeviceConfig; device = LedDeviceFile::construct(QJsonObject()); } device->open(); - + return device; } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index b49d098a..d40ffa48 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -21,7 +21,7 @@ ProviderUdp::ProviderUdp() , _defaultHost("127.0.0.1") { _latchTime_ms = 1; - _udpSocket = new QUdpSocket(); + _udpSocket = new QUdpSocket(this); } ProviderUdp::~ProviderUdp() @@ -34,7 +34,7 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig) LedDevice::init(deviceConfig); QString host = deviceConfig["host"].toString(_defaultHost); - + if (_address.setAddress(host) ) { Debug( _log, "Successfully parsed %s as an ip address.", deviceConfig["host"].toString().toStdString().c_str()); @@ -57,9 +57,9 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig) { throw std::runtime_error("invalid target port"); } - + Debug( _log, "UDP using %s:%d", _address.toString().toStdString().c_str() , _port ); - + return true; } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.h b/libsrc/leddevice/dev_net/ProviderUdp.h index ef34220d..316902bd 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.h +++ b/libsrc/leddevice/dev_net/ProviderUdp.h @@ -1,11 +1,14 @@ #pragma once -#include - // Hyperion includes #include #include +// qt +#include + +class QUdpSocket; + /// /// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets. /// diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index 737292f2..f492d380 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -7,24 +7,42 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIRS} ) -FILE ( GLOB ProtoServer_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) set(ProtoServer_PROTOS ${CURRENT_SOURCE_DIR}/message.proto ) protobuf_generate_cpp(ProtoServer_PROTO_SRCS ProtoServer_PROTO_HDRS ${ProtoServer_PROTOS} ) -add_library(protoserver - ${ProtoServer_SOURCES} - ${ProtoServer_PROTOS} +### Split protoclient from protoserver as protoserver relates to HyperionDaemon and standalone capture binarys can't link to it + +add_library(protoclient + ${CURRENT_HEADER_DIR}/ProtoConnection.h + ${CURRENT_SOURCE_DIR}/ProtoConnection.cpp + ${CURRENT_HEADER_DIR}/ProtoConnectionWrapper.h + ${CURRENT_SOURCE_DIR}/ProtoConnectionWrapper.cpp + ${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 generatet 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(protoserver +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 index 1e2e24e2..2a3b6b22 100644 --- a/libsrc/protoserver/ProtoClientConnection.cpp +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -14,8 +14,6 @@ #include // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" // project includes @@ -24,17 +22,14 @@ ProtoClientConnection::ProtoClientConnection(QTcpSocket *socket) : QObject() , _socket(socket) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _priority(-1) - , _priorityChannelName("Proto-Server") , _clientAddress(QHostInfo::fromName(socket->peerAddress().toString()).hostName()) { // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); - connect(_hyperion, SIGNAL(imageToLedsMappingChanged(int)), _imageProcessor, SLOT(setLedMappingType(int))); } ProtoClientConnection::~ProtoClientConnection() @@ -81,7 +76,7 @@ void ProtoClientConnection::readData() void ProtoClientConnection::socketClosed() { - _hyperion->unRegisterPriority(_priorityChannelName); + _hyperion->clear(_priority); emit connectionClosed(this); } @@ -103,7 +98,6 @@ void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message // forward messages emit newMessage(&message); - int prevPriority = _priority; switch (message.command()) { case proto::HyperionRequest::COLOR: @@ -136,24 +130,26 @@ void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message default: handleNotImplemented(); } - - if (prevPriority != _priority) - { - _hyperion->registerPriority(_priorityChannelName, _priority); - prevPriority = _priority; - } } void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &message) { // extract parameters - _priority = message.priority(); + 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); @@ -164,12 +160,20 @@ void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &messag void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &message) { // extract parameters - _priority = message.priority(); + 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) { @@ -177,17 +181,11 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag return; } - // set width and height of the image processor - _imageProcessor->setSize(width, height); - // create ImageRgb Image image(width, height); memcpy(image.memptr(), imageData.c_str(), imageData.size()); - // process the image - std::vector ledColors = _imageProcessor->process(image); - _hyperion->setColors(_priority, ledColors, duration, true, hyperion::COMP_PROTOSERVER , "proto@"+_clientAddress); - _hyperion->setImage(_priority, image, duration); + _hyperion->setInputImage(_priority, image, duration); // send reply sendSuccessReply(); @@ -197,11 +195,10 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &message) { // extract parameters - _priority = message.priority(); + int priority = message.priority(); // clear priority - _hyperion->clear(_priority); - _hyperion->unRegisterPriority(_priorityChannelName); + _hyperion->clear(priority); // send reply sendSuccessReply(); } diff --git a/libsrc/protoserver/ProtoClientConnection.h b/libsrc/protoserver/ProtoClientConnection.h index ad139094..f61cfed1 100644 --- a/libsrc/protoserver/ProtoClientConnection.h +++ b/libsrc/protoserver/ProtoClientConnection.h @@ -19,8 +19,6 @@ #include "message.pb.h" #include "protoserver/ProtoConnection.h" -class ImageProcessor; - /// /// The Connection object created by a ProtoServer when a new connection is establshed /// @@ -128,9 +126,6 @@ private: /// The TCP-Socket that is connected tot the Proto-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; @@ -139,8 +134,6 @@ private: int _priority; - QString _priorityChannelName; - /// address of client QString _clientAddress; }; diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp index a925bf74..4504dda0 100644 --- a/libsrc/protoserver/ProtoServer.cpp +++ b/libsrc/protoserver/ProtoServer.cpp @@ -1,43 +1,44 @@ // system includes #include +// qt incl +#include + // project includes +#include #include #include #include "protoserver/ProtoConnection.h" #include "ProtoClientConnection.h" +#include +#include -ProtoServer::ProtoServer(uint16_t port) +ProtoServer::ProtoServer(const QJsonDocument& config) : QObject() , _hyperion(Hyperion::getInstance()) - , _server() + , _server(new QTcpServer(this)) , _openConnections() , _log(Logger::getInstance("PROTOSERVER")) - , _forwarder_enabled(true) + , _componentRegister( & _hyperion->getComponentRegister()) { + Debug(_log,"Instance created"); + connect( _server, SIGNAL(newConnection()), this, SLOT(newConnection())); + handleSettingsUpdate(settings::PROTOSERVER, config); - MessageForwarder * forwarder = _hyperion->getForwarder(); - QStringList slaves = forwarder->getProtoSlaves(); + QStringList slaves = _hyperion->getForwarder()->getProtoSlaves(); - for (int i = 0; i < slaves.size(); ++i) { - if ( QString("127.0.0.1:%1").arg(port) == slaves.at(i) ) { - throw std::runtime_error("PROTOSERVER ERROR: Loop between proto server and forwarder detected. Fix your config!"); - } - - ProtoConnection* p = new ProtoConnection(slaves.at(i).toLocal8Bit().constData()); + for (const auto& entry : slaves) + { + ProtoConnection* p = new ProtoConnection(entry.toLocal8Bit().constData()); p->setSkipReply(true); _proxy_connections << p; } - if (!_server.listen(QHostAddress::Any, port)) - { - throw std::runtime_error("PROTOSERVER ERROR: Could not bind to port"); - } - - // Set trigger for incoming connections - connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); - connect( _hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); + // listen for component changes + connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &ProtoServer::componentStateChanged); + // get inital forwarder state + componentStateChanged(hyperion::COMP_FORWARDER, _componentRegister->isComponentEnabled(hyperion::COMP_FORWARDER)); } ProtoServer::~ProtoServer() @@ -50,27 +51,70 @@ ProtoServer::~ProtoServer() delete _proxy_connections.takeFirst(); } +void ProtoServer::start() +{ + if(_server->isListening()) + return; + + if (!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Could not bind to port '%d', please use an available port",_port); + return; + } + Info(_log, "Started on port %d", _port); + + if(_serviceRegister == nullptr) + { + _serviceRegister = new BonjourServiceRegister(); + _serviceRegister->registerService("_hyperiond-proto._tcp", _port); + } +} + +void ProtoServer::stop() +{ + if(!_server->isListening()) + return; + + _server->close(); + Info(_log, "Stopped"); +} + +void ProtoServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::PROTOSERVER) + { + QJsonObject obj = config.object(); + if(obj["port"].toInt() != _port) + { + _port = obj["port"].toInt(); + stop(); + start(); + } + } +} + uint16_t ProtoServer::getPort() const { - return _server.serverPort(); + return _port; } void ProtoServer::newConnection() { - QTcpSocket * socket = _server.nextPendingConnection(); - - if (socket != nullptr) + while(_server->hasPendingConnections()) { - Debug(_log, "New connection"); - ProtoClientConnection * connection = new ProtoClientConnection(socket); - _openConnections.insert(connection); + if(QTcpSocket * socket = _server->nextPendingConnection()) + { + Debug(_log, "New connection"); + ProtoClientConnection * connection = new ProtoClientConnection(socket); + _openConnections.insert(connection); - // register slot for cleaning up after the connection closed - connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); - connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); + // register slot for cleaning up after the connection closed + connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); + connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); - // register forward signal for video mode - connect(this, SIGNAL(videoMode(VideoMode)), connection, SLOT(setVideoMode(VideoMode))); + // register forward signal for video mode + connect(this, SIGNAL(videoMode(VideoMode)), connection, SLOT(setVideoMode(VideoMode))); + } } } @@ -93,12 +137,7 @@ void ProtoServer::componentStateChanged(const hyperion::Components component, bo { if (component == hyperion::COMP_FORWARDER) { - if (_forwarder_enabled != enable) - { - _forwarder_enabled = enable; - Info(_log, "forwarder change state to %s", (_forwarder_enabled ? "enabled" : "disabled") ); - } - _hyperion->getComponentRegister().componentStateChanged(component, _forwarder_enabled); + _forwarder_enabled = enable; } } diff --git a/libsrc/python/CMakeLists.txt b/libsrc/python/CMakeLists.txt new file mode 100644 index 00000000..2f363d88 --- /dev/null +++ b/libsrc/python/CMakeLists.txt @@ -0,0 +1,21 @@ +find_package(PythonLibs 3.4 REQUIRED) + +# Include the python directory. Also include the parent (which is for example /usr/include) +# which may be required when it is not includes by the (cross-) compiler by default. +include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/python) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/python) + +FILE ( GLOB PYTHON_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +add_library(python + ${PYTHON_SOURCES} +) + +target_link_libraries(python + effectengine + hyperion-utils + ${PYTHON_LIBRARIES} +) diff --git a/libsrc/python/PythonInit.cpp b/libsrc/python/PythonInit.cpp new file mode 100644 index 00000000..f93f920f --- /dev/null +++ b/libsrc/python/PythonInit.cpp @@ -0,0 +1,31 @@ +#undef slots +#include +#define slots + +// utils +#include + +#include +#include + +// modules to init +#include + +PythonInit::PythonInit() +{ + // register modules + EffectModule::registerHyperionExtensionModule(); + + // init Python + Debug(Logger::getInstance("DAEMON"), "Initializing Python interpreter"); + Py_InitializeEx(0); + PyEval_InitThreads(); // Create the GIL + mainThreadState = PyEval_SaveThread(); +} + +PythonInit::~PythonInit() +{ + Debug(Logger::getInstance("DAEMON"), "Cleaning up Python interpreter"); + PyEval_RestoreThread(mainThreadState); + Py_Finalize(); +} diff --git a/libsrc/udplistener/UDPListener.cpp b/libsrc/udplistener/UDPListener.cpp index cb53958c..3674587b 100644 --- a/libsrc/udplistener/UDPListener.cpp +++ b/libsrc/udplistener/UDPListener.cpp @@ -1,33 +1,38 @@ // project includes #include +// hyperion includes +#include +#include + // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" #include "HyperionConfig.h" +// qt includes +#include + using namespace hyperion; -UDPListener::UDPListener(const int priority, const int timeout, const QString& address, quint16 listenPort, bool shared) : +UDPListener::UDPListener(const QJsonDocument& config) : QObject(), _hyperion(Hyperion::getInstance()), - _server(), + _server(new QUdpSocket(this)), _openConnections(), - _priority(priority), - _timeout(timeout), + _priority(0), + _timeout(0), _log(Logger::getInstance("UDPLISTENER")), _isActive(false), - _listenPort(listenPort), - _bondage(shared ? QAbstractSocket::ShareAddress : QAbstractSocket::DefaultForPlatform) + _listenPort(0) { - _server = new QUdpSocket(this); - _listenAddress = address.isEmpty()? QHostAddress::AnyIPv4 : QHostAddress(address); - + Debug(_log, "Instance created"); + // listen for comp changes + connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); // Set trigger for incoming connections connect(_server, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - _hyperion->registerPriority("UDPLISTENER", _priority); + // init + handleSettingsUpdate(settings::UDPLISTENER, config); } UDPListener::~UDPListener() @@ -51,7 +56,7 @@ void UDPListener::start() if (!_server->bind(_listenAddress, _listenPort, _bondage)) { - Warning(_log, "Could not bind to %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort); + Error(_log, "Could not bind to %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort); } else { @@ -62,7 +67,13 @@ void UDPListener::start() WarningIf( ! joinGroupOK, _log, "Multicast failed"); } _isActive = true; - emit statusChanged(_isActive); + _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); + + if(_bonjourService == nullptr) + { + _bonjourService = new BonjourServiceRegister(); + _bonjourService->registerService("_hyperiond-udp._udp", _listenPort); + } } } @@ -73,7 +84,9 @@ void UDPListener::stop() _server->close(); _isActive = false; - emit statusChanged(_isActive); + Info(_log, "Stopped"); + _hyperion->clear(_priority); + _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); } void UDPListener::componentStateChanged(const hyperion::Components component, bool enable) @@ -84,9 +97,7 @@ void UDPListener::componentStateChanged(const hyperion::Components component, bo { if (enable) start(); else stop(); - Info(_log, "change state to %s", (enable ? "enabled" : "disabled") ); } - _hyperion->getComponentRegister().componentStateChanged(component, enable); } } @@ -105,9 +116,7 @@ void UDPListener::readPendingDatagrams() quint16 senderPort; _server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); - processTheDatagram(&datagram, &sender); - } } @@ -126,9 +135,26 @@ void UDPListener::processTheDatagram(const QByteArray * datagram, const QHostAdd rgb.green = datagram->at(ledIndex*3+1); rgb.blue = datagram->at(ledIndex*3+2); } - - _hyperion->setColors(_priority, _ledColors, _timeout, -1, hyperion::COMP_UDPLISTENER, sender->toString()); + // TODO provide a setInput with origin arg to overwrite senders smarter + _hyperion->registerInput(_priority, hyperion::COMP_UDPLISTENER, QString("UDPListener@%1").arg(sender->toString())); + _hyperion->setInput(_priority, _ledColors, _timeout); } +void UDPListener::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::UDPLISTENER) + { + QJsonObject obj = config.object(); + // if we change the prio we need to make sure the old one is cleared before we apply the new one! + stop(); - + QString addr = obj["address"].toString(""); + _priority = obj["priority"].toInt(); + _listenPort = obj["port"].toInt(); + _listenAddress = addr.isEmpty()? QHostAddress::AnyIPv4 : QHostAddress(addr); + _bondage = (obj["shared"].toBool(false)) ? QAbstractSocket::ShareAddress : QAbstractSocket::DefaultForPlatform; + _timeout = obj["timeout"].toInt(10000); + if(obj["enable"].toBool()) + start(); + } +} diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index a1483ec6..6b3865ac 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -9,11 +9,8 @@ if ( NOT ENABLE_PROFILER ) LIST ( REMOVE_ITEM Utils_SOURCES ${CURRENT_HEADER_DIR}/Profiler.h ${CURRENT_SOURCE_DIR}/Profiler.cpp ) endif() -set(Utils_RESOURCES ${CURRENT_SOURCE_DIR}/JSONRPC_schemas.qrc ) - add_library(hyperion-utils ${Utils_SOURCES} - ${Utils_RESOURCES} ) target_link_libraries(hyperion-utils diff --git a/libsrc/utils/FileUtils.cpp b/libsrc/utils/FileUtils.cpp index c7c6d208..31c70cd5 100644 --- a/libsrc/utils/FileUtils.cpp +++ b/libsrc/utils/FileUtils.cpp @@ -1,6 +1,7 @@ #include // qt incl +#include #include #include @@ -21,6 +22,17 @@ namespace FileUtils { return fi.path(); } + bool removeDir(const QString& path, Logger* log) + { + //QDir dir(path); + if(!QDir(path).removeRecursively()) + { + Error(log, "Failed to remove directory: %s", QSTRING_CSTR(path)); + return false; + } + return true; + } + bool fileExists(const QString& path, Logger* log, bool ignError) { QFile file(path); @@ -71,17 +83,18 @@ namespace FileUtils { return true; } - bool removeFile(const QString& path, Logger* log) + bool removeFile(const QString& path, Logger* log, bool ignError) { QFile file(path); if(!file.remove()) { - resolveFileError(file,log); + if(!ignError) + resolveFileError(file,log); return false; } return true; } - + QString convertPath(const QString path) { QString p = path; diff --git a/libsrc/utils/JSONRPC_schema/schema-clearall.json b/libsrc/utils/JSONRPC_schema/schema-clearall.json deleted file mode 100644 index 8c88cc6c..00000000 --- a/libsrc/utils/JSONRPC_schema/schema-clearall.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type":"object", - "required":true, - "properties":{ - "command": { - "type" : "string", - "required" : true, - "enum" : ["clearall"] - }, - "tan" : { - "type" : "integer" - } - }, - "additionalProperties": false -} diff --git a/libsrc/utils/JsonProcessor.cpp b/libsrc/utils/JsonProcessor.cpp deleted file mode 100644 index 5e114f6c..00000000 --- a/libsrc/utils/JsonProcessor.cpp +++ /dev/null @@ -1,1196 +0,0 @@ -// project includes -#include - -// stl includes -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - #include - #include - #include -#include - -// hyperion includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace hyperion; - -std::map JsonProcessor::_componentsPrevState; - -JsonProcessor::JsonProcessor(QString peerAddress, Logger* log, QObject* parent, bool noListener) - : QObject(parent) - , _peerAddress(peerAddress) - , _log(log) - , _hyperion(Hyperion::getInstance()) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) - , _streaming_logging_activated(false) - , _image_stream_timeout(0) - , _led_stream_timeout(0) -{ - // notify hyperion about a jsonMessageForward - connect(this, &JsonProcessor::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); - // notify hyperion about a push emit TODO: Remove! Make sure that the target of the commands trigger this (less error margin) instead this instance - connect(this, &JsonProcessor::pushReq, _hyperion, &Hyperion::hyperionStateChanged); - - if(!noListener) - { - // listen for sendServerInfo pushes from hyperion - connect(_hyperion, &Hyperion::sendServerInfo, this, &JsonProcessor::forceServerInfo); - } - - _image_stream_mutex.unlock(); - _led_stream_mutex.unlock(); -} - -void JsonProcessor::handleMessage(const QString& messageString) -{ - const QString ident = "JsonRpc@"+_peerAddress; - Q_INIT_RESOURCE(JSONRPC_schemas); - QJsonObject message; - // parse the message - if(!JsonUtils::parse(ident, messageString, message, _log)) - { - sendErrorReply("Errors during message parsing, please consult the Hyperion Log. Data:"+messageString); - return; - } - - // check basic message - if(!JsonUtils::validate(ident, message, ":schema", _log)) - { - sendErrorReply("Errors during message validation, please consult the Hyperion Log."); - return; - } - - // check specific message - const QString command = message["command"].toString(); - if(!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) - { - sendErrorReply("Errors during specific message validation, please consult the Hyperion Log"); - return; - } - - int tan = message["tan"].toInt(); - // switch over all possible commands and handle them - if (command == "color") handleColorCommand (message, command, tan); - else if (command == "image") handleImageCommand (message, command, tan); - else if (command == "effect") handleEffectCommand (message, command, tan); - else if (command == "create-effect") handleCreateEffectCommand (message, command, tan); - else if (command == "delete-effect") handleDeleteEffectCommand (message, command, tan); - else if (command == "sysinfo") handleSysInfoCommand (message, command, tan); - else if (command == "serverinfo") handleServerInfoCommand (message, command, tan); - else if (command == "clear") handleClearCommand (message, command, tan); - else if (command == "clearall") handleClearallCommand (message, command, tan); - else if (command == "adjustment") handleAdjustmentCommand (message, command, tan); - else if (command == "sourceselect") handleSourceSelectCommand (message, command, tan); - else if (command == "config") handleConfigCommand (message, command, tan); - else if (command == "componentstate") handleComponentStateCommand(message, command, tan); - else if (command == "ledcolors") handleLedColorsCommand (message, command, tan); - else if (command == "logging") handleLoggingCommand (message, command, tan); - else if (command == "processing") handleProcessingCommand (message, command, tan); - else if (command == "videomode") handleVideoModeCommand (message, command, tan); - else handleNotImplemented (); -} - -void JsonProcessor::handleColorCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - QString origin = message["origin"].toString() + "@"+_peerAddress; - - std::vector colorData(_hyperion->getLedCount()); - const QJsonArray & jsonColor = message["color"].toArray(); - unsigned int i = 0; - for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i) - { - colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt()); - colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt()); - colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt()); - } - - // copy full blocks of led colors - unsigned size = i; - while (i + size < _hyperion->getLedCount()) - { - memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb)); - i += size; - } - - // copy remaining block of led colors - if (i < _hyperion->getLedCount()) - { - memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb)); - } - - // set output - _hyperion->setColors(priority, colorData, duration, true, hyperion::COMP_COLOR, origin); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleImageCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - int width = message["imagewidth"].toInt(); - int height = message["imageheight"].toInt(); - QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); - - // check consistency of the size of the received data - if (data.size() != width*height*3) - { - sendErrorReply("Size of image data does not match with the width and height", command, tan); - return; - } - - // set width and height of the image processor - _imageProcessor->setSize(width, height); - - // create ImageRgb - Image image(width, height); - memcpy(image.memptr(), data.data(), data.size()); - - // process the image - std::vector ledColors = _imageProcessor->process(image); - _hyperion->setColors(priority, ledColors, duration); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - QString pythonScript = message["pythonScript"].toString(); - QString origin = message["origin"].toString() + "@"+_peerAddress; - const QJsonObject & effect = message["effect"].toObject(); - const QString & effectName = effect["name"].toString(); - - // set output - if (effect.contains("args")) - { - _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); - } - else - { - _hyperion->setEffect(effectName, priority, duration, origin); - } - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) -{ - if (!message["args"].toObject().isEmpty()) - { - QString scriptName; - (message["script"].toString().mid(0, 1) == ":" ) - ? scriptName = ":/effects//" + message["script"].toString().mid(1) - : scriptName = message["script"].toString(); - - std::list effectsSchemas = _hyperion->getEffectSchemas(); - std::list::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName)); - - if (it != effectsSchemas.end()) - { - if(!JsonUtils::validate("JsonRpc@"+_peerAddress, message["args"].toObject(), it->schemaFile, _log)) - { - sendErrorReply("Error during arg validation against schema, please consult the Hyperion Log", command, tan); - return; - } - - QJsonObject effectJson; - QJsonArray effectArray; - effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray(); - - if (effectArray.size() > 0) - { - if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) - { - sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan); - return; - } - - effectJson["name"] = message["name"].toString(); - effectJson["script"] = message["script"].toString(); - effectJson["args"] = message["args"].toObject(); - - std::list availableEffects = _hyperion->getEffects(); - std::list::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString())); - - QFileInfo newFileName; - if (iter != availableEffects.end()) - { - newFileName.setFile(iter->file); - if (newFileName.absoluteFilePath().mid(0, 1) == ":") - { - sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan); - return; - } - } else - { - QString f = FileUtils::convertPath(effectArray[0].toString() + "/" + message["name"].toString().replace(QString(" "), QString("")) + QString(".json")); - newFileName.setFile(f); - } - - if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log)) - { - sendErrorReply("Error while saving effect, please check the Hyperion Log", command, tan); - return; - } - - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - { - sendErrorReply("Can't save new effect. Effect path empty", command, tan); - return; - } - } else - sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan); - } else - sendErrorReply("Missing or empty Object 'args'", command, tan); -} - -void JsonProcessor::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - QString effectName = message["name"].toString(); - std::list effectsDefinition = _hyperion->getEffects(); - std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); - - if (it != effectsDefinition.end()) - { - QFileInfo effectConfigurationFile(it->file); - if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" ) - { - if (effectConfigurationFile.exists()) - { - bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); - if (result) - { - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan); - } else - sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan); - } else - sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan); - } else - sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan); -} - -void JsonProcessor::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan) -{ - // create result - QJsonObject result; - QJsonObject info; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - SysInfo::HyperionSysInfo data = SysInfo::get(); - QJsonObject system; - system["kernelType" ] = data.kernelType; - system["kernelVersion" ] = data.kernelVersion; - system["architecture" ] = data.architecture; - system["wordSize" ] = data.wordSize; - system["productType" ] = data.productType; - system["productVersion"] = data.productVersion; - system["prettyName" ] = data.prettyName; - system["hostName" ] = data.hostName; - system["domainName" ] = data.domainName; - info["system"] = system; - - QJsonObject hyperion; - hyperion["jsonrpc_version" ] = QString(HYPERION_JSON_VERSION); - hyperion["version" ] = QString(HYPERION_VERSION); - hyperion["build" ] = QString(HYPERION_BUILD_ID); - hyperion["time" ] = QString(__DATE__ " " __TIME__); - hyperion["id" ] = _hyperion->id; - info["hyperion"] = hyperion; - - // send the result - result["info" ] = info; - emit callbackMessage(result); -} - -void JsonProcessor::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - QJsonObject info; - - // collect priority information - QJsonArray priorities; - uint64_t now = QDateTime::currentMSecsSinceEpoch(); - QList activePriorities = _hyperion->getActivePriorities(); - Hyperion::PriorityRegister priorityRegister = _hyperion->getPriorityRegister(); - int currentPriority = _hyperion->getCurrentPriority(); - - foreach (int priority, activePriorities) { - const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); - QJsonObject item; - item["priority"] = priority; - if (priorityInfo.timeoutTime_ms != -1 ) - { - item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); - } - - item["owner"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["origin"] = priorityInfo.origin; - item["active"] = true; - item["visible"] = (priority == currentPriority); - - // remove item from prio register, because we have more valuable information via active priority - QList prios = priorityRegister.keys(priority); - if (! prios.empty()) - { - item["owner"] = prios[0]; - priorityRegister.remove(prios[0]); - } - - if(priorityInfo.componentId == hyperion::COMP_COLOR) - { - QJsonObject LEDcolor; - - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB", RGBValue); - - uint16_t Hue; - float Saturation, Luminace; - - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); - - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL", HSLValue); - - // add HEX Value to Array ["HEX Value"] - QJsonArray HEXValue; - std::stringstream hex; - hex << "0x" - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->red) - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->green) - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->blue); - - HEXValue.append(QString::fromStdString(hex.str())); - LEDcolor.insert("HEX", HEXValue); - - item["value"] = LEDcolor; - } - // priorities[priorities.size()] = item; - priorities.append(item); - } - - // append left over priorities - for(auto key : priorityRegister.keys()) - { - QJsonObject item; - item["priority"] = priorityRegister[key]; - item["active"] = false; - item["visible"] = false; - item["owner"] = key; - priorities.append(item); - } - - info["priorities"] = priorities; - info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); - - // collect adjustment information - QJsonArray adjustmentArray; - for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) - { - const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); - continue; - } - - QJsonObject adjustment; - adjustment["id"] = adjustmentId; - - QJsonArray blackAdjust; - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentR()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentG()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentB()); - adjustment.insert("black", blackAdjust); - - QJsonArray whiteAdjust; - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); - adjustment.insert("white", whiteAdjust); - - QJsonArray redAdjust; - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); - adjustment.insert("red", redAdjust); - - QJsonArray greenAdjust; - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); - adjustment.insert("green", greenAdjust); - - QJsonArray blueAdjust; - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); - adjustment.insert("blue", blueAdjust); - - QJsonArray cyanAdjust; - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); - adjustment.insert("cyan", cyanAdjust); - - QJsonArray magentaAdjust; - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); - adjustment.insert("magenta", magentaAdjust); - - QJsonArray yellowAdjust; - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); - adjustment.insert("yellow", yellowAdjust); - - adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); - adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); - adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); - adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); - adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); - adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); - adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); - - adjustmentArray.append(adjustment); - } - - info["adjustment"] = adjustmentArray; - - // collect effect info - QJsonArray effects; - const std::list & effectsDefinitions = _hyperion->getEffects(); - for (const EffectDefinition & effectDefinition : effectsDefinitions) - { - QJsonObject effect; - effect["name"] = effectDefinition.name; - effect["file"] = effectDefinition.file; - effect["script"] = effectDefinition.script; - effect["args"] = effectDefinition.args; - effects.append(effect); - } - - info["effects"] = effects; - - // get available led devices - QJsonObject ledDevices; - ledDevices["active"] = LedDevice::activeDevice(); - QJsonArray availableLedDevices; - for (auto dev: LedDevice::getDeviceMap()) - { - availableLedDevices.append(dev.first); - } - - ledDevices["available"] = availableLedDevices; - info["ledDevices"] = ledDevices; - - QJsonObject grabbers; - QJsonArray availableGrabbers; -#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) - // get available grabbers - //grabbers["active"] = ????; - for (auto grabber: GrabberWrapper::availableGrabbers()) - { - availableGrabbers.append(grabber); - } -#endif - grabbers["available"] = availableGrabbers; - grabbers["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); - info["grabbers"] = grabbers; - - // get available components - QJsonArray component; - std::map components = _hyperion->getComponentRegister().getRegister(); - for(auto comp : components) - { - QJsonObject item; - item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); - item["enabled"] = comp.second; - - component.append(item); - } - - info["components"] = component; - info["ledMAppingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); - - // Add Hyperion - QJsonObject hyperion; - hyperion["config_modified" ] = _hyperion->configModified(); - hyperion["config_writeable"] = _hyperion->configWriteable(); - hyperion["off"] = hyperionIsActive()? false : true; - - // sessions - QJsonArray sessions; - for (auto session: _hyperion->getHyperionSessions()) - { - if (session.port<0) continue; - QJsonObject item; - item["name"] = session.serviceName; - item["type"] = session.registeredType; - item["domain"] = session.replyDomain; - item["host"] = session.hostName; - item["address"]= session.address; - item["port"] = session.port; - sessions.append(item); - } - hyperion["sessions"] = sessions; - - info["hyperion"] = hyperion; - - // send the result - result["info"] = info; - emit callbackMessage(result); -} - -void JsonProcessor::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - - // clear priority - _hyperion->clear(priority); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // clear priority - _hyperion->clearall(); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan) -{ - const QJsonObject & adjustment = message["adjustment"].toObject(); - - const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first()); - ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str()); - return; - } - - if (adjustment.contains("red")) - { - const QJsonArray & values = adjustment["red"].toArray(); - colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("green")) - { - const QJsonArray & values = adjustment["green"].toArray(); - colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("blue")) - { - const QJsonArray & values = adjustment["blue"].toArray(); - colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("cyan")) - { - const QJsonArray & values = adjustment["cyan"].toArray(); - colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("magenta")) - { - const QJsonArray & values = adjustment["magenta"].toArray(); - colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("yellow")) - { - const QJsonArray & values = adjustment["yellow"].toArray(); - colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("black")) - { - const QJsonArray & values = adjustment["black"].toArray(); - colorAdjustment->_rgbBlackAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("white")) - { - const QJsonArray & values = adjustment["white"].toArray(); - colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("gammaRed")) - { - colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaGreen")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaBlue")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble()); - } - - if (adjustment.contains("backlightThreshold")) - { - colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble()); - } - if (adjustment.contains("backlightColored")) - { - colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool()); - } - if (adjustment.contains("brightness")) - { - colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt()); - } - if (adjustment.contains("brightnessCompensation")) - { - colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt()); - } - - // commit the changes - _hyperion->adjustmentsUpdated(); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleSourceSelectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - bool success = false; - if (message["auto"].toBool(false)) - { - _hyperion->setSourceAutoSelectEnabled(true); - success = true; - } - else if (message.contains("priority")) - { - success = _hyperion->setCurrentSourcePriority(message["priority"].toInt()); - } - - if (success) - { - sendSuccessReply(command, tan); - } - else - { - sendErrorReply("setting current priority failed", command, tan); - } -} - -void JsonProcessor::handleConfigCommand(const QJsonObject& message, const QString& command, const int tan) -{ - QString subcommand = message["subcommand"].toString(""); - QString full_command = command + "-" + subcommand; - - if (subcommand == "getschema") - { - handleSchemaGetCommand(message, full_command, tan); - } - else if (subcommand == "setconfig") - { - handleConfigSetCommand(message, full_command, tan); - } - else if (subcommand == "getconfig") - { - handleConfigGetCommand(message, full_command, tan); - } - else if (subcommand == "reload") - { - _hyperion->freeObjects(true); - Process::restartHyperion(); - sendErrorReply("failed to restart hyperion", full_command, tan); - } - else - { - sendErrorReply("unknown or missing subcommand", full_command, tan); - } -} - -void JsonProcessor::handleConfigSetCommand(const QJsonObject& message, const QString &command, const int tan) -{ - if(message.size() > 0) - { - if (message.contains("config")) - { - QJsonObject hyperionConfigJsonObj = message["config"].toObject(); - try - { - Q_INIT_RESOURCE(resource); - - QJsonObject schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); - - QJsonSchemaChecker schemaChecker; - schemaChecker.setSchema(schemaJson); - - QPair validate = schemaChecker.validate(hyperionConfigJsonObj); - - if (validate.first && validate.second) - { - QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj); - } - else if (!validate.first && validate.second) - { - Warning(_log,"Errors have been found in the configuration file. Automatic correction is applied"); - - QStringList schemaErrors = schemaChecker.getMessages(); - for (auto & schemaError : schemaErrors) - Info(_log, QSTRING_CSTR(schemaError)); - - hyperionConfigJsonObj = schemaChecker.getAutoCorrectedConfig(hyperionConfigJsonObj); - - if (!QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj)) - throw std::runtime_error("ERROR: can not save configuration file, aborting"); - } - else //Error in Schema - { - QString errorMsg = "ERROR: Json validation failed: \n"; - QStringList schemaErrors = schemaChecker.getMessages(); - for (auto & schemaError: schemaErrors) - { - Error(_log, "config write validation: %s", QSTRING_CSTR(schemaError)); - errorMsg += schemaError + "\n"; - } - - throw std::runtime_error(errorMsg.toStdString()); - } - sendSuccessReply(command, tan); - } - catch(const std::runtime_error& validate_error) - { - sendErrorReply("Error while validating json: " + QString(validate_error.what()), command, tan); - } - } - } - else - { - sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); - } -} - -void JsonProcessor::handleConfigGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - result["result"] = _hyperion->getQJsonConfig(); - - // send the result - emit callbackMessage(result); -} - -void JsonProcessor::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result, schemaJson, alldevices, properties; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(resource); - - // read the hyperion json schema from the resource - QString schemaFile = ":/hyperion-schema"; - - try - { - schemaJson = QJsonFactory::readSchema(schemaFile); - } - catch(const std::runtime_error& error) - { - throw std::runtime_error(error.what()); - } - - // collect all LED Devices - properties = schemaJson["properties"].toObject(); - alldevices = LedDevice::getLedDeviceSchemas(); - properties.insert("alldevices", alldevices); - - // collect all available effect schemas - QJsonObject pyEffectSchemas, pyEffectSchema; - QJsonArray in, ex; - const std::list & effectsSchemas = _hyperion->getEffectSchemas(); - for (const EffectSchema & effectSchema : effectsSchemas) - { - if (effectSchema.pyFile.mid(0, 1) == ":") - { - QJsonObject internal; - internal.insert("script", effectSchema.pyFile); - internal.insert("schemaLocation", effectSchema.schemaFile); - internal.insert("schemaContent", effectSchema.pySchema); - in.append(internal); - } - else - { - QJsonObject external; - external.insert("script", effectSchema.pyFile); - external.insert("schemaLocation", effectSchema.schemaFile); - external.insert("schemaContent", effectSchema.pySchema); - ex.append(external); - } - } - - if (!in.empty()) - pyEffectSchema.insert("internal", in); - if (!ex.empty()) - pyEffectSchema.insert("external", ex); - - pyEffectSchemas = pyEffectSchema; - properties.insert("effectSchemas", pyEffectSchemas); - - schemaJson.insert("properties", properties); - - result["result"] = schemaJson; - - // send the result - emit callbackMessage(result); -} - -void JsonProcessor::handleComponentStateCommand(const QJsonObject& message, const QString &command, const int tan) -{ - const QJsonObject & componentState = message["componentstate"].toObject(); - - QString compStr = componentState["component"].toString("invalid"); - bool compState = componentState["state"].toBool(true); - - if (compStr == "ALL" ) - { - if (hyperionIsActive() != compState) - { - std::map components = _hyperion->getComponentRegister().getRegister(); - - if (!compState) - { - JsonProcessor::_componentsPrevState = components; - } - - for(auto comp : components) - { - _hyperion->setComponentState(comp.first, compState ? JsonProcessor::_componentsPrevState[comp.first] : false); - } - - if (compState) - { - JsonProcessor::_componentsPrevState.clear(); - } - } - - sendSuccessReply(command, tan); - return; - - } - else - { - Components component = stringToComponent(compStr); - - if (hyperionIsActive()) - { - if (component != COMP_INVALID) - { - _hyperion->setComponentState(component, compState); - sendSuccessReply(command, tan); - return; - } - sendErrorReply("invalid component name", command, tan); - return; - } - sendErrorReply("can't change component state when hyperion is off", command, tan); - } -} - -void JsonProcessor::handleLedColorsCommand(const QJsonObject& message, const QString &command, const int tan) -{ - // create result - QString subcommand = message["subcommand"].toString(""); - - if (subcommand == "ledstream-start") - { - _streaming_leds_reply["success"] = true; - _streaming_leds_reply["command"] = command+"-ledstream-update"; - _streaming_leds_reply["tan"] = tan; - connect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate, Qt::UniqueConnection); - _ledcolorsLedsActive = true; - } - else if (subcommand == "ledstream-stop") - { - disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate); - _ledcolorsLedsActive = false; - } - else if (subcommand == "imagestream-start") - { - _streaming_image_reply["success"] = true; - _streaming_image_reply["command"] = command+"-imagestream-update"; - _streaming_image_reply["tan"] = tan; - connect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, SLOT(setImage(int, const Image&, const int)) ); - } - else if (subcommand == "imagestream-stop") - { - disconnect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, 0 ); - } - else - { - sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonProcessor::handleLoggingCommand(const QJsonObject& message, const QString &command, const int tan) -{ - // create result - QString subcommand = message["subcommand"].toString(""); - _streaming_logging_reply["success"] = true; - _streaming_logging_reply["command"] = command; - _streaming_logging_reply["tan"] = tan; - - if (subcommand == "start") - { - if (!_streaming_logging_activated) - { - _streaming_logging_reply["command"] = command+"-update"; - connect(LoggerManager::getInstance(),SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE))); - Debug(_log, "log streaming activated for client %s",_peerAddress.toStdString().c_str()); // needed to trigger log sending - } - } - else if (subcommand == "stop") - { - if (_streaming_logging_activated) - { - disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0); - _streaming_logging_activated = false; - Debug(_log, "log streaming deactivated for client %s",_peerAddress.toStdString().c_str()); - - } - } - else - { - sendErrorReply("unknown subcommand",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonProcessor::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) -{ - _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleVideoModeCommand(const QJsonObject& message, const QString &command, const int tan) -{ - _hyperion->setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleNotImplemented() -{ - sendErrorReply("Command not implemented"); -} - -void JsonProcessor::sendSuccessReply(const QString &command, const int tan) -{ - // create reply - QJsonObject reply; - reply["success"] = true; - reply["command"] = command; - reply["tan"] = tan; - - // send reply - emit callbackMessage(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 JsonProcessor::sendErrorReply(const QString &error, const QString &command, const int tan) -{ - // create reply - QJsonObject reply; - reply["success"] = false; - reply["error"] = error; - reply["command"] = command; - reply["tan"] = tan; - - // send reply - emit callbackMessage(reply); -} - - -void JsonProcessor::streamLedcolorsUpdate(const std::vector& ledColors) -{ - if ( (_led_stream_timeout+50) < QDateTime::currentMSecsSinceEpoch() && _led_stream_mutex.tryLock(0) ) - { - _led_stream_timeout = QDateTime::currentMSecsSinceEpoch(); - QJsonObject result; - QJsonArray leds; - - for(auto color = ledColors.begin(); color != ledColors.end(); ++color) - { - QJsonObject item; - item["index"] = int(color - ledColors.begin()); - item["red"] = color->red; - item["green"] = color->green; - item["blue"] = color->blue; - leds.append(item); - } - - result["leds"] = leds; - _streaming_leds_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_leds_reply); - - _led_stream_mutex.unlock(); - } -} - -void JsonProcessor::setImage(int priority, const Image & image, int duration_ms) -{ - if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) ) - { - _image_stream_timeout = QDateTime::currentMSecsSinceEpoch(); - - QImage jpgImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); - QByteArray ba; - QBuffer buffer(&ba); - buffer.open(QIODevice::WriteOnly); - jpgImage.save(&buffer, "jpg"); - - QJsonObject result; - result["image"] = "data:image/jpg;base64,"+QString(ba.toBase64()); - _streaming_image_reply["result"] = result; - emit callbackMessage(_streaming_image_reply); - - _image_stream_mutex.unlock(); - } -} - -void JsonProcessor::incommingLogMessage(Logger::T_LOG_MESSAGE msg) -{ - QJsonObject result, message; - QJsonArray messageArray; - - if (!_streaming_logging_activated) - { - _streaming_logging_activated = true; - QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); - for(int i=0; ilength(); i++) - { - message["appName"] = logBuffer->at(i).appName; - message["loggerName"] = logBuffer->at(i).loggerName; - message["function"] = logBuffer->at(i).function; - message["line"] = QString::number(logBuffer->at(i).line); - message["fileName"] = logBuffer->at(i).fileName; - message["message"] = logBuffer->at(i).message; - message["levelString"] = logBuffer->at(i).levelString; - - messageArray.append(message); - } - } - else - { - message["appName"] = msg.appName; - message["loggerName"] = msg.loggerName; - message["function"] = msg.function; - message["line"] = QString::number(msg.line); - message["fileName"] = msg.fileName; - message["message"] = msg.message; - message["levelString"] = msg.levelString; - - messageArray.append(message); - } - - result.insert("messages", messageArray); - _streaming_logging_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_logging_reply); -} - -void JsonProcessor::forceServerInfo() -{ - const QString command("serverinfo"); - const int tan = 1; - const QJsonObject obj; - handleServerInfoCommand(obj,command,tan); -} diff --git a/libsrc/utils/JsonUtils.cpp b/libsrc/utils/JsonUtils.cpp index d103978c..0040f241 100644 --- a/libsrc/utils/JsonUtils.cpp +++ b/libsrc/utils/JsonUtils.cpp @@ -9,8 +9,6 @@ #include #include -#include - namespace JsonUtils { bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError) @@ -38,13 +36,33 @@ namespace JsonUtils { } bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log) + { + QJsonDocument doc; + if(!parse(path, data, doc, log)) + return false; + + obj = doc.object(); + return true; + } + + bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log) + { + QJsonDocument doc; + if(!parse(path, data, doc, log)) + return false; + + arr = doc.array(); + return true; + } + + bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log) { //remove Comments in data QString cleanData = data; - cleanData.remove(QRegularExpression("([^:]?\\/\\/.*)")); + //cleanData .remove(QRegularExpression("([^:]?\\/\\/.*)")); QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error); + doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { @@ -63,7 +81,6 @@ namespace JsonUtils { Error(log,"Failed to parse json data from %s: Error: %s at Line: %i, Column: %i", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn); return false; } - obj = doc.object(); return true; } @@ -74,6 +91,14 @@ namespace JsonUtils { if(!readFile(schemaPath, schema, log)) return false; + if(!validate(file, json, schema, log)) + return false; + return true; + + } + + bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log) + { QJsonSchemaChecker schemaChecker; schemaChecker.setSchema(schema); if (!schemaChecker.validate(json).first) @@ -120,7 +145,7 @@ namespace JsonUtils { obj.insert(attribute, resolveRefs(attributeValue.toObject(), obj, log)); else { - qDebug() <<"ADD ATTR:VALUE"< namespace Process { - + void restartHyperion(bool asNewProcess) { Logger* log = Logger::getInstance("Process"); @@ -19,8 +19,8 @@ void restartHyperion(bool asNewProcess) << " *******************************************" << std::endl << " * hyperion will restart now *" << std::endl << " *******************************************" << std::endl << std::endl; - - + + QStringList qargs = QCoreApplication::arguments(); int size = qargs.size(); char *args[size+1]; @@ -43,7 +43,7 @@ QByteArray command_exec(QString cmd, QByteArray data) QString result = ""; std::shared_ptr pipe(popen(cmd.toLocal8Bit().constData(), "r"), pclose); - if (pipe) + if (pipe) { while (!feof(pipe.get())) { @@ -54,4 +54,4 @@ QByteArray command_exec(QString cmd, QByteArray data) return QSTRING_CSTR(result); } -}; \ No newline at end of file +}; diff --git a/libsrc/utils/RgbChannelAdjustment.cpp b/libsrc/utils/RgbChannelAdjustment.cpp index dc763ff8..84d6d059 100644 --- a/libsrc/utils/RgbChannelAdjustment.cpp +++ b/libsrc/utils/RgbChannelAdjustment.cpp @@ -21,7 +21,7 @@ RgbChannelAdjustment::~RgbChannelAdjustment() void RgbChannelAdjustment::resetInitialized() { - Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]); + //Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]); memset(_initialized, false, sizeof(_initialized)); } diff --git a/libsrc/utils/RgbTransform.cpp b/libsrc/utils/RgbTransform.cpp index 7c891dfc..2b3fd730 100644 --- a/libsrc/utils/RgbTransform.cpp +++ b/libsrc/utils/RgbTransform.cpp @@ -144,8 +144,8 @@ void RgbTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) { // apply gamma red = _mappingR[red]; - green = _mappingG[green]; - blue = _mappingB[blue]; + green = _mappingR[green]; + blue = _mappingR[blue]; // apply brightnesss int rgbSum = red+green+blue; diff --git a/libsrc/utils/Stats.cpp b/libsrc/utils/Stats.cpp index b78d6fc2..039e8361 100644 --- a/libsrc/utils/Stats.cpp +++ b/libsrc/utils/Stats.cpp @@ -23,7 +23,7 @@ Stats::Stats() { if (!(interface.flags() & QNetworkInterface::IsLoopBack)) { - _hyperion->id = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFileName().toLocal8Bit()),QCryptographicHash::Sha1).toHex()); + _hyperion->setId(QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFileName().toLocal8Bit()),QCryptographicHash::Sha1).toHex())); _hash = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); break; } @@ -34,12 +34,12 @@ Stats::Stats() { Warning(_log, "No interface found, abort"); // fallback id - _hyperion->id = QString(QCryptographicHash::hash(_hyperion->getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); + _hyperion->setId(QString(QCryptographicHash::hash(_hyperion->getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex())); return; } // prepare content - QJsonObject config = _hyperion->getConfig(); + QJsonObject config = _hyperion->getQJsonConfig(); SysInfo::HyperionSysInfo data = SysInfo::get(); QJsonObject system; @@ -49,8 +49,8 @@ Stats::Stats() system["pVersion" ] = data.productVersion; system["pName" ] = data.prettyName; system["version" ] = QString(HYPERION_VERSION); - system["device" ] = LedDevice::activeDevice(); - system["id" ] = _hyperion->id; + system["device" ] = Hyperion::getInstance()->getActiveDevice(); + system["id" ] = _hyperion->getId(); system["hw_id" ] = _hash; system["ledCount" ] = QString::number(Hyperion::getInstance()->getLedCount()); system["comp_sm" ] = config["smoothing"].toObject().take("enable"); @@ -100,7 +100,7 @@ void Stats::sendHTTP() void Stats::sendHTTPp() { - _req.setUrl(QUrl("https://api.hyperion-project.org/api/stats/"+_hyperion->id)); + _req.setUrl(QUrl("https://api.hyperion-project.org/api/stats/"+_hyperion->getId())); _mgr.put(_req,_ba); } @@ -122,7 +122,7 @@ bool Stats::trigger(bool set) { QString path = _hyperion->getRootPath()+"/misc/"; QDir dir; - QFile file(path + _hyperion->id); + QFile file(path + _hyperion->getId()); if(set && file.open(QIODevice::ReadWrite) ) { diff --git a/libsrc/webconfig/WebConfig.cpp b/libsrc/webconfig/WebConfig.cpp deleted file mode 100644 index 36c9cf71..00000000 --- a/libsrc/webconfig/WebConfig.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "webconfig/WebConfig.h" -#include "StaticFileServing.h" - -#include - -WebConfig::WebConfig(QObject * parent) - : QObject(parent) - , _hyperion(Hyperion::getInstance()) - , _server(nullptr) -{ - Logger* log = Logger::getInstance("WEBSERVER"); - _port = WEBCONFIG_DEFAULT_PORT; - _baseUrl = WEBCONFIG_DEFAULT_PATH; - const QJsonObject config = _hyperion->getQJsonConfig(); - - bool webconfigEnable = true; - - if (config.contains("webConfig")) - { - const QJsonObject webconfigConfig = config["webConfig"].toObject(); - webconfigEnable = webconfigConfig["enable"].toBool(true); - _port = webconfigConfig["port"].toInt(_port); - _baseUrl = webconfigConfig["document_root"].toString(_baseUrl); - } - - if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) - { - QFileInfo info(_baseUrl); - if (!info.exists() || !info.isDir()) - { - Error(log, "document_root '%s' is invalid, set to default '%s'", _baseUrl.toUtf8().constData(), WEBCONFIG_DEFAULT_PATH.toUtf8().constData()); - _baseUrl = WEBCONFIG_DEFAULT_PATH; - } - } - else - _baseUrl = WEBCONFIG_DEFAULT_PATH; - - Debug(log, "WebUI initialized, document root: %s", _baseUrl.toUtf8().constData()); - if ( webconfigEnable ) - { - start(); - } -} - - -WebConfig::~WebConfig() -{ - stop(); -} - - -void WebConfig::start() -{ - if ( _server == nullptr ) - _server = new StaticFileServing (_hyperion, _baseUrl, _port, this); -} - -void WebConfig::stop() -{ - if ( _server != nullptr ) - { - delete _server; - _server = nullptr; - } -} - - diff --git a/libsrc/webconfig/CMakeLists.txt b/libsrc/webserver/CMakeLists.txt similarity index 78% rename from libsrc/webconfig/CMakeLists.txt rename to libsrc/webserver/CMakeLists.txt index 51e99367..175df60e 100644 --- a/libsrc/webconfig/CMakeLists.txt +++ b/libsrc/webserver/CMakeLists.txt @@ -1,7 +1,7 @@ # Define the current source locations -set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webconfig) -set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webconfig) +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webserver) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webserver) FILE ( GLOB WebConfig_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) FILE ( GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig/* ) @@ -13,13 +13,14 @@ ENDFOREACH() CONFIGURE_FILE(${CURRENT_SOURCE_DIR}/WebConfig.qrc.in ${CMAKE_BINARY_DIR}/WebConfig.qrc ) SET(WebConfig_RESOURCES ${CMAKE_BINARY_DIR}/WebConfig.qrc) -add_library(webconfig +add_library(webserver ${WebConfig_SOURCES} ${WebConfig_RESOURCES} ) -target_link_libraries(webconfig +target_link_libraries(webserver hyperion hyperion-utils + hyperion-api Qt5::Network ) diff --git a/libsrc/webconfig/CgiHandler.cpp b/libsrc/webserver/CgiHandler.cpp similarity index 94% rename from libsrc/webconfig/CgiHandler.cpp rename to libsrc/webserver/CgiHandler.cpp index e4e5d404..a9ddc107 100644 --- a/libsrc/webconfig/CgiHandler.cpp +++ b/libsrc/webserver/CgiHandler.cpp @@ -13,12 +13,12 @@ #include #include -CgiHandler::CgiHandler (Hyperion * hyperion, QString baseUrl, QObject * parent) +CgiHandler::CgiHandler (Hyperion * hyperion, QObject * parent) : QObject(parent) , _hyperion(hyperion) , _args(QStringList()) , _hyperionConfig(_hyperion->getQJsonConfig()) - , _baseUrl(baseUrl) + , _baseUrl() , _log(Logger::getInstance("WEBSERVER")) { } @@ -27,6 +27,11 @@ CgiHandler::~CgiHandler() { } +void CgiHandler::setBaseUrl(const QString& url) +{ + _baseUrl = url; +} + void CgiHandler::exec(const QStringList & args, QtHttpRequest * request, QtHttpReply * reply) { try diff --git a/libsrc/webconfig/CgiHandler.h b/libsrc/webserver/CgiHandler.h similarity index 83% rename from libsrc/webconfig/CgiHandler.h rename to libsrc/webserver/CgiHandler.h index 02c5aa6a..0be79812 100644 --- a/libsrc/webconfig/CgiHandler.h +++ b/libsrc/webserver/CgiHandler.h @@ -15,25 +15,24 @@ class CgiHandler : public QObject { Q_OBJECT public: - CgiHandler (Hyperion * hyperion, QString baseUrl, QObject * parent = NULL); + CgiHandler (Hyperion * hyperion, QObject * parent = NULL); virtual ~CgiHandler (void); + void setBaseUrl(const QString& url); void exec(const QStringList & args,QtHttpRequest * request, QtHttpReply * reply); - + // cgi commands void cmd_cfg_jsonserver(); void cmd_runscript (); - + private: Hyperion* _hyperion; QtHttpReply * _reply; QtHttpRequest * _request; QStringList _args; const QJsonObject & _hyperionConfig; - const QString _baseUrl; + QString _baseUrl; Logger * _log; }; #endif // CGIHANDLER_H - - diff --git a/libsrc/webconfig/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp similarity index 99% rename from libsrc/webconfig/QtHttpClientWrapper.cpp rename to libsrc/webserver/QtHttpClientWrapper.cpp index 0720abe5..9f3c5474 100644 --- a/libsrc/webconfig/QtHttpClientWrapper.cpp +++ b/libsrc/webserver/QtHttpClientWrapper.cpp @@ -159,9 +159,8 @@ void QtHttpClientWrapper::onClientDataReceived (void) { this, &QtHttpClientWrapper::onReplySendHeadersRequested); connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested); - emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request - m_parsingStatus = sendReplyToClient (&reply); + m_parsingStatus = sendReplyToClient (&reply); break; } case ParsingError: { // there was an error durin one of parsing steps diff --git a/libsrc/webconfig/QtHttpClientWrapper.h b/libsrc/webserver/QtHttpClientWrapper.h similarity index 100% rename from libsrc/webconfig/QtHttpClientWrapper.h rename to libsrc/webserver/QtHttpClientWrapper.h diff --git a/libsrc/webconfig/QtHttpHeader.cpp b/libsrc/webserver/QtHttpHeader.cpp similarity index 100% rename from libsrc/webconfig/QtHttpHeader.cpp rename to libsrc/webserver/QtHttpHeader.cpp diff --git a/libsrc/webconfig/QtHttpHeader.h b/libsrc/webserver/QtHttpHeader.h similarity index 100% rename from libsrc/webconfig/QtHttpHeader.h rename to libsrc/webserver/QtHttpHeader.h diff --git a/libsrc/webconfig/QtHttpReply.cpp b/libsrc/webserver/QtHttpReply.cpp similarity index 100% rename from libsrc/webconfig/QtHttpReply.cpp rename to libsrc/webserver/QtHttpReply.cpp diff --git a/libsrc/webconfig/QtHttpReply.h b/libsrc/webserver/QtHttpReply.h similarity index 100% rename from libsrc/webconfig/QtHttpReply.h rename to libsrc/webserver/QtHttpReply.h diff --git a/libsrc/webconfig/QtHttpRequest.cpp b/libsrc/webserver/QtHttpRequest.cpp similarity index 100% rename from libsrc/webconfig/QtHttpRequest.cpp rename to libsrc/webserver/QtHttpRequest.cpp diff --git a/libsrc/webconfig/QtHttpRequest.h b/libsrc/webserver/QtHttpRequest.h similarity index 100% rename from libsrc/webconfig/QtHttpRequest.h rename to libsrc/webserver/QtHttpRequest.h diff --git a/libsrc/webconfig/QtHttpServer.cpp b/libsrc/webserver/QtHttpServer.cpp similarity index 66% rename from libsrc/webconfig/QtHttpServer.cpp rename to libsrc/webserver/QtHttpServer.cpp index d58b6aed..bccdb07b 100644 --- a/libsrc/webconfig/QtHttpServer.cpp +++ b/libsrc/webserver/QtHttpServer.cpp @@ -53,23 +53,20 @@ QString QtHttpServer::getErrorString (void) const { } void QtHttpServer::start (quint16 port) { - if (m_sockServer->listen (QHostAddress::Any, port)) { - emit started (m_sockServer->serverPort ()); - } - else { - emit error (m_sockServer->errorString ()); - } + if(!m_sockServer->isListening()) + { + if (m_sockServer->listen (QHostAddress::Any, port)) { + emit started (m_sockServer->serverPort ()); + } + else { + emit error (m_sockServer->errorString ()); + } + } } void QtHttpServer::stop (void) { if (m_sockServer->isListening ()) { m_sockServer->close (); - // disconnect clients - const QList socks = m_socksClientsHash.keys(); - for(auto sock : socks) - { - sock->close(); - } emit stopped (); } } @@ -94,22 +91,22 @@ void QtHttpServer::setCertificates (const QList & certs) { void QtHttpServer::onClientConnected (void) { while (m_sockServer->hasPendingConnections ()) { if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) { - connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected); - if (m_useSsl) { - if (QSslSocket * ssl = qobject_cast (sock)) { - connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors); - connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted); - connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError); - connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged); - ssl->setLocalCertificateChain (m_sslCerts); - ssl->setPrivateKey (m_sslKey); - ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer); - ssl->startServerEncryption (); - } - } - QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this); - m_socksClientsHash.insert (sock, wrapper); - emit clientConnected (wrapper->getGuid ()); + connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected); + if (m_useSsl) { + if (QSslSocket * ssl = qobject_cast (sock)) { + connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors); + connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted); + connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError); + connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged); + ssl->setLocalCertificateChain (m_sslCerts); + ssl->setPrivateKey (m_sslKey); + ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer); + ssl->startServerEncryption (); + } + } + QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this); + m_socksClientsHash.insert (sock, wrapper); + emit clientConnected (wrapper->getGuid ()); } } } diff --git a/libsrc/webconfig/QtHttpServer.h b/libsrc/webserver/QtHttpServer.h similarity index 100% rename from libsrc/webconfig/QtHttpServer.h rename to libsrc/webserver/QtHttpServer.h diff --git a/libsrc/webconfig/StaticFileServing.cpp b/libsrc/webserver/StaticFileServing.cpp similarity index 63% rename from libsrc/webconfig/StaticFileServing.cpp rename to libsrc/webserver/StaticFileServing.cpp index 80e8e901..85004470 100644 --- a/libsrc/webconfig/StaticFileServing.cpp +++ b/libsrc/webserver/StaticFileServing.cpp @@ -8,66 +8,29 @@ #include #include #include -#include -#include -#include -#include #include -StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent) +StaticFileServing::StaticFileServing (Hyperion *hyperion, QObject * parent) : QObject (parent) , _hyperion(hyperion) - , _baseUrl (baseUrl) - , _cgi(hyperion, baseUrl, this) + , _baseUrl () + , _cgi(hyperion, this) , _log(Logger::getInstance("WEBSERVER")) { Q_INIT_RESOURCE(WebConfig); _mimeDb = new QMimeDatabase; - - _server = new QtHttpServer (this); - _server->setServerName (QStringLiteral ("Hyperion WebConfig")); - - connect (_server, &QtHttpServer::started, this, &StaticFileServing::onServerStarted); - connect (_server, &QtHttpServer::stopped, this, &StaticFileServing::onServerStopped); - connect (_server, &QtHttpServer::error, this, &StaticFileServing::onServerError); - connect (_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply); - - _server->start (port); } StaticFileServing::~StaticFileServing () { - _server->stop (); + } -void StaticFileServing::onServerStarted (quint16 port) +void StaticFileServing::setBaseUrl(const QString& url) { - Info(_log, "started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); - const QJsonObject & generalConfig = _hyperion->getQJsonConfig()["general"].toObject(); - const QString mDNSDescr = generalConfig["name"].toString("") + "@" + QHostInfo::localHostName() + ":" + QString::number(port); - - // txt record for zeroconf - QString id = _hyperion->id; - std::string version = HYPERION_VERSION; - std::vector > txtRecord = {{"id",id.toStdString()},{"version",version}}; - - BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); - bonjourRegister_http->registerService( - BonjourRecord(mDNSDescr, "_hyperiond-http._tcp", QString()), - port, - txtRecord - ); - Debug(_log, "Web Config mDNS responder started"); -} - -void StaticFileServing::onServerStopped () { - Info(_log, "stopped %s", _server->getServerName().toStdString().c_str()); -} - -void StaticFileServing::onServerError (QString msg) -{ - Error(_log, "%s", msg.toStdString().c_str()); + _baseUrl = url; + _cgi.setBaseUrl(url); } void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage) @@ -133,7 +96,6 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl } return; } - Q_INIT_RESOURCE(WebConfig); QFileInfo info(_baseUrl % "/" % path); if ( path == "/" || path.isEmpty() ) diff --git a/libsrc/webconfig/StaticFileServing.h b/libsrc/webserver/StaticFileServing.h similarity index 69% rename from libsrc/webconfig/StaticFileServing.h rename to libsrc/webserver/StaticFileServing.h index 42e03950..875bad4d 100644 --- a/libsrc/webconfig/StaticFileServing.h +++ b/libsrc/webserver/StaticFileServing.h @@ -1,10 +1,9 @@ #ifndef STATICFILESERVING_H #define STATICFILESERVING_H -#include #include -#include "QtHttpServer.h" +//#include "QtHttpServer.h" #include "QtHttpRequest.h" #include "QtHttpReply.h" #include "QtHttpHeader.h" @@ -17,19 +16,17 @@ class StaticFileServing : public QObject { Q_OBJECT public: - explicit StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent = nullptr); + explicit StaticFileServing (Hyperion *hyperion, QObject * parent = nullptr); virtual ~StaticFileServing (void); + void setBaseUrl(const QString& url); + public slots: - void onServerStopped (void); - void onServerStarted (quint16 port); - void onServerError (QString msg); void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); private: Hyperion * _hyperion; QString _baseUrl; - QtHttpServer * _server; QMimeDatabase * _mimeDb; CgiHandler _cgi; Logger * _log; diff --git a/libsrc/webconfig/WebConfig.qrc.in b/libsrc/webserver/WebConfig.qrc.in similarity index 100% rename from libsrc/webconfig/WebConfig.qrc.in rename to libsrc/webserver/WebConfig.qrc.in diff --git a/libsrc/webconfig/WebJsonRpc.cpp b/libsrc/webserver/WebJsonRpc.cpp similarity index 74% rename from libsrc/webconfig/WebJsonRpc.cpp rename to libsrc/webserver/WebJsonRpc.cpp index 16f4a52b..85e16fa0 100644 --- a/libsrc/webconfig/WebJsonRpc.cpp +++ b/libsrc/webserver/WebJsonRpc.cpp @@ -4,7 +4,7 @@ #include "QtHttpServer.h" #include "QtHttpClientWrapper.h" -#include +#include WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent) : QObject(parent) @@ -13,20 +13,20 @@ WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClien , _log(Logger::getInstance("HTTPJSONRPC")) { const QString client = request->getClientInfo().clientAddress.toString(); - _jsonProcessor = new JsonProcessor(client, _log, this, true); - connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebJsonRpc::handleCallback); + _jsonAPI = new JsonAPI(client, _log, this, true); + connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback); } void WebJsonRpc::handleMessage(QtHttpRequest* request) { QByteArray data = request->getRawData(); _unlocked = true; - _jsonProcessor->handleMessage(data); + _jsonAPI->handleMessage(data); } void WebJsonRpc::handleCallback(QJsonObject obj) { - // guard against wrong callbacks; TODO: Remove when JsonProcessor is more solid + // guard against wrong callbacks; TODO: Remove when JSONAPI is more solid if(!_unlocked) return; _unlocked = false; // construct reply with headers timestamp and server name diff --git a/libsrc/webconfig/WebJsonRpc.h b/libsrc/webserver/WebJsonRpc.h similarity index 89% rename from libsrc/webconfig/WebJsonRpc.h rename to libsrc/webserver/WebJsonRpc.h index 5aabfa71..d074dd2f 100644 --- a/libsrc/webconfig/WebJsonRpc.h +++ b/libsrc/webserver/WebJsonRpc.h @@ -5,7 +5,7 @@ class QtHttpServer; class QtHttpRequest; class QtHttpClientWrapper; -class JsonProcessor; +class JsonAPI; class WebJsonRpc : public QObject { Q_OBJECT @@ -18,7 +18,7 @@ private: QtHttpServer* _server; QtHttpClientWrapper* _wrapper; Logger* _log; - JsonProcessor* _jsonProcessor; + JsonAPI* _jsonAPI; bool _unlocked = false; diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp new file mode 100644 index 00000000..2298d25d --- /dev/null +++ b/libsrc/webserver/WebServer.cpp @@ -0,0 +1,99 @@ +#include "webserver/WebServer.h" +#include "StaticFileServing.h" +#include "QtHttpServer.h" + +// bonjour +#include +#include + +#include + +WebServer::WebServer(const QJsonDocument& config, QObject * parent) + : QObject(parent) + , _log(Logger::getInstance("WEBSERVER")) + , _hyperion(Hyperion::getInstance()) + , _server(new QtHttpServer (this)) +{ + _server->setServerName (QStringLiteral ("Hyperion Webserver")); + + connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted); + connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped); + connect (_server, &QtHttpServer::error, this, &WebServer::onServerError); + + // create StaticFileServing + _staticFileServing = new StaticFileServing (_hyperion, this); + connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply); + + Debug(_log, "Instance created"); + // init + handleSettingsUpdate(settings::WEBSERVER, config); +} + +WebServer::~WebServer() +{ + stop(); +} + +void WebServer::onServerStarted (quint16 port) +{ + Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); + + BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); + bonjourRegister_http->registerService("_hyperiond-http._tcp", port); +} + +void WebServer::onServerStopped () { + Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str()); +} + +void WebServer::onServerError (QString msg) +{ + Error(_log, "%s", msg.toStdString().c_str()); +} + +void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::WEBSERVER) + { + const QJsonObject& obj = config.object(); + + bool webconfigEnable = obj["enable"].toBool(true); + _baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH); + + + if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) + { + QFileInfo info(_baseUrl); + if (!info.exists() || !info.isDir()) + { + Error(_log, "document_root '%s' is invalid", _baseUrl.toUtf8().constData()); + _baseUrl = WEBSERVER_DEFAULT_PATH; + } + } + else + _baseUrl = WEBSERVER_DEFAULT_PATH; + + Debug(_log, "Set document root to: %s", _baseUrl.toUtf8().constData()); + _staticFileServing->setBaseUrl(_baseUrl); + + if(_port != obj["port"].toInt(WEBSERVER_DEFAULT_PORT)) + { + _port = obj["port"].toInt(WEBSERVER_DEFAULT_PORT); + stop(); + } + if ( webconfigEnable ) + { + start(); + } + } +} + +void WebServer::start() +{ + _server->start(_port); +} + +void WebServer::stop() +{ + _server->stop(); +} diff --git a/libsrc/webconfig/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp similarity index 96% rename from libsrc/webconfig/WebSocketClient.cpp rename to libsrc/webserver/WebSocketClient.cpp index c24a796d..7ee64530 100644 --- a/libsrc/webconfig/WebSocketClient.cpp +++ b/libsrc/webserver/WebSocketClient.cpp @@ -3,7 +3,7 @@ #include "QtHttpHeader.h" #include -#include +#include #include #include @@ -23,8 +23,8 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObje const QString client = request->getClientInfo().clientAddress.toString(); // Json processor - _jsonProcessor = new JsonProcessor(client, _log, this); - connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebSocketClient::sendMessage); + _jsonAPI = new JsonAPI(client, _log, this); + connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage); Debug(_log, "New connection from %s", QSTRING_CSTR(client)); @@ -112,7 +112,7 @@ void WebSocketClient::handleWebSocketFrame(void) _onContinuation = false; if (_wsh.opCode == OPCODE::TEXT) { - _jsonProcessor->handleMessage(QString(_wsReceiveBuffer)); + _jsonAPI->handleMessage(QString(_wsReceiveBuffer)); } else { @@ -238,7 +238,8 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data) image.resize(width, height); memcpy(image.memptr(), data.data()+4, imgSize); - _hyperion->setImage(priority, image, duration_s*1000); + //_hyperion->registerInput(); + _hyperion->setInputImage(priority, image, duration_s*1000); } qint64 WebSocketClient::sendMessage(QJsonObject obj) diff --git a/libsrc/webconfig/WebSocketClient.h b/libsrc/webserver/WebSocketClient.h similarity index 97% rename from libsrc/webconfig/WebSocketClient.h rename to libsrc/webserver/WebSocketClient.h index 7aaf3ce3..05bfb1c7 100644 --- a/libsrc/webconfig/WebSocketClient.h +++ b/libsrc/webserver/WebSocketClient.h @@ -7,7 +7,7 @@ class QTcpSocket; class QtHttpRequest; class Hyperion; -class JsonProcessor; +class JsonAPI; class WebSocketClient : public QObject { Q_OBJECT @@ -27,7 +27,7 @@ private: QTcpSocket* _socket; Logger* _log; Hyperion* _hyperion; - JsonProcessor* _jsonProcessor; + JsonAPI* _jsonAPI; void getWsFrameHeader(WebSocketHeader* header); void sendClose(int status, QString reason = ""); diff --git a/libsrc/webconfig/WebSocketUtils.h b/libsrc/webserver/WebSocketUtils.h similarity index 100% rename from libsrc/webconfig/WebSocketUtils.h rename to libsrc/webserver/WebSocketUtils.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89a91802..428a45e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,4 +25,3 @@ endif() if(ENABLE_OSX) add_subdirectory(hyperion-osx) endif() - diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt index 48516408..feddffd1 100644 --- a/src/hyperion-aml/CMakeLists.txt +++ b/src/hyperion-aml/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries(${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient amlogic-grabber framebuffer-grabber Qt5::Core diff --git a/src/hyperion-dispmanx/CMakeLists.txt b/src/hyperion-dispmanx/CMakeLists.txt index a35dfd1c..4d4e2425 100644 --- a/src/hyperion-dispmanx/CMakeLists.txt +++ b/src/hyperion-dispmanx/CMakeLists.txt @@ -34,7 +34,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient dispmanx-grabber ${Dispmanx_LIBRARIES} Qt5::Core diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt index 452604d6..83f69f72 100644 --- a/src/hyperion-framebuffer/CMakeLists.txt +++ b/src/hyperion-framebuffer/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient framebuffer-grabber Qt5::Core Qt5::Gui diff --git a/src/hyperion-osx/CMakeLists.txt b/src/hyperion-osx/CMakeLists.txt index 69b78ff4..8f234ebe 100644 --- a/src/hyperion-osx/CMakeLists.txt +++ b/src/hyperion-osx/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient osx-grabber Qt5::Core Qt5::Gui diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 9bf50daa..d9e81d24 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -275,7 +275,8 @@ void JsonConnection::clearAll() // create command QJsonObject command; - command["command"] = QString("clearall"); + command["command"] = QString("clear"); + command["priority"] = -1; // send command message QJsonObject reply = sendMessage(command); diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index f768dd0c..6ff41a81 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -28,7 +28,7 @@ target_link_libraries(${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient Qt5::Core Qt5::Gui Qt5::Network diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 5eb4adc3..e343a340 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -60,8 +60,6 @@ int main(int argc, char** argv) SwitchOption & argVideoStandard= parser.add>('v', "video-standard", "The used video standard. Valid values are PAL, NTSC, SECAM or no-change. [default: %1]", "no-change"); SwitchOption & argPixelFormat = parser.add> (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, RGB32 or no-change. [default: %1]", "no-change"); IntOption & argInput = parser.add (0x0, "input", "Input channel (optional)", "-1"); - IntOption & argWidth = parser.add (0x0, "width", "Try to set the width of the video input [default: %1]", "-1"); - IntOption & argHeight = parser.add (0x0, "height", "Try to set the height of the video input [default: %1]", "-1"); IntOption & argCropWidth = parser.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0"); IntOption & argCropHeight = parser.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0"); IntOption & argCropLeft = parser.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); @@ -69,7 +67,6 @@ int main(int argc, char** argv) IntOption & argCropTop = parser.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); IntOption & argCropBottom = parser.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); IntOption & argSizeDecimation = parser.add ('s', "size-decimator", "Decimation factor for the output size [default=%1]", "1"); - IntOption & argFrameDecimation = parser.add ('f', "frame-decimator", "Decimation factor for the video frames [default=%1]", "1"); BooleanOption & argScreenshot = parser.add(0x0, "screenshot", "Take a single screenshot, save it to file and quit"); BooleanOption & argSignalDetection = parser.add('s', "signal-detection-disabled", "disable signal detection"); @@ -115,10 +112,6 @@ int main(int argc, char** argv) argInput.getInt(parser), argVideoStandard.switchValue(parser), argPixelFormat.switchValue(parser), - argWidth.getInt(parser), - argHeight.getInt(parser), - std::max(1, argFrameDecimation.getInt(parser)), - std::max(1, argSizeDecimation.getInt(parser)), std::max(1, argSizeDecimation.getInt(parser))); // set signal detection diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index a38bdfda..7e773598 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -29,7 +29,7 @@ target_link_libraries(${PROJECT_NAME} blackborder commandline hyperion-utils - protoserver + protoclient x11-grabber ${X11_LIBRARIES} ${X11_Xrender_LIB} diff --git a/src/hyperion-x11/X11Wrapper.cpp b/src/hyperion-x11/X11Wrapper.cpp index 83c39961..f78335c8 100644 --- a/src/hyperion-x11/X11Wrapper.cpp +++ b/src/hyperion-x11/X11Wrapper.cpp @@ -2,9 +2,9 @@ // Hyperion-X11 includes #include "X11Wrapper.h" -X11Wrapper::X11Wrapper(int grabInterval, bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) : +X11Wrapper::X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) : _timer(this), - _grabber(useXGetImage, cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation) + _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) { _timer.setSingleShot(false); _timer.setInterval(grabInterval); @@ -36,7 +36,7 @@ bool X11Wrapper::displayInit() void X11Wrapper::capture() { - _grabber.grabFrame(_screenshot, false); + _grabber.grabFrame(_screenshot, true); emit sig_screenshot(_screenshot); } diff --git a/src/hyperion-x11/X11Wrapper.h b/src/hyperion-x11/X11Wrapper.h index 9ec59c92..8f014f47 100644 --- a/src/hyperion-x11/X11Wrapper.h +++ b/src/hyperion-x11/X11Wrapper.h @@ -12,7 +12,7 @@ class X11Wrapper : public QObject { Q_OBJECT public: - X11Wrapper(int grabInterval, bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); const Image & getScreenshot(); diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp index 2d0ac3f1..15f94afb 100644 --- a/src/hyperion-x11/hyperion-x11.cpp +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -33,7 +33,6 @@ int main(int argc, char ** argv) Parser parser("X11 capture application for Hyperion"); IntOption & argFps = parser.add ('f', "framerate", "Capture frame rate [default: %1]", "10"); - BooleanOption & argXGetImage = parser.add('x', "xgetimage", "Use XGetImage instead of XRender"); IntOption & argCropWidth = parser.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0"); IntOption & argCropHeight = parser.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0"); IntOption & argCropLeft = parser.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); @@ -59,13 +58,11 @@ int main(int argc, char ** argv) // Create the X11 grabbing stuff X11Wrapper x11Wrapper( 1000 / argFps.getInt(parser), - parser.isSet(argXGetImage), parser.isSet(argCropLeft) ? argCropLeft.getInt(parser) : argCropWidth.getInt(parser), parser.isSet(argCropRight) ? argCropRight.getInt(parser) : argCropWidth.getInt(parser), parser.isSet(argCropTop) ? argCropTop.getInt(parser) : argCropHeight.getInt(parser), parser.isSet(argCropBottom) ? argCropBottom.getInt(parser) : argCropHeight.getInt(parser), - argSizeDecimation.getInt(parser), // horizontal decimation - argSizeDecimation.getInt(parser)); // vertical decimation + argSizeDecimation.getInt(parser)); // pixel decimation if (!x11Wrapper.displayInit()) return -1; diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index d6fb90f9..3a78377a 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -1,4 +1,7 @@ +find_package(PythonLibs 3.4 REQUIRED) +include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + add_executable(hyperiond hyperiond.h systray.h @@ -15,8 +18,10 @@ target_link_libraries(hyperiond boblightserver udplistener protoserver - webconfig + webserver bonjour + python + ${PYTHON_LIBRARIES} ) if (ENABLE_DISPMANX) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 0eb9e90d..d68220cc 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -16,80 +15,136 @@ #include #include -#include "HyperionConfig.h" - -#include #include #include #include -#include -#include -#include -#include #include #include #include #include - +#include +#include +#include // Required to determine the cmake options #include "hyperiond.h" -#include +// bonjour browser +#include + +// settings +#include + +// Init Python +#include + +HyperionDaemon* HyperionDaemon::daemon = nullptr; HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObject *parent) : QObject(parent) - , _log(Logger::getInstance("MAIN")) + , _log(Logger::getInstance("DAEMON")) + , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) + , _pyInit(new PythonInit()) + , _webserver(nullptr) , _jsonServer(nullptr) , _protoServer(nullptr) , _boblightServer(nullptr) , _udpListener(nullptr) , _v4l2Grabbers() , _dispmanx(nullptr) -#ifdef ENABLE_X11 , _x11Grabber(nullptr) -#endif , _amlGrabber(nullptr) , _fbGrabber(nullptr) , _osxGrabber(nullptr) , _hyperion(nullptr) , _stats(nullptr) + , _currVideoMode(VIDEO_2D) { - loadConfig(configFile); + HyperionDaemon::daemon = this; + // init settings + _settingsManager = new SettingsManager(0,configFile); + + const QJsonObject& logConfig = _settingsManager->getSetting(settings::LOGGER).object(); if (Logger::getLogLevel() == Logger::WARNING) { - if (_qconfig.contains("logger")) - { - const QJsonObject & logConfig = _qconfig["logger"].toObject(); - std::string level = logConfig["level"].toString("warn").toStdString(); // silent warn verbose debug - if (level == "silent") Logger::setLogLevel(Logger::OFF); - else if (level == "warn") Logger::setLogLevel(Logger::WARNING); - else if (level == "verbose") Logger::setLogLevel(Logger::INFO); - else if (level == "debug") Logger::setLogLevel(Logger::DEBUG); - else Error(Logger::getInstance("LOGGER"), "log level '%s' used in config is unknown. valid: silent warn verbose debug", level.c_str()); - - } + std::string level = logConfig["level"].toString("warn").toStdString(); // silent warn verbose debug + if (level == "silent") Logger::setLogLevel(Logger::OFF); + else if (level == "warn") Logger::setLogLevel(Logger::WARNING); + else if (level == "verbose") Logger::setLogLevel(Logger::INFO); + else if (level == "debug") Logger::setLogLevel(Logger::DEBUG); } else { - WarningIf(_qconfig.contains("logger"), Logger::getInstance("LOGGER"), "Logger settings overridden by command line argument"); + Warning(Logger::getInstance("LOGGER"), "Logger settings overridden by command line argument"); } - _hyperion = Hyperion::initInstance(_qconfig, configFile, rootPath); + _hyperion = Hyperion::initInstance(this, 0, configFile, rootPath); Info(_log, "Hyperion initialized"); + + connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects())); + // listen for setting changes + connect(_hyperion, &Hyperion::settingsChanged, this, &HyperionDaemon::settingsChanged); + // listen for setting changes of framegrabber and v4l2 + connect(this, &HyperionDaemon::settingsChanged, this, &HyperionDaemon::handleSettingsUpdate); + // forward system and v4l images to Hyperion + connect(this, &HyperionDaemon::systemImage, _hyperion, &Hyperion::systemImage); + connect(this, &HyperionDaemon::v4lImage, _hyperion, &Hyperion::v4lImage); + // forward videoModes from Hyperion to Daemon evaluation + connect(_hyperion, &Hyperion::videoMode, this, &HyperionDaemon::setVideoMode); + // forward videoMode changes from Daemon to Hyperion + connect(this, &HyperionDaemon::videoMode, _hyperion, &Hyperion::newVideoMode); + + // ---- grabber ----- + #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) + Warning(_log, "No platform capture can be instantiated, because all grabbers have been left out from the build"); + #endif + // init system capture (framegrabber) + handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + // init v4l2 capture + handleSettingsUpdate(settings::V4L2, getSetting(settings::V4L2)); + // ---- network services ----- + startNetworkServices(); } HyperionDaemon::~HyperionDaemon() { freeObjects(); delete _hyperion; + delete _settingsManager; + delete _pyInit; +} + +quint16 HyperionDaemon::getWebServerPort() +{ + return _webserver->getPort(); +} + +void HyperionDaemon::setVideoMode(const VideoMode& mode) +{ + if(_currVideoMode != mode) + { + _currVideoMode = mode; + emit videoMode(mode); + } +} + +const QJsonDocument HyperionDaemon::getSetting(const settings::type &type) +{ + return _settingsManager->getSetting(type); } void HyperionDaemon::freeObjects() { _hyperion->clearall(true); - Debug(_log, "destroy grabbers and network stuff"); + // destroy network first as a client might want to access pointers + delete _webserver; + delete _jsonServer; + delete _protoServer; + delete _boblightServer; + delete _udpListener; + + delete _bonjourBrowserWrapper; delete _amlGrabber; delete _dispmanx; delete _fbGrabber; @@ -98,17 +153,15 @@ void HyperionDaemon::freeObjects() { delete grabber; } - delete _jsonServer; - delete _protoServer; - delete _boblightServer; - delete _udpListener; delete _stats; _v4l2Grabbers.clear(); + _bonjourBrowserWrapper = nullptr; _amlGrabber = nullptr; _dispmanx = nullptr; _fbGrabber = nullptr; _osxGrabber = nullptr; + _webserver = nullptr; _jsonServer = nullptr; _protoServer = nullptr; _boblightServer = nullptr; @@ -116,332 +169,159 @@ void HyperionDaemon::freeObjects() _stats = nullptr; } -void HyperionDaemon::run() -{ - // ---- network services ----- - startNetworkServices(); - - // ---- grabber ----- - createGrabberV4L2(); - createSystemFrameGrabber(); - - #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) - WarningIf(_qconfig.contains("framegrabber"), _log, "No grabber can be instantiated, because all grabbers have been left out from the build"); - #endif - Info(_log, "Hyperion started"); - - connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects())); - - startInitialEffect(); -} - -void HyperionDaemon::loadConfig(const QString & configFile) -{ - Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); - - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(resource); - - // read the json schema from the resource - QString schemaFile = ":/hyperion-schema"; - QJsonObject schemaJson; - try - { - //QJsonObject obj; - //JsonUtils::readSchema(schemaFile, obj, _log); - schemaJson = QJsonFactory::readSchema(schemaFile); - } - catch(const std::runtime_error& error) - { - throw std::runtime_error(error.what()); - } - - QJsonSchemaChecker schemaChecker; - schemaChecker.setSchema(schemaJson); - - if(!JsonUtils::readFile(configFile, _qconfig, _log)) - throw std::runtime_error("Failed to load config!"); - - // validate config with schema and correct it if required - QPair validate = schemaChecker.validate(_qconfig); - - // errors in schema syntax, abort - if (!validate.second) - { - foreach (auto & schemaError, schemaChecker.getMessages()) - Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); - - throw std::runtime_error("ERROR: Hyperion schema has errors!"); - } - // errors in configuration, correct it! - if (!validate.first) - { - Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); - _qconfig = schemaChecker.getAutoCorrectedConfig(_qconfig); - - foreach (auto & schemaError, schemaChecker.getMessages()) - Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); - - if (!JsonUtils::write(configFile, _qconfig, _log)) - throw std::runtime_error("ERROR: Can't save configuration file, aborting"); - } -} - - -void HyperionDaemon::startInitialEffect() -{ - #define FGCONFIG_ARRAY fgColorConfig.toArray() - #define BGCONFIG_ARRAY bgColorConfig.toArray() - - Hyperion *hyperion = Hyperion::getInstance(); - - // create boot sequence - const QJsonObject & FGEffectConfig = _qconfig["foregroundEffect"].toObject(); - const QJsonObject & BGEffectConfig = _qconfig["backgroundEffect"].toObject(); - const int FG_PRIORITY = 0; - const int DURATION_INFINITY = 0; - const int BG_PRIORITY = PriorityMuxer::LOWEST_PRIORITY-1; - - // clear the leds - hyperion->setColor(FG_PRIORITY, ColorRgb::BLACK, 100, false); - - // initial foreground effect/color - if (FGEffectConfig["enable"].toBool(true)) - { - const QString fgTypeConfig = FGEffectConfig["type"].toString("effect"); - const QString fgEffectConfig = FGEffectConfig["effect"].toString("Rainbow swirl fast"); - const QJsonValue fgColorConfig = FGEffectConfig["color"]; - int default_fg_duration_ms = 3000; - int fg_duration_ms = FGEffectConfig["duration_ms"].toInt(default_fg_duration_ms); - if (fg_duration_ms == DURATION_INFINITY) - { - fg_duration_ms = default_fg_duration_ms; - Warning(_log, "foreground effect duration 'infinity' is forbidden, set to default value %d ms",default_fg_duration_ms); - } - if ( fgTypeConfig.contains("color") ) - { - ColorRgb fg_color = { - (uint8_t)FGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(2).toInt(0) - }; - hyperion->setColor(FG_PRIORITY, fg_color, fg_duration_ms, false); - Info(_log,"Inital foreground color set (%d %d %d)",fg_color.red,fg_color.green,fg_color.blue); - } - else - { - int result = hyperion->setEffect(fgEffectConfig, FG_PRIORITY, fg_duration_ms); - Info(_log,"Inital foreground effect '%s' %s", QSTRING_CSTR(fgEffectConfig), ((result == 0) ? "started" : "failed")); - } - } - // initial background effect/color - if (BGEffectConfig["enable"].toBool(true)) - { - const QString bgTypeConfig = BGEffectConfig["type"].toString("effect"); - const QString bgEffectConfig = BGEffectConfig["effect"].toString("Warm mood blobs"); - const QJsonValue bgColorConfig = BGEffectConfig["color"]; - if (bgTypeConfig.contains("color")) - { - ColorRgb bg_color = { - (uint8_t)BGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(2).toInt(0) - }; - hyperion->setColor(BG_PRIORITY, bg_color, DURATION_INFINITY, false); - Info(_log,"Inital background color set (%d %d %d)",bg_color.red,bg_color.green,bg_color.blue); - } - else - { - int result = hyperion->setEffect(bgEffectConfig, BG_PRIORITY, DURATION_INFINITY); - Info(_log,"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); - } - } - - #undef FGCONFIG_ARRAY - #undef BGCONFIG_ARRAY -} - void HyperionDaemon::startNetworkServices() { - // Create Stats + // Create Stats before network services _stats = new Stats(); - // Create Json server if configuration is present - unsigned int jsonPort = 19444; - if (_qconfig.contains("jsonServer")) - { - const QJsonObject & jsonServerConfig = _qconfig["jsonServer"].toObject(); - //jsonEnable = jsonServerConfig.get("enable", true).asBool(); - jsonPort = jsonServerConfig["port"].toInt(jsonPort); - } + // Create Json server + _jsonServer = new JsonServer(getSetting(settings::JSONSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _jsonServer, &JsonServer::handleSettingsUpdate); - _jsonServer = new JsonServer(jsonPort); - Info(_log, "Json server created and started on port %d", _jsonServer->getPort()); + // Create Proto server + _protoServer = new ProtoServer(getSetting(settings::PROTOSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate); + //QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SLOT(setVideoMode(VideoMode))); - // Create Proto server if configuration is present - unsigned int protoPort = 19445; - if (_qconfig.contains("protoServer")) - { - const QJsonObject & protoServerConfig = _qconfig["protoServer"].toObject(); - //protoEnable = protoServerConfig.get("enable", true).asBool(); - protoPort = protoServerConfig["port"].toInt(protoPort); - } + // boblight server + _boblightServer = new BoblightServer(getSetting(settings::BOBLSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate); - _protoServer = new ProtoServer(protoPort); - QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SIGNAL(videoMode(VideoMode))); - Info(_log, "Proto server created and started on port %d", _protoServer->getPort()); + // Create UDP listener + _udpListener = new UDPListener(getSetting(settings::UDPLISTENER)); + connect(this, &HyperionDaemon::settingsChanged, _udpListener, &UDPListener::handleSettingsUpdate); - // Create Boblight server if configuration is present - bool boblightConfigured = _qconfig.contains("boblightServer"); - - const QJsonObject & boblightServerConfig = _qconfig["boblightServer"].toObject(); - _boblightServer = new BoblightServer( - boblightServerConfig["priority"].toInt(710), - boblightServerConfig["port"].toInt(19333) ); - Debug(_log, "Boblight server created"); - - if ( boblightConfigured && boblightServerConfig["enable"].toBool(true)) - { - _boblightServer->start(); - } - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BOBLIGHTSERVER, _boblightServer->componentState()); - connect( Hyperion::getInstance(), SIGNAL(componentStateChanged(hyperion::Components,bool)), _boblightServer, SLOT(componentStateChanged(hyperion::Components,bool))); - - // Create UDP listener if configuration is present - bool udpListenerConfigured = _qconfig.contains("udpListener"); - const QJsonObject & udpListenerConfig = _qconfig["udpListener"].toObject(); - _udpListener = new UDPListener( - udpListenerConfig["priority"].toInt(700), - udpListenerConfig["timeout"].toInt(10000), - udpListenerConfig["address"].toString(""), - udpListenerConfig["port"].toInt(2801), - udpListenerConfig["shared"].toBool(false)); - - Debug(_log, "UDP listener created"); - - if ( udpListenerConfigured && udpListenerConfig["enable"].toBool(true)) - { - _udpListener->start(); - } - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_UDPLISTENER, _udpListener->componentState()); - connect( Hyperion::getInstance(), SIGNAL(componentStateChanged(hyperion::Components,bool)), _udpListener, SLOT(componentStateChanged(hyperion::Components,bool))); - - // zeroconf description - $leddevicename@$hostname - const QJsonObject & generalConfig = _qconfig["general"].toObject(); - const QString mDNSDescr = generalConfig["name"].toString("") + "@" + QHostInfo::localHostName(); - // txt record for zeroconf - QString id = _hyperion->id; - std::string version = HYPERION_VERSION; - std::vector > txtRecord = {{"id",id.toStdString()},{"version",version}}; - - // zeroconf udp listener - if (_udpListener != nullptr) - { - BonjourServiceRegister *bonjourRegister_udp = new BonjourServiceRegister(); - bonjourRegister_udp->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_udpListener->getPort()), "_hyperiond-udp._udp", QString()), _udpListener->getPort(), txtRecord); - Debug(_log, "UDP LIstener mDNS responder started"); - } - - // zeroconf json - BonjourServiceRegister *bonjourRegister_json = new BonjourServiceRegister(); - bonjourRegister_json->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_jsonServer->getPort()), "_hyperiond-json._tcp", QString()), _jsonServer->getPort(), txtRecord); - Debug(_log, "Json mDNS responder started"); - - // zeroconf proto - BonjourServiceRegister *bonjourRegister_proto = new BonjourServiceRegister(); - bonjourRegister_proto->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_jsonServer->getPort()), "_hyperiond-proto._tcp", QString()), _protoServer->getPort(), txtRecord); - Debug(_log, "Proto mDNS responder started"); + // Create Webserver + _webserver = new WebServer(getSetting(settings::WEBSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _webserver, &WebServer::handleSettingsUpdate); } - -void HyperionDaemon::createSystemFrameGrabber() +void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - if (_qconfig.contains("framegrabber")) + if(type == settings::SYSTEMCAPTURE) { - const QJsonObject & grabberConfig = _qconfig["framegrabber"].toObject(); -// if (grabberConfig["enable"].toBool(true)) + const QJsonObject & grabberConfig = config.object(); + + _grabber_width = grabberConfig["width"].toInt(96); + _grabber_height = grabberConfig["height"].toInt(96); + _grabber_frequency = grabberConfig["frequency_Hz"].toInt(10); + + _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); + _grabber_cropRight = grabberConfig["cropRight"].toInt(0); + _grabber_cropTop = grabberConfig["cropTop"].toInt(0); + _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); + + #ifdef ENABLE_OSX + QString type = "osx"; + #else + QString type = grabberConfig["type"].toString("auto"); + #endif + + // auto eval of type + if ( type == "auto" ) { - _grabber_width = grabberConfig["width"].toInt(96); - _grabber_height = grabberConfig["height"].toInt(96); - _grabber_frequency = grabberConfig["frequency_Hz"].toInt(10); - _grabber_priority = grabberConfig["priority"].toInt(900); - - _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); - _grabber_cropRight = grabberConfig["cropRight"].toInt(0); - _grabber_cropTop = grabberConfig["cropTop"].toInt(0); - _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); - - #ifdef ENABLE_OSX - QString type = "osx"; - #else - QString type = grabberConfig["type"].toString("auto"); - #endif - - // auto eval of type - if ( type == "auto" ) + // dispmanx -> on raspi + if (QFile::exists("/dev/vchiq")) { - // dispmanx -> on raspi - // TODO currently a compile option - #ifdef ENABLE_DISPMANX - if (true) - #else - if (false) - #endif - { - type = "dispmanx"; - } - // amlogic -> /dev/amvideo exists - else if ( QFile::exists("/dev/amvideo") && ( QFile::exists("/dev/amvideocap0") || QFile::exists("/dev/ge2d") ) ) - { - type = "amlogic"; - } - // x11 -> if DISPLAY is set - else if (getenv("DISPLAY") != NULL ) - { - type = "x11"; - } - // framebuffer -> if nothing other applies - else - { - type = "framebuffer"; - } - Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type)); + type = "dispmanx"; } - - bool grabberCompState = grabberConfig["enable"].toBool(true); - if (type == "") { Info( _log, "screen capture device disabled"); grabberCompState = false; } - else if (type == "framebuffer") createGrabberFramebuffer(grabberConfig); - else if (type == "dispmanx") createGrabberDispmanx(); - else if (type == "amlogic") createGrabberAmlogic(); - else if (type == "osx") createGrabberOsx(grabberConfig); - else if (type == "x11") createGrabberX11(grabberConfig); - else { Warning( _log, "unknown framegrabber type '%s'", QSTRING_CSTR(type)); grabberCompState = false; } - -// _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, grabberCompState); - _hyperion->setComponentState(hyperion::COMP_GRABBER, grabberCompState ); + // amlogic -> /dev/amvideo exists + else if ( QFile::exists("/dev/amvideo") && ( QFile::exists("/dev/amvideocap0") || QFile::exists("/dev/ge2d") ) ) + { + type = "amlogic"; + } + // x11 -> if DISPLAY is set + else if (getenv("DISPLAY") != NULL ) + { + type = "x11"; + } + // framebuffer -> if nothing other applies + else + { + type = "framebuffer"; + } + Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type)); } + + if (type == "") { Info( _log, "screen capture device disabled"); } + else if (type == "framebuffer" && _fbGrabber == nullptr) createGrabberFramebuffer(grabberConfig); + else if (type == "dispmanx" && _dispmanx == nullptr) createGrabberDispmanx(); + else if (type == "amlogic" && _amlGrabber == nullptr) createGrabberAmlogic(); + else if (type == "osx" && _osxGrabber == nullptr) createGrabberOsx(grabberConfig); + else if (type == "x11" && _x11Grabber == nullptr) createGrabberX11(grabberConfig); + else { Warning( _log, "unknown framegrabber type '%s'", QSTRING_CSTR(type)); } + } + else if(type == settings::V4L2) + { + // stop + if(_v4l2Grabbers.size()>0) + return; + + unsigned v4lEnableCount = 0; + + const QJsonArray & v4lArray = config.array(); + for ( signed idx=0; idxsetSignalThreshold( + grabberConfig["redSignalThreshold"].toDouble(0.0)/100.0, + grabberConfig["greenSignalThreshold"].toDouble(0.0)/100.0, + grabberConfig["blueSignalThreshold"].toDouble(0.0)/100.0); + grabber->setCropping( + grabberConfig["cropLeft"].toInt(0), + grabberConfig["cropRight"].toInt(0), + grabberConfig["cropTop"].toInt(0), + grabberConfig["cropBottom"].toInt(0)); + grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); + grabber->setSignalDetectionOffset( + grabberConfig["sDHOffsetMin"].toDouble(0.25), + grabberConfig["sDVOffsetMin"].toDouble(0.25), + grabberConfig["sDHOffsetMax"].toDouble(0.75), + grabberConfig["sDVOffsetMax"].toDouble(0.75)); + Debug(_log, "V4L2 grabber created"); + + // connect to HyperionDaemon signal + connect(grabber, &V4L2Wrapper::systemImage, this, &HyperionDaemon::v4lImage); + connect(this, &HyperionDaemon::videoMode, grabber, &V4L2Wrapper::setVideoMode); + connect(this, &HyperionDaemon::settingsChanged, grabber, &V4L2Wrapper::handleSettingsUpdate); + + if (grabber->start()) + { + Info(_log, "V4L2 grabber started"); + } + _v4l2Grabbers.push_back(grabber); + #endif + } + + ErrorIf( (v4lEnableCount>0 && _v4l2Grabbers.size()==0), _log, "The v4l2 grabber can not be instantiated, because it has been left out from the build"); } } - void HyperionDaemon::createGrabberDispmanx() { #ifdef ENABLE_DISPMANX - _dispmanx = new DispmanxWrapper(_grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _dispmanx = new DispmanxWrapper(_grabber_width, _grabber_height, _grabber_frequency); _dispmanx->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _dispmanx, SLOT(setVideoMode(VideoMode))); - QObject::connect(_dispmanx, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); - QObject::connect(_dispmanx, SIGNAL(emitImage(int, const Image&, const int)), _hyperion, SLOT(setImage(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _dispmanx, &DispmanxWrapper::setVideoMode); + connect(_dispmanx, &DispmanxWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _dispmanx, &DispmanxWrapper::handleSettingsUpdate); _dispmanx->start(); Info(_log, "DISPMANX frame grabber created and started"); #else - ErrorIf(_qconfig.contains("framegrabber"), _log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build"); + Error( _log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build"); #endif } @@ -449,10 +329,13 @@ void HyperionDaemon::createGrabberDispmanx() void HyperionDaemon::createGrabberAmlogic() { #ifdef ENABLE_AMLOGIC - _amlGrabber = new AmlogicWrapper(_grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _amlGrabber = new AmlogicWrapper(_grabber_width, _grabber_height, _grabber_frequency); _amlGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_amlGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _amlGrabber, &AmlogicWrapper::setVideoMode); + connect(_amlGrabber, &AmlogicWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _amlGrabber, &AmlogicWrapper::handleSettingsUpdate); _amlGrabber->start(); Info(_log, "AMLOGIC grabber created and started"); @@ -465,14 +348,15 @@ void HyperionDaemon::createGrabberX11(const QJsonObject & grabberConfig) { #ifdef ENABLE_X11 _x11Grabber = new X11Wrapper( - grabberConfig["useXGetImage"].toBool(false), _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom, - grabberConfig["horizontalPixelDecimation"].toInt(8), - grabberConfig["verticalPixelDecimation"].toInt(8), - _grabber_frequency, _grabber_priority ); + grabberConfig["pixelDecimation"].toInt(8), + _grabber_frequency ); _x11Grabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_x11Grabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _x11Grabber, &X11Wrapper::setVideoMode); + connect(_x11Grabber, &X11Wrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _x11Grabber, &X11Wrapper::handleSettingsUpdate); _x11Grabber->start(); Info(_log, "X11 grabber created and started"); @@ -488,9 +372,12 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject & grabberConfig) // Construct and start the framebuffer grabber if the configuration is present _fbGrabber = new FramebufferWrapper( grabberConfig["device"].toString("/dev/fb0"), - _grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _grabber_width, _grabber_height, _grabber_frequency); _fbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_fbGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _fbGrabber, &FramebufferWrapper::setVideoMode); + connect(_fbGrabber, &FramebufferWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _fbGrabber, &FramebufferWrapper::handleSettingsUpdate); _fbGrabber->start(); Info(_log, "Framebuffer grabber created and started"); @@ -506,9 +393,12 @@ void HyperionDaemon::createGrabberOsx(const QJsonObject & grabberConfig) // Construct and start the osx grabber if the configuration is present _osxGrabber = new OsxWrapper( grabberConfig["display"].toInt(0), - _grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _grabber_width, _grabber_height, _grabber_frequency); - QObject::connect(_osxGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _osxGrabber, &OsxWrapper::setVideoMode); + connect(_osxGrabber, &OsxWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _osxGrabber, &OsxWrapper::handleSettingsUpdate); _osxGrabber->start(); Info(_log, "OSX grabber created and started"); @@ -516,65 +406,3 @@ void HyperionDaemon::createGrabberOsx(const QJsonObject & grabberConfig) Error(_log, "The osx grabber can not be instantiated, because it has been left out from the build"); #endif } - - -void HyperionDaemon::createGrabberV4L2() -{ - // construct and start the v4l2 grabber if the configuration is present - bool v4lConfigured = _qconfig.contains("grabberV4L2"); - bool v4lStarted = false; - unsigned v4lEnableCount = 0; - - if (_qconfig["grabberV4L2"].isArray()) - { - const QJsonArray & v4lArray = _qconfig["grabberV4L2"].toArray(); - for ( signed idx=0; idxsetCropping( - grabberConfig["cropLeft"].toInt(0), - grabberConfig["cropRight"].toInt(0), - grabberConfig["cropTop"].toInt(0), - grabberConfig["cropBottom"].toInt(0)); - grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); - grabber->setSignalDetectionOffset( - grabberConfig["sDHOffsetMin"].toDouble(0.25), - grabberConfig["sDVOffsetMin"].toDouble(0.25), - grabberConfig["sDHOffsetMax"].toDouble(0.75), - grabberConfig["sDVOffsetMax"].toDouble(0.75)); - Debug(_log, "V4L2 grabber created"); - - QObject::connect(grabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int))); - - if (enableV4l && grabber->start()) - { - v4lStarted = true; - Info(_log, "V4L2 grabber started"); - } - _v4l2Grabbers.push_back(grabber); - #endif - } - } - - ErrorIf( (v4lEnableCount>0 && _v4l2Grabbers.size()==0), _log, "The v4l2 grabber can not be instantiated, because it has been left out from the build"); - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, (_v4l2Grabbers.size()>0 && v4lEnableCount>0 && v4lStarted) ); -} diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 9f4f68dd..8640fb4d 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -40,15 +40,23 @@ #endif #include +#include +#include -#include -#include -#include -#include -#include - +// settings management +#include +class Hyperion; class SysTray; +class JsonServer; +class ProtoServer; +class BoblightServer; +class UDPListener; +class Stats; +class BonjourBrowserWrapper; +class WebServer; +class SettingsManager; +class PythonInit; class HyperionDaemon : public QObject { @@ -60,19 +68,60 @@ public: HyperionDaemon(QString configFile, QString rootPath, QObject *parent=nullptr); ~HyperionDaemon(); - void loadConfig(const QString & configFile); - void run(); + quint16 getWebServerPort(); + /// + /// @brief Get the current videoMode + /// + const VideoMode & getVideoMode() { return _currVideoMode; }; + + /// + /// @brief get the settings + /// + const QJsonDocument getSetting(const settings::type& type); - void startInitialEffect(); void startNetworkServices(); - // grabber creators - void createGrabberV4L2(); - void createSystemFrameGrabber(); + static HyperionDaemon* getInstance() { return daemon; }; + static HyperionDaemon* daemon; public slots: void freeObjects(); +signals: + /// + /// @brief PIPE settings events from Hyperion class to HyperionDaemon components + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + + /// + /// @brief PIPE SystemCapture images from SystemCapture over HyperionDaemon to Hyperion class + /// + void systemImage(const Image& image); + + /// + /// @brief PIPE v4lCapture images from v4lCapture over HyperionDaemon to Hyperion class + /// + void v4lImage(const Image & image); + + /// + /// @brief After eval of setVideoMode this signal emits with a new one on change + /// + void videoMode(const VideoMode& mode); + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief Listen for videoMode changes and emit videoMode in case of a change, update _currVideoMode + /// @param mode The requested video mode + /// + void setVideoMode(const VideoMode& mode); + private: void createGrabberDispmanx(); void createGrabberAmlogic(); @@ -80,29 +129,31 @@ private: void createGrabberOsx(const QJsonObject & grabberConfig); void createGrabberX11(const QJsonObject & grabberConfig); - Logger* _log; - QJsonObject _qconfig; - JsonServer* _jsonServer; - ProtoServer* _protoServer; - BoblightServer* _boblightServer; - UDPListener* _udpListener; + Logger* _log; + BonjourBrowserWrapper* _bonjourBrowserWrapper; + PythonInit* _pyInit; + WebServer* _webserver; + JsonServer* _jsonServer; + ProtoServer* _protoServer; + BoblightServer* _boblightServer; + UDPListener* _udpListener; std::vector _v4l2Grabbers; - DispmanxWrapper* _dispmanx; -#ifdef ENABLE_X11 - X11Wrapper* _x11Grabber; -#endif - AmlogicWrapper* _amlGrabber; - FramebufferWrapper* _fbGrabber; - OsxWrapper* _osxGrabber; - Hyperion* _hyperion; - Stats* _stats; + DispmanxWrapper* _dispmanx; + X11Wrapper* _x11Grabber; + AmlogicWrapper* _amlGrabber; + FramebufferWrapper* _fbGrabber; + OsxWrapper* _osxGrabber; + Hyperion* _hyperion; + Stats* _stats; unsigned _grabber_width; unsigned _grabber_height; unsigned _grabber_frequency; - int _grabber_priority; unsigned _grabber_cropLeft; unsigned _grabber_cropRight; unsigned _grabber_cropTop; unsigned _grabber_cropBottom; + + VideoMode _currVideoMode; + SettingsManager* _settingsManager; }; diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index a1ade791..7d5e58d4 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -319,7 +318,6 @@ int main(int argc, char** argv) try { hyperiond = new HyperionDaemon(configFiles[0], rootPath, qApp); - hyperiond->run(); } catch (std::exception& e) { @@ -327,16 +325,14 @@ int main(int argc, char** argv) } int rc = 1; - WebConfig* webConfig = nullptr; try { - webConfig = new WebConfig(qApp); // run the application if (isGuiApp) { Info(log, "start systray"); QApplication::setQuitOnLastWindowClosed(false); - SysTray tray(hyperiond, webConfig->getPort()); + SysTray tray(hyperiond, hyperiond->getWebServerPort()); tray.hide(); rc = (qobject_cast(app.data()))->exec(); } @@ -352,7 +348,6 @@ int main(int argc, char** argv) } // delete components - delete webConfig; delete hyperiond; Logger::deleteInstance();