Merge pull request #578 from Paulchen-Panther/api_auth

Token Management, Database, ...
This commit is contained in:
Paulchen Panther 2019-08-11 23:25:06 +02:00 committed by GitHub
commit c62ea87ab7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 5879 additions and 2958 deletions

76
.codedocs Normal file
View File

@ -0,0 +1,76 @@
# Hyperion.NG .codedocs Configuration File
#---------------------------------------------------------------------------
# CodeDocs Configuration
#---------------------------------------------------------------------------
# Include the Doxygen configuration from another file.
# The file must be a relative path with respect to the root of the repository.
DOXYFILE =
# Specify external repository to link documentation with.
# This is similar to Doxygen's TAGFILES option, but will automatically link to
# tags of other repositories already using CodeDocs. List each repository to
# link with by giving its location in the form of owner/repository.
# For example:
# TAGLINKS = doxygen/doxygen CodeDocs/osg
# Note: these repositories must already be built on CodeDocs.
TAGLINKS =
#---------------------------------------------------------------------------
# Doxygen Configuration
#---------------------------------------------------------------------------
# Doxygen configuration may also be placed in this file.
# Currently, the following Doxygen configuration options are available. Refer
# to http://doxygen.org/manual/config.html for detailed explanation of the
# options. To request support for more options, contact support@codedocs.xyz.
#
# ABBREVIATE_BRIEF =
# ALIASES =
# ALPHABETICAL_INDEX =
# ALWAYS_DETAILED_SEC =
# CASE_SENSE_NAMES =
# CLASS_DIAGRAMS =
# DISABLE_INDEX =
# DISTRIBUTE_GROUP_DOC =
# EXAMPLE_PATH =
EXCLUDE = .ci/ \
assets/ \
bin/
config/ \
effects/ \
test/ \
# EXCLUDE_PATTERNS =
# EXCLUDE_SYMBOLS =
# EXTENSION_MAPPING =
# EXTRACT_LOCAL_CLASSES =
# FILE_PATTERNS =
# GENERATE_TAGFILE =
# GENERATE_TREEVIEW =
# HIDE_COMPOUND_REFERENCE =
# HIDE_SCOPE_NAMES =
# HIDE_UNDOC_CLASSES =
# HIDE_UNDOC_MEMBERS =
# HTML_TIMESTAMP =
# INLINE_GROUPED_CLASSES =
# INPUT_ENCODING =
# INTERNAL_DOCS =
# OPTIMIZE_OUTPUT_FOR_C =
PROJECT_BRIEF = "The successor to Hyperion aka Hyperion Next Generation"
PROJECT_NAME = "Hyperion.NG"
# PROJECT_NUMBER =
# SHORT_NAMES =
# SHOW_FILES =
# SHOW_INCLUDE_FILES =
# SHOW_NAMESPACES =
# SORT_BRIEF_DOCS =
# SORT_BY_SCOPE_NAME =
# SORT_MEMBER_DOCS =
# STRICT_PROTO_MATCHING =
# TYPEDEF_HIDES_STRUCT =
USE_MDFILE_AS_MAINPAGE = README.md
# VERBATIM_HEADERS =
#

View File

@ -311,7 +311,7 @@ IF ( CMAKE_CROSSCOMPILING )
ENDIF()
SET(QT_MIN_VERSION "5.5.0")
find_package(Qt5 COMPONENTS Core Gui Network SerialPort REQUIRED)
find_package(Qt5 COMPONENTS Core Gui Network SerialPort Sql REQUIRED)
message( STATUS "Found Qt Version: ${Qt5Core_VERSION}" )
IF ( "${Qt5Core_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" )
message( FATAL_ERROR "Your Qt version is to old! Minimum required ${QT_MIN_VERSION}" )
@ -354,9 +354,6 @@ endif ()
# Add resources directory
add_subdirectory(resources)
# Add the doxygen generation directory
add_subdirectory(doc)
# remove generated files on make cleaan too
LIST( APPEND GENERATED_QRC
${CMAKE_BINARY_DIR}/EffectEngine.qrc

View File

@ -1,7 +1,7 @@
# With Docker
If you are using [Docker](https://www.docker.com/), you can compile Hyperion inside a docker container. This keeps your system clean and with a simple script it's easy to use. Supported is also cross compiling for Raspberry Pi (Debian Stretch or higher). To compile Hyperion just execute one of the following commands.
The compiled binaries and packages will be available at the deploy folder next to the script
The compiled binaries and packages will be available at the deploy folder next to the script.<br/>
Note: call the script with `./docker-compile.sh -h` for more options
## Native compiling on Raspberry Pi
@ -40,7 +40,7 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/
```
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libqt5sql5-sqlite
```
**on RPI you need the videocore IV headers**

View File

@ -4,6 +4,7 @@
[![Azure-Pipeline](https://dev.azure.com/Hyperion-Project/Hyperion.NG/_apis/build/status/Hyperion.NG?branchName=master)](https://dev.azure.com/Hyperion-Project/Hyperion.NG/_build/latest?definitionId=7&branchName=master)
[![Travis-CI](https://travis-ci.org/hyperion-project/hyperion.ng.svg?branch=master)](https://travis-ci.org/hyperion-project/hyperion.ng)
[![LGTM](https://img.shields.io/lgtm/alerts/g/hyperion-project/hyperion.ng.svg)](https://lgtm.com/projects/g/hyperion-project/hyperion.ng/alerts/)
[![Documentation](https://codedocs.xyz/hyperion-project/hyperion.ng.svg)](https://codedocs.xyz/hyperion-project/hyperion.ng/)
## About Hyperion

View File

@ -4,6 +4,27 @@
<h3 class="page-header"><i class="fa fa-wrench fa-fw"></i><span data-i18n="conf_general_label_title">General</span></h3>
<div class="row" id="conf_cont"></div>
<div class="row">
<div class="col-lg-6" id="inst_desc">
<div class="panel panel-default">
<div class="panel-heading"><i class="fa fa-plus fa-fw"></i><span data-i18n="conf_general_inst_title"></span></div>
<div class="panel-body">
<div id="inst_desc_cont"></div>
<div id="itable"></div>
<div style="margin: 30px 0; border-top:1px solid #d0d0d0"></div>
<div class="row">
<div class="col-lg-6">
<p data-i18n="conf_general_inst_name_title" style="font-weight:bold"></p>
</div>
<div class="col-lg-6">
<input class="form-control" id="inst_name" type="text"></input>
</div>
</div>
<div>
<button class="btn btn-primary" id="btn_create_inst" data-i18n="conf_general_createInst_btn" disabled>Create New Token</button>
</div>
</div>
</div>
</div>
<div class="col-lg-6" id="conf_imp">
<div class="panel panel-default">
<div class="panel-heading"><i class="fa fa-wrench fa-fw"></i><span data-i18n="conf_general_impexp_title"></span></div>

View File

@ -2,9 +2,36 @@
<div class="row">
<div class="col-lg-12">
<h3 class="page-header"><i class="fa fa-sitemap fa-fw"></i><span data-i18n="main_menu_network_conf_token">Network Services</span></h3>
<div id="conf_cont"></div>
<div id="conf_cont">
<div class="row" id="conf_cont_tok">
<div class="col-lg-6" id="tok_desc">
<div class="panel panel-default">
<div class="panel-heading"><i class="fa fa-key fa-fw"></i><span data-i18n="conf_network_tok_title"></span></div>
<div class="panel-body">
<div id="tok_desc_cont"></div>
<div id="tktable"></div>
<div style="margin: 30px 0; border-top:1px solid #d0d0d0"></div>
<div class="row">
<div class="col-lg-6">
<p data-i18n="conf_network_tok_comment_title" style="font-weight:bold"></p>
</div>
<div class="col-lg-6">
<input class="form-control" id="tok_comment" type="text"></input>
</div>
<div class="col-lg-6">
<span id="tok_chars_needed"><br /></span>
</div>
</div>
</div>
<div class="panel-footer" style="text-align: right;">
<button class="btn btn-primary" id="btn_create_tok" data-i18n="conf_network_createToken_btn" disabled>Create New Token</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/content_network.js"></script>
<script src="/js/content_network.js"></script>

View File

@ -27,7 +27,7 @@ var connectionLost = false;
var connectionTimer;
var count = 1;
var reconnectInterval = 4000;
var connURL = location.protocol+"//"+location.hostname+":"+window.jsonPort+location.pathname+location.hash;
var connURL = window.location.protocol+"//"+window.location.hostname+":"+window.jsonPort+window.location.pathname+window.location.hash;
function tryReconnect()
{
@ -41,7 +41,14 @@ function tryReconnect()
$.ajax({ url: connURL }).done(function(data) {
window.clearInterval(connectionTimer);
window.location.href = connURL;
if(reconnectInterval <= 2000){
let url = connURL;
if (window.connURL.includes("#"))
url = window.connURL.slice(0,window.connURL.indexOf("#"));
window.location.replace(url);
} else {
window.location.reload();
}
})
.fail( function( jqXHR, textStatus ) {
count++;
@ -55,7 +62,7 @@ function connectionLostAction()
{
connectionLost = true;
// if we changed the webui port we connect faster
if(window.fastReconnect){
if(window.fastReconnect) {
window.fastReconnect = false;
reconnectInterval = 2000;
}

View File

@ -68,13 +68,6 @@
</div>
</div>
</div>
<div class="col-md-6 col-lg-6 col-xxl-5">
<div class="panel panel-default" >
<div class="panel-heading"><i class="fa fa-wifi fa-fw"></i><span data-i18n="remote_adjustment_label"></span></div>
<div class="panel-body" id="adjust_content">
</div>
</div>
</div>
<div class="col-md-6 col-lg-6 col-xxl-5">
<div class="panel panel-default" >
<div class="panel-heading"><i class="fa fa-wifi fa-fw"></i><span data-i18n="remote_videoMode_label"></span></div>
@ -83,6 +76,13 @@
</div>
</div>
</div>
<div class="col-md-6 col-lg-6 col-xxl-5">
<div class="panel panel-default" >
<div class="panel-heading"><i class="fa fa-wifi fa-fw"></i><span data-i18n="remote_adjustment_label"></span></div>
<div class="panel-body" id="adjust_content">
</div>
</div>
</div>
</div>
</div>
<script src="/js/content_remote.js" ></script>

View File

@ -10,11 +10,11 @@ body{font-family:Roboto,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15
padding-bottom:50px;
}
body{
overflow-y: scroll;
overflow-y: scroll;
}
.btn{margin: 2px 0;}
/*
#page-wrapper a[target=_blank]::after {
#page-wrapper a[target=_blank]::after {
content:"\f08e";
position:relative;
font:normal normal normal 10px/1 FontAwesome;
@ -109,7 +109,7 @@ table label{margin:0}
.colorpicker-2x .colorpicker-saturation {width: 200px;height: 200px;}
.colorpicker-2x .colorpicker-hue,.colorpicker-2x .colorpicker-alpha {width: 30px;height: 200px;}
.colorpicker-2x .colorpicker-color,.colorpicker-2x .colorpicker-color div {height: 30px;}
/*Hint*/
.info-hint{
background-color:rgb(236,236,236);
@ -123,7 +123,7 @@ table label{margin:0}
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.25);
font-size:97%;
}
/*Support page*/
.unlink,.unlink:hover{color:#333;text-decoration:none;}
.support-container ul{padding-left:0px;list-style-type: none;}
@ -196,6 +196,10 @@ table label{margin:0}
background-color:#d1322e;
}
.modal-icon-edit{
background-color:#3579b6;
}
.overlay {
background-image: url('/img/hyperion/hyperionwhitelogo.png');
background-repeat: no-repeat;

View File

@ -16,7 +16,6 @@
"general_comp_BLACKBORDER": "Detekce černýh pruh",
"general_comp_KODICHECKER": "Kodi Snímač",
"general_comp_FORWARDER": "Zasílat",
"general_comp_UDPLISTENER": "UDP server",
"general_comp_BOBLIGHTSERVER": "Boblight Server",
"general_comp_GRABBER": "Platforma zachycení",
"general_comp_V4L": "USB zachycení",
@ -142,7 +141,6 @@
"conf_network_json_intro": "The JSON-RPC-Port této instance Hyperion, který se používá pro dálkové ovládání.",
"conf_network_proto_intro": "PROTO-Port této instance Hyperion, používaný pro obrazové proudy (Hyperion ScreenCap, Kodi Addon, ...)",
"conf_network_bobl_intro": "Přijímač pro Boblight",
"conf_network_udpl_intro": "Přijímač pro UDP",
"conf_network_forw_intro": "Předat všechny vstupy na druhou instanci Hyperion, která by mohla být řízena jiným LED ovladačem",
"conf_kodi_label_title": "Kodi Snímač",
"conf_kodi_intro": "Kodi Snímač umožňuje zapnout a vypnout obrazovku v závislosti na stavu Kodi. Nastavení není omezeno na stejný stroj, můžete pozorovat i Kodi na libovolně jiném zařízení v síti.",
@ -518,13 +516,6 @@
"edt_conf_js_heading_title": "JSON Server",
"edt_conf_ps_heading_title": "PROTO Server",
"edt_conf_bobls_heading_title": "Boblight Server",
"edt_conf_udpl_heading_title": "UDP Listener",
"edt_conf_udpl_address_title": "Adresa",
"edt_conf_udpl_address_expl": "Adresa, kde jsou přijaty balíčky UDP.",
"edt_conf_udpl_timeout_title": "Čas vypršel",
"edt_conf_udpl_timeout_expl": "Pokud pro danou dobu nejsou přijaty žádné pakety, bude součást (soft) zakázána.",
"edt_conf_udpl_shared_title": "Sdíled",
"edt_conf_udpl_shared_expl": "Sdíleny všemi instancemi Hyperion.",
"edt_conf_webc_heading_title": "Webová konfigurace",
"edt_conf_webc_docroot_title": "Document Root",
"edt_conf_webc_docroot_expl": "Místní kořenová cesta webového rozhraní (pouze pro vývojáře webui)",

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@
"general_comp_SMOOTHING" : "Smoothing",
"general_comp_BLACKBORDER" : "Blackbar Detection",
"general_comp_FORWARDER" : "Forwarder",
"general_comp_UDPLISTENER" : "UDP Listener",
"general_comp_BOBLIGHTSERVER" : "Boblight Server",
"general_comp_FLATBUFSERVER" : "Flatbuffers Server",
"general_comp_PROTOSERVER" : "Protocol Buffers Server",
@ -31,10 +30,13 @@
"general_button_savesettings" : "Save settings",
"general_btn_yes" : "Yes",
"general_btn_ok" : "OK",
"general_btn_start" : "Start",
"general_btn_stop" : "Stop",
"general_btn_cancel" : "Cancel",
"general_btn_delete" : "Delete",
"general_btn_rename" : "Rename",
"general_btn_continue" : "Continue",
"general_btn_save" : "Save",
"general_btn_saverestart" : "Save and restart",
"general_btn_saveandreload" : "Save and reload",
"general_btn_restarthyperion" : "Restart Hyperion",
"general_btn_off" : "Off",
@ -66,10 +68,12 @@
"dashboard_newsbox_readmore" : "Read more",
"dashboard_alert_message_confedit_t" : "Configuration modified",
"dashboard_alert_message_confedit" : "Your Hyperion configuration has been modified. To apply it, restart Hyperion.",
"dashboard_alert_message_disabled_t" : "Hyperion disabled",
"dashboard_alert_message_disabled" : "Hyperion is currently disabled! To use it again, enable it at the dashboard.",
"dashboard_alert_message_disabled_t" : "LED hardware instance disabled",
"dashboard_alert_message_disabled" : "This instance is currently disabled! To use it again, enable it at the dashboard.",
"dashboard_alert_message_confsave_success_t" : "Configuration saved",
"dashboard_alert_message_confsave_success" : "Your Hyperion configuration has been saved successfully. Your changes are now active.",
"dashboard_message_global_setting_t": "Instance independent setting",
"dashboard_message_global_setting": "The settings on this page are not depending on a specific instance. Changes will be stored globally for all instances.",
"main_menu_dashboard_token" : "Dashboard",
"main_menu_configuration_token" : "Configuration",
"main_menu_general_conf_token" : "General",
@ -99,6 +103,15 @@
"conf_general_impexp_l2" : "Export a configuration by clicking on \"Export\". Your browser starts a download.",
"conf_general_impexp_impbtn" : "Import",
"conf_general_impexp_expbtn" : "Export",
"conf_general_inst_title" : "LED Hardware Instance Management",
"conf_general_inst_desc" : "Use different LED hardware at the same time. Each instance runs independent of each other which allows different LED layouts and calibration settings. Running instances are available at the top icon bar",
"conf_general_inst_namehead" : "Instance name",
"conf_general_inst_actionhead" : "Action",
"conf_general_inst_name_title" : "New Instance name",
"conf_general_createInst_btn" : "Create Instance",
"conf_general_inst_renreq_t" : "Enter a new name for your instance in the field below.",
"conf_general_inst_delreq_h" : "Delete LED Hardware instance",
"conf_general_inst_delreq_t" : "Are you sure that you want to delete instance \"$1\"? All settings will be deleted too.",
"conf_helptable_option" : "Option",
"conf_helptable_expl" : "Explanation",
"conf_effect_path_intro" : "Load effects from the defined paths. Additional you can disable single effects by name to hide them from all effect lists.",
@ -167,12 +180,22 @@
"conf_colors_color_intro" : "Create one or more calibration profiles, adjust each color, brightness, linearization and more.",
"conf_colors_smoothing_intro" : "Smoothing flattens color/brightness changes to reduce annoying distraction.",
"conf_colors_blackborder_intro" : "Skip black bars wherever they are. Each mode use another detection algorithm which is tuned for special situations. Higher the threshold if it doesn't work for you.",
"conf_network_json_intro" : "The JSON-RPC-Port of this Hyperion instance, used for remote control.",
"conf_network_net_intro" : "Network related settings which are applied to all network services.",
"conf_network_json_intro" : "The JSON-RPC-Port of all Hyperion instances, used for remote control.",
"conf_network_bobl_intro" : "Receiver for Boblight",
"conf_network_udpl_intro" : "Receiver for UDP",
"conf_network_fbs_intro" : "Google Flatbuffers Receiver. Used for fast image transmission.",
"conf_network_proto_intro" : "The PROTO-Port of this Hyperion instance, used for picture streams (HyperionScreenCap, Kodi Addon, ...)",
"conf_network_proto_intro" : "The PROTO-Port of all Hyperion instances, used for picture streams (HyperionScreenCap, Kodi Addon, Android Hyperion Grabber, ...)",
"conf_network_forw_intro" : "Forward all input to a second Hyperion instance which could be driven with another led controller",
"conf_network_tok_title" : "Token Management",
"conf_network_tok_desc" : "Tokens grant other applications access to the Hyperion API, an application can request a token where you need to accept it or you create them on your own below. These tokens are just required when \"API Authorization\" is enabled in network settings.",
"conf_network_tok_cidhead" : "Description",
"conf_network_tok_lastuse" : "Last use",
"conf_network_tok_comment_title" : "Token description",
"conf_network_tok_chars_needed" : "more characters needed",
"conf_network_createToken_btn" : "Create Token",
"conf_network_tok_diaTitle" : "New Token created!",
"conf_network_tok_diaMsg" : "Here is your new token which can be used to grant an application access to the Hyperion API. For security reasons you can't view it again so use/note it now.",
"conf_network_tok_intro" : "Here you can create and delete tokens for API authentification. Created tokens will only be deisplayed once.",
"conf_logging_label_intro" : "Area to check log messages, depending on loglevel setting you see more or less information.",
"conf_logging_btn_pbupload" : "Upload report for support request",
"conf_logging_btn_autoscroll" : "Auto scrolling",
@ -575,6 +598,16 @@
"edt_conf_fw_proto_title" : "List of proto clients",
"edt_conf_fw_proto_expl" : "One proto target per line. Contains IP:PORT (Example: 127.0.0.1:19401)",
"edt_conf_fw_proto_itemtitle" : "Proto target",
"edt_conf_net_heading_title" : "Network",
"edt_conf_net_internetAccessAPI_title":"Internet API Access",
"edt_conf_net_internetAccessAPI_expl":"Allow access to the Hyperion API/Webinterface from the internet, disable for higher security.",
"edt_conf_net_ipWhitelist_title":"Whitelisted IP's",
"edt_conf_net_ipWhitelist_expl":"You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.",
"edt_conf_net_ip_itemtitle":"IP",
"edt_conf_net_apiAuth_title":"API Authentication",
"edt_conf_net_apiAuth_expl":"Enforce all applications that use the Hyperion API to authenticate themself against Hyperion (Exception see \"Local API Authentication\"). Higher security, as you control the access and revoke it at any time.",
"edt_conf_net_localApiAuth_title" : "Local API Authentication",
"edt_conf_net_localApiAuth_expl" : "When enabled, connections from your home network needs to authenticate themself against Hyperion too.",
"edt_conf_js_heading_title" : "JSON Server",
"edt_conf_fbs_heading_title" : "Flatbuffers Server",
"edt_conf_fbs_timeout_title" : "Timeout",
@ -583,13 +616,6 @@
"edt_conf_pbs_timeout_title" : "Timeout",
"edt_conf_pbs_timeout_expl" : "If no data are received for the given period, the component will be (soft) disabled.",
"edt_conf_bobls_heading_title" : "Boblight Server",
"edt_conf_udpl_heading_title" : "UDP Listener",
"edt_conf_udpl_address_title" : "Address",
"edt_conf_udpl_address_expl" : "The address where UDP packages are accepted.",
"edt_conf_udpl_timeout_title" : "Timeout",
"edt_conf_udpl_timeout_expl" : "If no packages are received for the given period, the component will be (soft) disabled.",
"edt_conf_udpl_shared_title" : "Shared",
"edt_conf_udpl_shared_expl" : "Shared across all Hyperion instances.",
"edt_conf_webc_heading_title" : "Web Configuration",
"edt_conf_webc_docroot_title" : "Document Root",
"edt_conf_webc_docroot_expl" : "Local webinterface root path (just for webui developer)",

View File

@ -16,7 +16,6 @@
"general_comp_BLACKBORDER": "Detección de bordes negros",
"general_comp_KODICHECKER": "Observador Kodi",
"general_comp_FORWARDER": "JSON/PROTO Progresivo",
"general_comp_UDPLISTENER": "Oyente UDP",
"general_comp_BOBLIGHTSERVER": "Servidor Boblight",
"general_comp_GRABBER": "Captura de plataforma",
"general_comp_V4L": "Captura USB",
@ -142,7 +141,6 @@
"conf_network_json_intro": "El puerto JSON-RPC de esta instancia de Hyperion, utilizado para el control remoto.",
"conf_network_proto_intro": "El PROTO-puerto de esta instancia de Hyperion, utilizado para flujos de imágenes (HyperionScreenCap, Kodi Adddon, ...)",
"conf_network_bobl_intro": "Receptor para Boblight",
"conf_network_udpl_intro": "Receptor para UDP",
"conf_network_forw_intro": "Reenviar toda la entrada a una segunda instancia de Hyperion que podría ser gestionada con otro controlador led",
"conf_kodi_label_title": "Observador Kodi",
"conf_kodi_intro": "El Observador de Kodi te permite habilitar y deshabilitar la captura de pantalla dependiendo del estado de Kodi. Esto no se limita a la misma máquina, se puede observar también un Kodi en cualquier otro dispositivo de tu red.",
@ -518,13 +516,6 @@
"edt_conf_js_heading_title": "Servidor JSON",
"edt_conf_ps_heading_title": "Servidor PROTO",
"edt_conf_bobls_heading_title": "Servidor Boblight",
"edt_conf_udpl_heading_title": "Oyente UDP",
"edt_conf_udpl_address_title": "Dirección",
"edt_conf_udpl_address_expl": "La dirección donde se aceptan paquetes UDP.",
"edt_conf_udpl_timeout_title": "Tiempo muerto",
"edt_conf_udpl_timeout_expl": "Si no se reciben paquetes durante el período especificado, el componente se desactivará (suavemente).",
"edt_conf_udpl_shared_title": "Compartido",
"edt_conf_udpl_shared_expl": "Compartido entre todas las instancias de Hyperion.",
"edt_conf_webc_heading_title": "Configuración web",
"edt_conf_webc_docroot_title": "Documento raíz",
"edt_conf_webc_docroot_expl": "Ruta raíz de la interfaz web local (sólo para desarrolladores webui)",

View File

@ -16,7 +16,6 @@
"general_comp_BLACKBORDER": "Rilevamento barra nera",
"general_comp_KODICHECKER": "Controllo Kodi",
"general_comp_FORWARDER": "Forwarder",
"general_comp_UDPLISTENER": "Listener UDP",
"general_comp_BOBLIGHTSERVER": "Server Boblight",
"general_comp_GRABBER": "Cattura di Sistema",
"general_comp_V4L": "Cattura USB",
@ -142,7 +141,6 @@
"conf_network_json_intro": "La porta JSON-RPC di questa istanza di Hyperion, usata per il controllo remoto.",
"conf_network_proto_intro": "La porta PROTO di questa istanza di Hyperion, usata per stream di immagini (HyperionScreenCap, Kodi Adddon, ...)",
"conf_network_bobl_intro": "Ricevitore Boblight",
"conf_network_udpl_intro": "Ricevitore UDP",
"conf_network_forw_intro": "Inoltra tutti gli input a una seconda istanza di Hyperion che può essere usata con un altro controller led",
"conf_kodi_label_title": "Controllo Kodi",
"conf_kodi_intro": "Il controllore di Kodi ti permetti di abilitare e disabilitare lo screencapture a seconda dello stato di Kodi. Non è limitato alla stessa macchina, puoi osservare Kodi su qualsiasi altro dispositivo nella tua rete.",
@ -518,13 +516,6 @@
"edt_conf_js_heading_title": "Server JSON",
"edt_conf_ps_heading_title": "Server PROTO",
"edt_conf_bobls_heading_title": "Srtver Boblight",
"edt_conf_udpl_heading_title": "Listener UDP",
"edt_conf_udpl_address_title": "Indirizzo",
"edt_conf_udpl_address_expl": "L'indirizzo dove vengono accettati i pacchetti UDP",
"edt_conf_udpl_timeout_title": "Timeout",
"edt_conf_udpl_timeout_expl": "Se non vengono ricevuti pacchetti nel dato periodo la componente sarà disabilitata (temporaneo).",
"edt_conf_udpl_shared_title": "Condivisi",
"edt_conf_udpl_shared_expl": "Condivisi tra tutte le istanze di Hyperion.",
"edt_conf_webc_heading_title": "Configurazione Web",
"edt_conf_webc_docroot_title": "Documento Root",
"edt_conf_webc_docroot_expl": "Percorso root della webinterface locale (solo per sviluppatori webui)",

View File

@ -93,11 +93,20 @@
<!-- /.navbar-header -->
<ul class="nav navbar-top-links navbar-right">
<li class="dropdown" id="btn_hypinstanceswitch" style="display:none">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-exchange fa-fw"></i> <i class="fa fa-caret-down"></i>
</a>
<ul id="hyp_inst_listing" class="dropdown-menu dropdown-alerts">
</ul>
</li>
<!--
<li class="dropdown" id="btn_instanceswitch" style="display:none">
<a>
<i class="fa fa-exchange fa-fw"></i>
</a>
</li>
-->
<li class="dropdown" id="btn_open_ledsim">
<a>
<i class="fa fa-television fa-fw"></i>
@ -192,19 +201,18 @@
<!-- Page Content -->
<div id="page-wrapper" style="padding-top:10px; overflow: hidden;">
<div id="hyperion_reload_notify" style="display:none;padding:0 10px;margin:0">
<div class="bs-callout bs-callout-warning">
<h4 data-i18n="dashboard_alert_message_confedit_t"></h4>
<span data-i18n="dashboard_alert_message_confedit"></span>
<p><button id="btn_hyperion_reload" class="btn btn-warning btn-sm" style="margin-top:10px" data-i18n="general_btn_restarthyperion"></button></p>
</div>
</div>
<div id="hyperion_config_write_success_notify" style="display:none;padding:0 10px;margin:0">
<div class="bs-callout bs-callout-success">
<h4 data-i18n="dashboard_alert_message_confsave_success_t"></h4>
<span data-i18n="dashboard_alert_message_confsave_success"></span>
</div>
</div>
<div id="hyperion_global_setting_notify" style="display:none;padding:0 10px;margin:0">
<div class="bs-callout bs-callout-warning">
<h4 data-i18n="dashboard_message_global_setting_t"></h4>
<span data-i18n="dashboard_message_global_setting"></span>
</div>
</div>
<div id="hyperion_disabled_notify" style="display:none;padding:0 10px;margin:0">
<div class="bs-callout bs-callout-danger">
<h4 data-i18n="dashboard_alert_message_disabled_t"></h4>
@ -233,6 +241,18 @@
</div>
</div>
<!-- renameDialog -->
<div id="modal_dialog_rename" class="modal fade" role="dialog" style="z-index:9999">
<div class="modal-dialog">
<center>
<div class="modal-content">
<div id="id_body_rename" class="modal-body"></div>
<div id="id_footer_rename" class="modal-footer" style="text-align:center"></div>
</div>
</center>
</div>
</div>
<!-- wizardDialog -->
<div id="wizard_modal" class="modal fade" role="dialog">
<div class="modal-dialog">

View File

@ -76,7 +76,7 @@ $(document).ready( function() {
// add more info
$('#dash_leddevice').html(window.serverInfo.ledDevices.active);
$('#dash_currv').html(window.currentChannel+' '+window.currentVersion);
$('#dash_instance').html(window.serverConfig.general.name);
$('#dash_instance').html(window.currentHyperionInstanceName);
$('#dash_ports').html(window.serverConfig.flatbufServer.port+' | '+window.serverConfig.protoServer.port);
$('#dash_watchedversionbranch').html(window.serverConfig.general.watchedVersionBranch);

View File

@ -25,6 +25,79 @@ $(document).ready( function() {
requestWriteConfig(conf_editor.getValue());
});
// Instance handling
function handleInstanceRename(e)
{
var inst = e.currentTarget.id.split("_")[1];
showInfoDialog('renInst', $.i18n('conf_general_inst_renreq_t'), getInstanceNameByIndex(inst));
$("#id_btn_ok").off().on('click', function(){
requestInstanceRename(inst, $('#renInst_name').val())
});
$('#renInst_name').off().on('input',function(e) {
(e.currentTarget.value.length >= 5 && e.currentTarget.value != getInstanceNameByIndex(inst)) ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true);
});
}
function handleInstanceStartStop(e)
{
var inst = e.currentTarget.id.split("_")[1];
var start = (e.currentTarget.className == "btn btn-danger")
requestInstanceStartStop(inst, start)
}
function handleInstanceDelete(e)
{
var inst = e.currentTarget.id.split("_")[1];
showInfoDialog('delInst',$.i18n('conf_general_inst_delreq_h'),$.i18n('conf_general_inst_delreq_t',getInstanceNameByIndex(inst)));
$("#id_btn_yes").off().on('click', function(){
requestInstanceDelete(inst)
});
}
function buildInstanceList()
{
var inst = serverInfo.instance
$('.itbody').html("");
for(var key in inst)
{
var startBtnColor = inst[key].running ? "success" : "danger";
var startBtnIcon = inst[key].running ? "stop" : "play";
var startBtnText = inst[key].running ? $.i18n('general_btn_stop') : $.i18n('general_btn_start');
var renameBtn = '<button id="instren_'+inst[key].instance+'" type="button" class="btn btn-primary"><i class="fa fa-pencil"> '+$.i18n('general_btn_rename')+'</i></button>';
var startBtn = ""
var delBtn = "";
if(inst[key].instance > 0)
{
delBtn = '<button id="instdel_'+inst[key].instance+'" type="button" class="btn btn-warning"><i class="fa fa-remove"> '+$.i18n('general_btn_delete')+'</i></button>';
startBtn = '<button id="inst_'+inst[key].instance+'" type="button" class="btn btn-'+startBtnColor+'"><i class="fa fa-'+startBtnIcon+'"> '+startBtnText+'</i></button>';
}
$('.itbody').append(createTableRow([inst[key].friendly_name, renameBtn, startBtn, delBtn], false, true));
$('#instren_'+inst[key].instance).off().on('click', handleInstanceRename);
$('#inst_'+inst[key].instance).off().on('click', handleInstanceStartStop);
$('#instdel_'+inst[key].instance).off().on('click', handleInstanceDelete);
}
}
createTable('ithead', 'itbody', 'itable');
$('.ithead').html(createTableRow([$.i18n('conf_general_inst_namehead'), "", $.i18n('conf_general_inst_actionhead'), ""], true, true));
buildInstanceList();
$('#inst_name').off().on('input',function(e) {
(e.currentTarget.value.length >= 5) ? $('#btn_create_inst').attr('disabled', false) : $('#btn_create_inst').attr('disabled', true);
});
$('#btn_create_inst').off().on('click',function(e) {
requestInstanceCreate($('#inst_name').val());
$('#inst_name').val("");
$('#btn_create_inst').attr('disabled', true)
});
$(hyperion).off("instance-updated").on("instance-updated", function(event) {
buildInstanceList()
});
//import
function dis_imp_btn(state)
{
@ -104,6 +177,8 @@ $(document).ready( function() {
//create introduction
if(window.showOptHelp)
createHint("intro", $.i18n('conf_general_intro'), "editor_container");
createHint("intro", $.i18n('conf_general_tok_desc'), "tok_desc_cont");
createHint("intro", $.i18n('conf_general_inst_desc'), "inst_desc_cont");
removeOverlay();
});

View File

@ -1,3 +1,5 @@
var instNameInit = false
$(document).ready( function() {
loadContentTo("#container_connection_lost","connection_lost");
@ -21,10 +23,19 @@ $(document).ready( function() {
}
});
if (window.serverInfo.hyperion.enabled)
$("#hyperion_disabled_notify").fadeOut("fast");
// determine button visibility
var running = window.serverInfo.instance.filter(entry => entry.running);
if(running.length > 1)
$('#btn_hypinstanceswitch').toggle(true)
else
$("#hyperion_disabled_notify").fadeIn("fast");
$('#btn_hypinstanceswitch').toggle(false)
// update listing at button
updateHyperionInstanceListing()
if(!instNameInit)
{
window.currentHyperionInstanceName = getInstanceNameByIndex(0);
instNameInit = true;
}
updateSessions();
}); // end cmd-serverinfo
@ -34,6 +45,11 @@ $(document).ready( function() {
updateSessions();
});
$(window.hyperion).one("cmd-authorize-getTokenList", function(event) {
tokenList = event.response.info;
requestServerInfo();
});
$(window.hyperion).on("cmd-sysinfo", function(event) {
requestServerInfo();
window.sysInfo = event.response.info;
@ -45,6 +61,7 @@ $(document).ready( function() {
$(window.hyperion).one("cmd-config-getschema", function(event) {
window.serverSchema = event.response.info;
requestServerConfig();
requestTokenInfo();
window.schema = window.serverSchema.properties;
});
@ -62,12 +79,16 @@ $(document).ready( function() {
}
});
$(window.hyperion).one("cmd-authorize-login", function(event) {
requestServerConfigSchema();
});
$(window.hyperion).on("error",function(event){
showInfoDialog("error","Error", event.reason);
});
$(window.hyperion).on("open",function(event){
requestServerConfigSchema();
requestAuthorization();
});
$(window.hyperion).one("ready", function(event) {
@ -103,6 +124,50 @@ $(document).ready( function() {
$(window.hyperion).trigger("components-updated");
});
$(window.hyperion).on("cmd-instance-update", function(event) {
window.serverInfo.instance = event.response.data
var avail = event.response.data;
// notify the update
$(window.hyperion).trigger("instance-updated");
// if our current instance is no longer available we are at instance 0 again.
var isInData = false;
for(var key in avail)
{
if(avail[key].instance == currentHyperionInstance && avail[key].running)
{
isInData = true;
}
}
if(!isInData)
{
currentHyperionInstance = 0;
currentHyperionInstanceName = getInstanceNameByIndex(0);
requestServerConfig();
setTimeout(requestServerInfo,100)
setTimeout(requestTokenInfo,200)
setTimeout(loadContent,300, undefined, true)
}
// determine button visibility
var running = serverInfo.instance.filter(entry => entry.running);
if(running.length > 1)
$('#btn_hypinstanceswitch').toggle(true)
else
$('#btn_hypinstanceswitch').toggle(false)
// update listing for button
updateHyperionInstanceListing()
});
$(window.hyperion).on("cmd-instance-switchTo", function(event){
requestServerConfig();
setTimeout(requestServerInfo,200)
setTimeout(requestTokenInfo,400)
setTimeout(loadContent,400, undefined, true)
});
$(window.hyperion).on("cmd-effects-update", function(event){
window.serverInfo.effects = event.response.data.effects
});

View File

@ -1,15 +1,20 @@
$(document).ready( function() {
performTranslation();
var conf_editor_net = null;
var conf_editor_json = null;
var conf_editor_proto = null;
var conf_editor_fbs = null;
var conf_editor_bobl = null;
var conf_editor_udpl = null;
var conf_editor_forw = null;
if(window.showOptHelp)
{
//network
$('#conf_cont').append(createRow('conf_cont_net'))
$('#conf_cont_net').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_net_heading_title"), 'editor_container_net', 'btn_submit_net'));
$('#conf_cont_net').append(createHelpTable(window.schema.network.properties, $.i18n("edt_conf_net_heading_title")));
//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'));
@ -30,11 +35,6 @@ $(document).ready( function() {
$('#conf_cont_bobl').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_bobls_heading_title"), 'editor_container_boblightserver', 'btn_submit_boblightserver'));
$('#conf_cont_bobl').append(createHelpTable(window.schema.boblightServer.properties, $.i18n("edt_conf_bobls_heading_title")));
//udplistener
$('#conf_cont').append(createRow('conf_cont_udpl'))
$('#conf_cont_udpl').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_udpl_heading_title"), 'editor_container_udplistener', 'btn_submit_udplistener'));
$('#conf_cont_udpl').append(createHelpTable(window.schema.udpListener.properties, $.i18n("edt_conf_udpl_heading_title")));
//forwarder
if(storedAccess != 'default')
{
@ -46,15 +46,29 @@ $(document).ready( function() {
else
{
$('#conf_cont').addClass('row');
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_net_heading_title"), 'editor_container_net', 'btn_submit_net'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fbs_heading_title"), 'editor_container_fbserver', 'btn_submit_fbserver'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_pbs_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_bobls_heading_title"), 'editor_container_boblightserver', 'btn_submit_boblightserver'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_udpl_heading_title"), 'editor_container_udplistener', 'btn_submit_udplistener'));
if(storedAccess != 'default')
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fw_heading_title"), 'editor_container_forwarder', 'btn_submit_forwarder'));
$('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fw_heading_title"), 'editor_container_forwarder', 'btn_submit_forwarder'));
$("#conf_cont_tok").removeClass('row');
}
// net
conf_editor_net = createJsonEditor('editor_container_net', {
network : window.schema.network
}, true, true);
conf_editor_net.on('change',function() {
conf_editor_net.validate().length ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false);
});
$('#btn_submit_net').off().on('click',function() {
requestWriteConfig(conf_editor_net.getValue());
});
//json
conf_editor_json = createJsonEditor('editor_container_jsonserver', {
jsonServer : window.schema.jsonServer
@ -107,19 +121,6 @@ $(document).ready( function() {
requestWriteConfig(conf_editor_bobl.getValue());
});
//udplistener
conf_editor_udpl = createJsonEditor('editor_container_udplistener', {
udpListener : window.schema.udpListener
}, true, true);
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
@ -139,13 +140,79 @@ $(document).ready( function() {
//create introduction
if(window.showOptHelp)
{
createHint("intro", $.i18n('conf_network_net_intro'), "editor_container_net");
createHint("intro", $.i18n('conf_network_json_intro'), "editor_container_jsonserver");
createHint("intro", $.i18n('conf_network_fbs_intro'), "editor_container_fbserver");
createHint("intro", $.i18n('conf_network_proto_intro'), "editor_container_protoserver");
createHint("intro", $.i18n('conf_network_bobl_intro'), "editor_container_boblightserver");
createHint("intro", $.i18n('conf_network_udpl_intro'), "editor_container_udplistener");
createHint("intro", $.i18n('conf_network_forw_intro'), "editor_container_forwarder");
createHint("intro", $.i18n('conf_network_tok_intro'), "tok_desc_cont");
}
// Token handling
function buildTokenList()
{
$('.tktbody').html("");
for(var key in tokenList)
{
var lastUse = (tokenList[key].last_use) ? tokenList[key].last_use : "-";
var btn = '<button id="tok'+tokenList[key].id+'" type="button" class="btn btn-danger">'+$.i18n('general_btn_delete')+'</button>';
$('.tktbody').append(createTableRow([tokenList[key].comment, lastUse, btn], false, true));
$('#tok'+tokenList[key].id).off().on('click', handleDeleteToken);
}
}
createTable('tkthead', 'tktbody', 'tktable');
$('.tkthead').html(createTableRow([$.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
buildTokenList();
function handleDeleteToken(e)
{
var key = e.currentTarget.id.replace("tok","");
requestTokenDelete(key);
$('#tok'+key).parent().parent().remove();
// rm deleted token id
tokenList = tokenList.filter(function( obj ) {
return obj.id !== key;
});
}
$('#btn_create_tok').off().on('click',function() {
requestToken($('#tok_comment').val())
$('#tok_comment').val("")
$('#btn_create_tok').attr('disabled', true)
});
$('#tok_comment').off().on('input',function(e) {
(e.currentTarget.value.length >= 10) ? $('#btn_create_tok').attr('disabled', false) : $('#btn_create_tok').attr('disabled', true);
if(10-e.currentTarget.value.length >= 1 && 10-e.currentTarget.value.length <= 9)
$('#tok_chars_needed').html(10-e.currentTarget.value.length + " " + $.i18n('conf_network_tok_chars_needed'))
else
$('#tok_chars_needed').html("<br />")
});
$(window.hyperion).off("cmd-authorize-createToken").on("cmd-authorize-createToken", function(event) {
var val = event.response.info;
showInfoDialog("newToken",$.i18n('conf_network_tok_diaTitle'),$.i18n('conf_network_tok_diaMsg')+'<br><div style="font-weight:bold">'+val.token+'</div>')
tokenList.push(val)
buildTokenList()
});
//Reorder hardcoded token div after the general token setting div
$("#conf_cont_tok").insertAfter("#conf_cont_net");
function checkApiTokenState(state)
{
if(state == false)
$("#conf_cont_tok").attr('style', 'display:none')
else
$("#conf_cont_tok").removeAttr('style')
}
$('#root_network_apiAuth').change(function () {
var state = $(this).is(":checked");
checkApiTokenState(state);
})
checkApiTokenState(window.serverConfig.network.apiAuth);
removeOverlay();
});

View File

@ -90,10 +90,9 @@ $(document).ready(function() {
{
$('.sstbody').html("");
var prios = window.serverInfo.priorities;
var i;
var clearAll = false;
for(i = 0; i < prios.length; i++)
for(var i = 0; i < prios.length; i++)
{
var origin = prios[i].origin ? prios[i].origin : "System";
origin = origin.split("@");
@ -149,9 +148,6 @@ $(document).ready(function() {
case "BOBLIGHTSERVER":
owner = $.i18n('general_comp_BOBLIGHTSERVER');
break;
case "UDPLISTENER":
owner = $.i18n('general_comp_UDPLISTENER');
break;
case "FLATBUFSERVER":
owner = $.i18n('general_comp_FLATBUFSERVER');
break;
@ -224,7 +220,7 @@ $(document).ready(function() {
var enable_icon = (components[idx].enabled? "fa-play" : "fa-stop");
var comp_name = components[idx].name;
var comp_btn_id = "comp_btn_"+comp_name;
var comp_goff = hyperionEnabled? "enabled" : "disabled";
var comp_goff = hyperionEnabled? "enabled" : "disabled";
// create btn if not there
if ($("#"+comp_btn_id).length == 0)
@ -298,8 +294,9 @@ $(document).ready(function() {
createCP('cp2', cpcolor, function(rgbT,hex){
rgb = rgbT;
sendColor()
sendColor();
setStorage('rmcpcolor', hex);
updateInputSelect();
});
$("#reset_color").off().on("click", function(){
@ -341,22 +338,22 @@ $(document).ready(function() {
$(window.hyperion).on("components-updated",updateComponents);
$(window.hyperion).on("cmd-priorities-update", function(event){
window.serverInfo.priorities = event.response.data.priorities
window.serverInfo.priorities_autoselect = event.response.data.priorities_autoselect
updateInputSelect()
window.serverInfo.priorities = event.response.data.priorities;
window.serverInfo.priorities_autoselect = event.response.data.priorities_autoselect;
updateInputSelect();
});
$(window.hyperion).on("cmd-imageToLedMapping-update", function(event){
window.serverInfo.imageToLedMappingType = event.response.data.imageToLedMappingType
updateLedMapping()
window.serverInfo.imageToLedMappingType = event.response.data.imageToLedMappingType;
updateLedMapping();
});
$(window.hyperion).on("cmd-videomode-update", function(event){
window.serverInfo.videomode = event.response.data.videomode
updateVideoMode()
window.serverInfo.videomode = event.response.data.videomode;
updateVideoMode();
});
$(window.hyperion).on("cmd-effects-update", function(event){
window.serverInfo.effects = event.response.data.effects
window.serverInfo.effects = event.response.data.effects;
updateEffectlist();
});

View File

@ -25,7 +25,10 @@ window.loggingHandlerInstalled = false;
window.watchdog = 0;
window.debugMessagesActive = true;
window.wSess = [];
window.currentHyperionInstance = 0;
window.currentHyperionInstanceName = "?";
window.comps = [];
tokenList = {};
function initRestart()
{
@ -162,10 +165,62 @@ function sendToHyperion(command, subcommand, msg)
// -----------------------------------------------------------
// wrapped server commands
// also used for watchdog
function requestAuthorization()
{
sendToHyperion("authorize","login",'"username": "Hyperion", "password": "hyperion"');
}
function requestToken(comment)
{
sendToHyperion("authorize","createToken",'"comment": "'+comment+'"');
}
function requestTokenInfo()
{
sendToHyperion("authorize","getTokenList","");
}
function requestHandleTokenRequest(id, state)
{
sendToHyperion("authorize","answerRequest",'"id":"'+id+'", "accept":'+state);
}
function requestTokenDelete(id)
{
sendToHyperion("authorize","deleteToken",'"id":"'+id+'"');
}
function requestInstanceRename(inst, name)
{
sendToHyperion("instance", "saveName",'"instance": '+inst+', "name": "'+name+'"');
}
function requestInstanceStartStop(inst, start)
{
if(start)
sendToHyperion("instance","startInstance",'"instance": '+inst);
else
sendToHyperion("instance","stopInstance",'"instance": '+inst);
}
function requestInstanceDelete(inst)
{
sendToHyperion("instance","deleteInstance",'"instance": '+inst);
}
function requestInstanceCreate(name)
{
sendToHyperion("instance","createInstance",'"name": "'+name+'"');
}
function requestInstanceSwitch(inst)
{
sendToHyperion("instance","switchTo",'"instance": '+inst);
}
function requestServerInfo()
{
sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update"]');
sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update", "instance-update"]');
}
function requestSysInfo()

View File

@ -125,6 +125,7 @@ $(document).ready( function() {
for (var i = 0; i<window.wSess.length; i++)
{
if(lsys != window.wSess[i].host+':'+window.wSess[i].port)
{
var hyperionAddress
if (window.wSess[i].address.indexOf(':') > -1 && window.wSess[i].address.length == 36)
@ -133,6 +134,7 @@ $(document).ready( function() {
hyperionAddress = 'http://'+window.wSess[i].address+':'+window.wSess[i].port
$('#id_select').append(createSelOpt(hyperionAddress, window.wSess[i].host))
}
}
$('#id_btn_saveset').off().on('click',function() {

View File

@ -114,10 +114,70 @@ function loadContent(event, forceRefresh)
$("#page-content").html('<h3>'+$.i18n('info_404')+'</h3>');
removeOverlay();
}
updateUiOnInstance(window.currentHyperionInstance);
});
}
}
function getInstanceNameByIndex(index)
{
var instData = window.serverInfo.instance
for(var key in instData)
{
if(instData[key].instance == index)
return instData[key].friendly_name;
}
return "unknown"
}
function updateHyperionInstanceListing()
{
var data = window.serverInfo.instance.filter(entry => entry.running);
$('#hyp_inst_listing').html("");
for(var key in data)
{
var currInstMarker = (data[key].instance == window.currentHyperionInstance) ? "component-on" : "";
var html = '<li id="hyperioninstance_'+data[key].instance+'"> \
<a> \
<div> \
<i class="fa fa-circle fa-fw '+currInstMarker+'"></i> \
<span>'+data[key].friendly_name+'</span> \
</div> \
</a> \
</li> '
if(data.length-1 > key)
html += '<li class="divider"></li>'
$('#hyp_inst_listing').append(html);
$('#hyperioninstance_'+data[key].instance).off().on("click",function(e){
var inst = e.currentTarget.id.split("_")[1]
requestInstanceSwitch(inst)
window.currentHyperionInstance = inst;
window.currentHyperionInstanceName = getInstanceNameByIndex(inst);
updateHyperionInstanceListing()
});
}
}
function updateUiOnInstance(inst)
{
if(inst != 0)
{
var currentURL = $(location).attr("href");
if(currentURL.indexOf('#conf_network') != -1 || currentURL.indexOf('#update') != -1 || currentURL.indexOf('#conf_webconfig') != -1 || currentURL.indexOf('#conf_grabber') != -1 || currentURL.indexOf('#conf_logging') != -1)
$("#hyperion_global_setting_notify").fadeIn("fast");
else
$("#hyperion_global_setting_notify").attr("style", "display:none");
}
else
{
$("#hyperion_global_setting_notify").fadeOut("fast");
}
}
function loadContentTo(containerId, fileName)
{
$(containerId).load("/content/"+fileName+".html");
@ -154,62 +214,103 @@ function setClassByBool(obj,enable,class1,class2)
function showInfoDialog(type,header,message)
{
if (type=="success"){
if (type=="success")
{
$('#id_body').html('<i style="margin-bottom:20px" class="fa fa-check modal-icon-check">');
if(header == "")
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+$.i18n('infoDialog_general_success_title')+'</h4>');
$('#id_footer').html('<button type="button" class="btn btn-success" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
}
else if (type=="warning"){
else if (type=="warning")
{
$('#id_body').html('<i style="margin-bottom:20px" class="fa fa-warning modal-icon-warning">');
if(header == "")
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+$.i18n('infoDialog_general_warning_title')+'</h4>');
$('#id_footer').html('<button type="button" class="btn btn-warning" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
}
else if (type=="error"){
else if (type=="error")
{
$('#id_body').html('<i style="margin-bottom:20px" class="fa fa-warning modal-icon-error">');
if(header == "")
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+$.i18n('infoDialog_general_error_title')+'</h4>');
$('#id_footer').html('<button type="button" class="btn btn-danger" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
}
else if (type == "select"){
else if (type == "select")
{
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_footer').html('<button type="button" id="id_btn_saveset" class="btn btn-primary" data-dismiss="modal"><i class="fa fa-fw fa-save"></i>'+$.i18n('general_btn_saveandreload')+'</button>');
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'+$.i18n('general_btn_cancel')+'</button>');
}
else if (type == "iswitch"){
else if (type == "iswitch")
{
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_footer').html('<button type="button" id="id_btn_saveset" class="btn btn-primary" data-dismiss="modal"><i class="fa fa-fw fa-exchange"></i>'+$.i18n('general_btn_iswitch')+'</button>');
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'+$.i18n('general_btn_cancel')+'</button>');
}
else if (type == "uilock"){
else if (type == "uilock")
{
$('#id_body').html('<img src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_footer').html('<b>'+$.i18n('InfoDialog_nowrite_foottext')+'</b>');
}
else if (type == "import"){
else if (type == "import")
{
$('#id_body').html('<i style="margin-bottom:20px" class="fa fa-warning modal-icon-warning">');
$('#id_footer').html('<button type="button" id="id_btn_import" class="btn btn-warning" data-dismiss="modal"><i class="fa fa-fw fa-save"></i>'+$.i18n('general_btn_saverestart')+'</button>');
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'+$.i18n('general_btn_cancel')+'</button>');
}
else if (type == "delInst")
{
$('#id_body').html('<i style="margin-bottom:20px" class="fa fa-remove modal-icon-warning">');
$('#id_footer').html('<button type="button" id="id_btn_yes" class="btn btn-warning" data-dismiss="modal"><i class="fa fa-fw fa-trash"></i>'+$.i18n('general_btn_yes')+'</button>');
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'+$.i18n('general_btn_cancel')+'</button>');
}
else if (type == "renInst")
{
$('#id_body_rename').html('<i style="margin-bottom:20px" class="fa fa-pencil modal-icon-edit"><br>');
$('#id_body_rename').append('<h4>'+header+'</h4>');
$('#id_body_rename').append('<input class="form-control" id="renInst_name" type="text" value="'+message+'">');
$('#id_footer_rename').html('<button type="button" id="id_btn_ok" class="btn btn-success" data-dismiss-modal="#modal_dialog_rename" disabled><i class="fa fa-fw fa-save"></i>'+$.i18n('general_btn_ok')+'</button>');
$('#id_footer_rename').append('<button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'+$.i18n('general_btn_cancel')+'</button>');
}
else if (type == "checklist")
{
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+$.i18n('infoDialog_checklist_title')+'</h4>');
$('#id_body').append(message);
$('#id_body').append(header);
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
}
else if (type == "newToken")
{
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal">'+$.i18n('general_btn_ok')+'</button>');
}
else if (type == "grantToken")
{
$('#id_body').html('<img style="margin-bottom:20px" src="img/hyperion/hyperionlogo.png" alt="Redefine ambient light!">');
$('#id_footer').html('<button type="button" class="btn btn-primary" data-dismiss="modal" id="tok_grant_acc">'+$.i18n('general_btn_grantAccess')+'</button>');
$('#id_footer').append('<button type="button" class="btn btn-danger" data-dismiss="modal" id="tok_deny_acc">'+$.i18n('general_btn_denyAccess')+'</button>');
}
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+header+'</h4>');
$('#id_body').append(message);
if(type != "renInst")
{
$('#id_body').append('<h4 style="font-weight:bold;text-transform:uppercase;">'+header+'</h4>');
$('#id_body').append(message);
}
if(type == "select" || type == "iswitch")
$('#id_body').append('<select id="id_select" class="form-control" style="margin-top:10px;width:auto;"></select>');
$("#modal_dialog").modal({
$(type == "renInst" ? "#modal_dialog_rename" : "#modal_dialog").modal({
backdrop : "static",
keyboard: false,
show: true
});
$(document).on('click', '[data-dismiss-modal]', function () {
var target = $(this).attr('data-dismiss-modal');
$(target).modal('hide');
});
}
function createHintH(type, text, container)
@ -593,8 +694,8 @@ function createHelpTable(list, phead){
// break one iteration (in the loop), if the schema has the entry hidden=true
if ("options" in list[key] && "hidden" in list[key].options && (list[key].options.hidden))
continue;
if ("access" in list[key] && ((list[key].access == "advanced" && storedAccess == "default") || (list[key].access == "expert" && storedAccess != "expert")))
continue;
if ("access" in list[key] && ((list[key].access == "advanced" && storedAccess == "default") || (list[key].access == "expert" && storedAccess != "expert")))
continue;
var text = list[key].title.replace('title', 'expl');
tbody.appendChild(createTableRow([$.i18n(list[key].title), $.i18n(text)], false, false));
@ -606,8 +707,8 @@ function createHelpTable(list, phead){
// break one iteration (in the loop), if the schema has the entry hidden=true
if ("options" in ilist[ikey] && "hidden" in ilist[ikey].options && (ilist[ikey].options.hidden))
continue;
if ("access" in ilist[ikey] && ((ilist[ikey].access == "advanced" && storedAccess == "default") || (ilist[ikey].access == "expert" && storedAccess != "expert")))
continue;
if ("access" in ilist[ikey] && ((ilist[ikey].access == "advanced" && storedAccess == "default") || (ilist[ikey].access == "expert" && storedAccess != "expert")))
continue;
var itext = ilist[ikey].title.replace('title', 'expl');
tbody.appendChild(createTableRow([$.i18n(ilist[ikey].title), $.i18n(itext)], false, false));
}

View File

@ -7,5 +7,6 @@ STRING ( STRIP "${BUILD_ID}" BUILD_ID )
STRING ( STRIP "${VERSION_ID}" VERSION_ID )
STRING ( STRIP "${GIT_REMOTE_PATH}" GIT_REMOTE_PATH )
SET ( HYPERION_BUILD_ID "${VERSION_ID} (${BUILD_ID}) Git Remote: ${GIT_REMOTE_PATH}" )
SET ( HYPERION_VERSION "${HYPERION_VERSION_CHANNEL}.${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}" )
message ( STATUS "Current Version: ${HYPERION_VERSION} (${HYPERION_BUILD_ID})" )
SET ( HYPERION_VERSION "${HYPERION_VERSION_CHANNEL} ${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}" )
message ( STATUS "Current Version: ${HYPERION_VERSION}" )
message ( STATUS " - Build: ${HYPERION_BUILD_ID}" )

View File

@ -13,11 +13,11 @@ ENDIF()
find_package(RpmBuilder)
find_package(DebBuilder)
IF(RPM_BUILDER_FOUND)
message("CPACK: Found RPM builder")
message(STATUS "CPACK: Found RPM builder")
SET ( CPACK_GENERATOR ${CPACK_GENERATOR} "RPM")
ENDIF()
IF(DEB_BUILDER_FOUND)
message("CPACK: Found DEB builder")
message(STATUS "CPACK: Found DEB builder")
SET ( CPACK_GENERATOR ${CPACK_GENERATOR} "DEB")
ENDIF()

View File

@ -263,23 +263,6 @@
"priority" : 128
},
/// The configuration of the udp listener
/// * enable : Enable or disable the udp listener (true/false)
/// * address : The listener address, pre configured is multicast which listen also to unicast ip addresses at the same time. If emtpy, multicast is disabled and it also accepts unicast from all IPs
/// * port : Port at which the udp listener starts
/// * priority : Priority of the udp listener server (Default=200)
/// * timeout : The timeout sets the timelimit for a "soft" off of the udp listener, if no packages are received (for example to switch to a gabber or InitialEffect - background-effect)
/// * shared : If true, the udp listener is shared across all hyperion instances (if using more than one (forwarder))
"udpListener" :
{
"enable" : false,
"address" : "239.255.28.01",
"port" : 2801,
"priority" : 200,
"timeout" : 10000,
"shared" : false
},
/// Configuration of the Hyperion webserver
/// * document_root : path to hyperion webapp files (webconfig developer only)
/// * port : the port where hyperion webapp is accasible
@ -307,13 +290,22 @@
]
},
"instCapture" : {
"instCapture" :
{
"systemEnable" : true,
"systemPriority" : 250,
"v4lEnable" : false,
"v4lPriority" : 240
},
"network" :
{
"internetAccessAPI" : false,
"ipWhitelist" : [],
"apiAuth" : true,
"localApiAuth" : false
},
/// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion.
"ledConfig" :
{

View File

@ -151,16 +151,6 @@
"priority" : 128
},
"udpListener" :
{
"enable" : false,
"address" : "239.255.28.01",
"port" : 2801,
"priority" : 200,
"timeout" : 10000,
"shared" : false
},
"webConfig" :
{
"document_root" : "",
@ -173,13 +163,22 @@
"disable": [""]
},
"instCapture" : {
"instCapture" :
{
"systemEnable" : true,
"systemPriority" : 250,
"v4lEnable" : false,
"v4lPriority" : 240
},
"network" :
{
"internetAccessAPI" : false,
"ipWhitelist" : [],
"apiAuth" : true,
"localApiAuth" : false
},
"ledConfig" :
{
"top" : 8,

View File

@ -80,10 +80,10 @@ typedef struct {
#define STATIC_ASSERT(condition, message) \
_Static_assert(condition, message)
#else
#define STATIC_ASSERT(condition, message) // FIXME
#define STATIC_ASSERT(condition, message)
#endif
#else
#define STATIC_ASSERT(condition, message) // FIXME
#define STATIC_ASSERT(condition, message)
#endif
STATIC_ASSERT(sizeof(PacketHeader) == 8, "PacketHeader has invalid size");

@ -1 +1 @@
Subproject commit 0eb7b3beb037748bf5b469e4df9db862c4833e35
Subproject commit 2d5315ff0eebfa4b9c967e708c24be0b21d921b6

@ -1 +1 @@
Subproject commit 6c5ade93d1af78cd19e60ee5ecc34adfd111b186
Subproject commit 68c6da2de32249d126264a363cc5ab788c87cc8b

View File

@ -1,34 +0,0 @@
option(BUILD_HYPERION_DOC "Build hyperion documentation" OFF)
# Find doxygen and check if Doxygen is installed
find_package(Doxygen QUIET)
if (BUILD_HYPERION_DOC)
if (DOXYGEN_FOUND)
# set input and output files
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/hyperion.in.doxygen)
set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/hyperion.doxygen)
# request to configure the file
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
message(STATUS "Doxygen build started")
# Define all static (i.e. not generated) documentation files
set(StaticDocumentationFiles hyperion-footer.html)
# Loop over all static documentation files
foreach(StaticDocumentationFile ${StaticDocumentationFiles})
# Copy the file to the bindary documentation directory
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${StaticDocumentationFile} ${CMAKE_CURRENT_BINARY_DIR}/html/${StaticDocumentationFile} COPYONLY)
endforeach()
add_custom_target( doc_doxygen ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM )
else(DOXYGEN_FOUND)
message(WARNING "Doxygen not found, unable to generate documenation!")
endif(DOXYGEN_FOUND)
endif()

View File

@ -1,8 +0,0 @@
<hr>
<table border="0">
<tr>
<td><address style="text-align: right;"><small>Generated at $datetime for $projectname by <a href="http://www.doxygen.org">doxygen</a> $doxygenversion.</small></address></td>
</tr>
</table>
</body>
</html>

View File

@ -2234,7 +2234,7 @@ INTERACTIVE_SVG = NO
# found. If left blank, it is assumed the dot tool can be found in the path.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_PATH = ""
DOT_PATH =
# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the \dotfile

View File

@ -11,7 +11,12 @@
#include <QMutex>
#include <QString>
// HyperionInstanceManager
#include <hyperion/HyperionIManager.h>
class JsonCB;
class AuthManager;
class HyperionIManager;
class JsonAPI : public QObject
{
@ -24,16 +29,17 @@ public:
/// @param peerAddress provide the Address of the peer
/// @param log The Logger class of the creator
/// @param parent Parent QObject
/// @param localConnection True when the sender has origin home network
/// @param noListener if true, this instance won't listen for hyperion push events
///
JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener = false);
JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener = false);
///
/// Handle an incoming JSON message
///
/// @param message the incoming message as string
///
void handleMessage(const QString & message);
void handleMessage(const QString & message, const QString& httpAuthHeader = "");
public slots:
///
@ -48,6 +54,32 @@ public slots:
/// process and push new log messages from logger (if enabled)
void incommingLogMessage(const Logger::T_LOG_MESSAGE&);
private slots:
///
/// @brief Handle emits from AuthManager of new request, just _userAuthorized sessions are allowed to handle them
/// @param id The id of the request
/// @param The comment which needs to be accepted
///
void handlePendingTokenRequest(const QString& id, const QString& comment);
///
/// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance we are allowed to send response.
/// @param success If true the request was accepted else false and no token was created
/// @param caller The origin caller instance who requested this token
/// @param token The new token that is now valid
/// @param comment The comment that was part of the request
/// @param id The id that was part of the request
///
void handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id);
///
/// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState
/// @param instaneState A state from enum
/// @param instance The index of instance
/// @param name The name of the instance, just available with H_CREATED
///
void handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name = QString());
signals:
///
/// Signal emits with the reply message provided with handleMessage()
@ -60,6 +92,16 @@ signals:
void forwardJsonMessage(QJsonObject);
private:
/// Auth management pointer
AuthManager* _authManager;
/// Reflect auth status of this client
bool _authorized;
bool _userAuthorized;
/// Reflect auth required
bool _apiAuthRequired;
// true if further callbacks are forbidden (http)
bool _noListener;
@ -69,6 +111,9 @@ private:
/// Log instance
Logger* _log;
/// Hyperion instance manager
HyperionIManager* _instanceManager;
/// Hyperion instance
Hyperion* _hyperion;
@ -95,6 +140,14 @@ private:
/// timeout for led color refresh
volatile qint64 _led_stream_timeout;
///
/// @brief Handle the switches of Hyperion instances
/// @param instance the instance to switch
/// @param forced indicate if it was a forced switch by system
/// @return true on success. false if not found
///
bool handleInstanceSwitch(const quint8& instance = 0, const bool& forced = false);
///
/// Handle an incoming JSON Color message
///
@ -151,6 +204,13 @@ private:
///
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
///
@ -214,12 +274,26 @@ private:
///
void handleVideoModeCommand(const QJsonObject & message, const QString &command, const int tan);
///
/// Handle an incoming JSON Clearall message
/// Handle an incoming JSON plugin message
///
/// @param message the incoming message
///
void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan);
void handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan);
///
/// Handle HTTP on-the-fly token authorization
/// @param command The command
/// @param tan The tan
/// @param token The token to verify
/// @return True on succcess else false (pushes failed client feedback)
///
bool handleHTTPAuth(const QString& command, const int& tan, const QString& token);
/// Handle an incoming JSON instance message
///
/// @param message the incoming message
///
void handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan);
///
/// Handle an incoming JSON message of unknown type

View File

@ -23,7 +23,7 @@ class JsonCB : public QObject
Q_OBJECT
public:
JsonCB(QObject* parent);
JsonCB(Hyperion* hyperion, QObject* parent);
///
/// @brief Subscribe to future data updates given by cmd
@ -94,6 +94,18 @@ private slots:
///
void handleSettingsChange(const settings::type& type, const QJsonDocument& data);
///
/// @brief Handle led config specific updates (required for led color streaming with positional display)
/// @param type The settings type from enum
/// @param data The data as QJsonDocument
///
void handleLedsConfigChange(const settings::type& type, const QJsonDocument& data);
///
/// @brief Handle Hyperion instance manager change
///
void handleInstanceChange();
private:
/// pointer of Hyperion instance
Hyperion* _hyperion;

222
include/db/AuthTable.h Normal file
View File

@ -0,0 +1,222 @@
#pragma once
// hyperion
#include <db/DBManager.h>
#include <QCryptographicHash>
// qt
#include <QDateTime>
#include <QUuid>
///
/// @brief Authentication table interface
///
class AuthTable : public DBManager
{
public:
/// construct wrapper with auth table
AuthTable(QObject* parent = nullptr)
: DBManager(parent)
{
// init Auth table
setTable("auth");
// create table columns
createTable(QStringList()<<"user TEXT"<<"password BLOB"<<"token BLOB"<<"salt BLOB"<<"comment TEXT"<<"id TEXT"<<"created_at TEXT"<<"last_use TEXT");
};
~AuthTable(){};
///
/// @brief Create a user record, if called on a existing user the auth is recreated
/// @param[in] user The username
/// @param[in] pw The password
/// @return true on success else false
///
inline bool createUser(const QString& user, const QString& pw)
{
// new salt
QByteArray salt = QCryptographicHash::hash(QUuid::createUuid().toByteArray(), QCryptographicHash::Sha512).toHex();
QVariantMap map;
map["user"] = user;
map["salt"] = salt;
map["password"] = hashPasswordWithSalt(pw,salt);
map["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
VectorPair cond;
cond.append(CPair("user",user));
return createRecord(cond, map);
}
///
/// @brief Test if user record exists
/// @param[in] user The user id
/// @return true on success else false
///
inline bool userExist(const QString& user)
{
VectorPair cond;
cond.append(CPair("user",user));
return recordExists(cond);
}
///
/// @brief Test if a user is authorized for access with given pw.
/// @param user The user name
/// @param pw The password
/// @return True on success else false
///
inline bool isUserAuthorized(const QString& user, const QString& pw)
{
if(userExist(user) && (calcPasswordHashOfUser(user, pw) == getPasswordHashOfUser(user)))
{
updateUserUsed(user);
return true;
}
return false;
}
///
/// @brief Update 'last_use' column entry for the corresponding user
/// @param[in] user The user to search for
///
inline void updateUserUsed(const QString& user)
{
QVariantMap map;
map["last_use"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
VectorPair cond;
cond.append(CPair("user", user));
updateRecord(cond, map);
}
///
/// @brief Test if token record exists, updates last_use on success
/// @param[in] token The token id
/// @return true on success else false
///
inline bool tokenExist(const QString& token)
{
QVariantMap map;
map["last_use"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
VectorPair cond;
cond.append(CPair("token", hashToken(token)));
if(recordExists(cond))
{
// update it
createRecord(cond,map);
return true;
}
return false;
}
///
/// @brief Create a new token record with comment
/// @param[in] token The token id as plaintext
/// @param[in] comment The comment for the token (eg a human readable identifier)
/// @param[in] id The id for the token
/// @return true on success else false
///
inline bool createToken(const QString& token, const QString& comment, const QString& id)
{
QVariantMap map;
map["comment"] = comment;
map["id"] = idExist(id) ? QUuid::createUuid().toString().remove("{").remove("}").left(5) : id;
map["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
VectorPair cond;
cond.append(CPair("token", hashToken(token)));
return createRecord(cond, map);
}
///
/// @brief Delete token record by id
/// @param[in] id The token id
/// @return true on success else false
///
inline bool deleteToken(const QString& id)
{
VectorPair cond;
cond.append(CPair("id", id));
return deleteRecord(cond);
}
///
/// @brief Get all 'comment', 'last_use' and 'id' column entries
/// @return A vector of all lists
///
inline const QVector<QVariantMap> getTokenList()
{
QVector<QVariantMap> results;
getRecords(results, QStringList() << "comment" << "id" << "last_use");
return results;
}
///
/// @brief Test if id exists
/// @param[in] id The id
/// @return true on success else false
///
inline bool idExist(const QString& id)
{
VectorPair cond;
cond.append(CPair("id", id));
return recordExists(cond);
}
///
/// @brief Get the passwort hash of a user from db
/// @param user The user name
/// @return password as hash
///
inline const QByteArray getPasswordHashOfUser(const QString& user)
{
QVariantMap results;
VectorPair cond;
cond.append(CPair("user", user));
getRecord(cond, results, QStringList()<<"password");
return results["password"].toByteArray();
}
///
/// @brief Calc the password hash of a user based on user name and password
/// @param user The user name
/// @param pw The password
/// @return The calced password hash
///
inline const QByteArray calcPasswordHashOfUser(const QString& user, const QString& pw)
{
// get salt
QVariantMap results;
VectorPair cond;
cond.append(CPair("user", user));
getRecord(cond, results, QStringList()<<"salt");
// calc
return hashPasswordWithSalt(pw,results["salt"].toByteArray());
}
///
/// @brief Create a password hash of plaintex password + salt
/// @param pw The plaintext password
/// @param salt The salt
/// @return The password hash with salt
///
inline const QByteArray hashPasswordWithSalt(const QString& pw, const QByteArray& salt)
{
return QCryptographicHash::hash(pw.toUtf8().append(salt), QCryptographicHash::Sha512).toHex();
}
///
/// @brief Create a token hash
/// @param token The plaintext token
/// @return The token hash
///
inline const QByteArray hashToken(const QString& token)
{
return QCryptographicHash::hash(token.toUtf8(), QCryptographicHash::Sha512).toHex();
}
};

130
include/db/DBManager.h Normal file
View File

@ -0,0 +1,130 @@
#pragma once
#include <utils/Logger.h>
#include <QMap>
#include <QVariant>
#include <QPair>
#include <QVector>
class QSqlDatabase;
class QSqlQuery;
typedef QPair<QString,QVariant> CPair;
typedef QVector<CPair> VectorPair;
///
/// @brief Database interface for SQLite3.
/// Inherit this class to create component specific methods based on this interface
/// Usage: setTable(name) once before you use read/write actions
/// To use another database use setDb(newDB) (defaults to "hyperion")
///
/// Incompatible functions with SQlite3:
/// QSqlQuery::size() returns always -1
///
class DBManager : public QObject
{
Q_OBJECT
public:
DBManager(QObject* parent = nullptr);
~DBManager();
/// set root path
void setRootPath(const QString& rootPath);
/// define the database to work with
void setDatabaseName(const QString& dbn) { _dbn = dbn; };
/// set a table to work with
void setTable(const QString& table);
/// get current database object set with setDB()
QSqlDatabase getDB() const;
///
/// @brief Create a table (if required) with the given columns. Older tables will be updated accordingly with missing columns
/// Does not delete or migrate old columns
/// @param[in] columns The columns of the table. Requires at least one entry!
/// @return True on success else false
///
bool createTable(QStringList& columns) const;
///
/// @brief Create a column if the column already exists returns false and logs error
/// @param[in] column The column of the table
/// @return True on success else false
///
bool createColumn(const QString& column) const;
///
/// @brief Check if at least one record exists in table with the conditions
/// @param[in] conditions The search conditions (WHERE)
/// @return True on success else false
///
bool recordExists(const VectorPair& conditions) const;
///
/// @brief Create a new record in table when the conditions find no existing entry. Add additional key:value pairs in columns
/// DO NOT repeat column keys between 'conditions' and 'columns' as they will be merged on creation
/// @param[in] conditions conditions to search for, as a record may exist and should be updated instead (WHERE)
/// @param[in] columns columns to create or update (optional)
/// @return True on success else false
///
bool createRecord(const VectorPair& conditions, const QVariantMap& columns = QVariantMap()) const;
///
/// @brief Update a record with conditions and additional key:value pairs in columns
/// @param[in] conditions conditions which rows should be updated (WHERE)
/// @param[in] columns columns to update
/// @return True on success else false
///
bool updateRecord(const VectorPair& conditions, const QVariantMap& columns) const;
///
/// @brief Get data of a single record, multiple records are not supported
/// @param[in] conditions condition to search for (WHERE)
/// @param[out] results results of query
/// @param[in] tColumns target columns to search in (optional) if not provided returns all columns
/// @return True on success else false
///
bool getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns = QStringList()) const;
///
/// @brief Get data of multiple records, you need to specify the columns. This search is without conditions. Good to grab all data from db
/// @param[in] conditions condition to search for (WHERE)
/// @param[out] results results of query
/// @param[in] tColumns target columns to search in (optional) if not provided returns all columns
/// @return True on success else false
///
bool getRecords(QVector<QVariantMap>& results, const QStringList& tColumns = QStringList()) const;
///
/// @brief Delete a record determined by conditions
/// @param[in] conditions conditions of the row to delete it (WHERE)
/// @return True on success; on error or not found false
///
bool deleteRecord(const VectorPair& conditions) const;
///
/// @brief Check if table exists in current database
/// @param[in] table The name of the table
/// @return True on success else false
///
bool tableExists(const QString& table) const;
///
/// @brief Delete a table, fails silent (return true) if table is not found
/// @param[in] table The name of the table
/// @return True on success else false
///
bool deleteTable(const QString& table) const;
private:
Logger* _log;
/// databse connection & file name, defaults to hyperion
QString _dbn = "hyperion";
/// table in database
QString _table;
/// addBindValue to query given by QVariantList
void doAddBindValue(QSqlQuery& query, const QVariantList& variants) const;
};

231
include/db/InstanceTable.h Normal file
View File

@ -0,0 +1,231 @@
#pragma once
// db
#include <db/DBManager.h>
#include <db/SettingsTable.h>
// qt
#include <QDateTime>
///
/// @brief Hyperion instance manager specific database interface. prepares also the Hyperion database for all follow up usage (Init QtSqlConnection) along with db name
///
class InstanceTable : public DBManager
{
public:
InstanceTable(const QString& rootPath, QObject* parent = nullptr)
: DBManager(parent)
{
// Init Hyperion database usage
setRootPath(rootPath);
setDatabaseName("hyperion");
// Init instance table
setTable("instances");
createTable(QStringList()<<"instance INTEGER"<<"friendly_name TEXT"<<"enabled INTEGER DEFAULT 0"<<"last_use TEXT");
// start/create the first Hyperion instance index 0
createInstance();
};
~InstanceTable(){};
///
/// @brief Create a new Hyperion instance entry, the name needs to be unique
/// @param name The name of the instance
/// @param[out] inst The id that has been assigned
/// @return True on success else false
///
inline bool createInstance(const QString& name, quint8& inst)
{
VectorPair fcond;
fcond.append(CPair("friendly_name",name));
// check duplicate
if(!recordExists(fcond))
{
inst = 0;
VectorPair cond;
cond.append(CPair("instance",inst));
// increment to next avail index
while(recordExists(cond))
{
inst++;
cond.removeFirst();
cond.append(CPair("instance",inst));
}
// create
QVariantMap data;
data["friendly_name"] = name;
data["instance"] = inst;
VectorPair lcond;
return createRecord(lcond, data);
}
return false;
}
///
/// @brief Delete a Hyperion instance
/// @param inst The id that has been assigned
/// @return True on success else false
///
inline bool deleteInstance(const quint8& inst)
{
VectorPair cond;
cond.append(CPair("instance",inst));
if(deleteRecord(cond))
{
// delete settings entries
SettingsTable settingsTable(inst);
settingsTable.deleteInstance();
return true;
}
return false;
}
///
/// @brief Assign a new name for the given instance
/// @param inst The instance index
/// @param name The new name of the instance
/// @return True on success else false (instance not found)
///
inline bool saveName(const quint8& inst, const QString& name)
{
VectorPair fcond;
fcond.append(CPair("friendly_name",name));
// check duplicate
if(!recordExists(fcond))
{
if(instanceExist(inst))
{
VectorPair cond;
cond.append(CPair("instance",inst));
QVariantMap data;
data["friendly_name"] = name;
return updateRecord(cond, data);
}
}
return false;
}
///
/// @brief Get all instances with all columns
/// @param justEnabled return just enabled instances if true
/// @return The found instances
///
inline QVector<QVariantMap> getAllInstances(const bool& justEnabled = false)
{
QVector<QVariantMap> results;
getRecords(results);
if(justEnabled)
{
for (auto it = results.begin(); it != results.end();)
{
if( ! (*it)["enabled"].toBool())
{
it = results.erase(it);
continue;
}
++it;
}
}
return results;
}
///
/// @brief Test if instance record exists
/// @param[in] user The user id
/// @return true on success else false
///
inline bool instanceExist(const quint8& inst)
{
VectorPair cond;
cond.append(CPair("instance",inst));
return recordExists(cond);
}
///
/// @brief Get instance name by instance index
/// @param index The index to search for
/// @return The name of this index, may return NOT FOUND if not found
///
inline const QString getNamebyIndex(const quint8 index)
{
QVariantMap results;
VectorPair cond;
cond.append(CPair("instance", index));
getRecord(cond, results, QStringList("friendly_name"));
QString name = results["friendly_name"].toString();
return name.isEmpty() ? "NOT FOUND" : name;
}
///
/// @brief Update 'last_use' timestamp
/// @param inst The instance to update
///
inline void setLastUse(const quint8& inst)
{
VectorPair cond;
cond.append(CPair("instance", inst));
QVariantMap map;
map["last_use"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
updateRecord(cond, map);
}
///
/// @brief Update 'enabled' column by instance index
/// @param inst The instance to update
/// @param newState True when enabled else false
///
inline void setEnable(const quint8& inst, const bool& newState)
{
VectorPair cond;
cond.append(CPair("instance", inst));
QVariantMap map;
map["enabled"] = newState;
updateRecord(cond, map);
}
///
/// @brief Get state of 'enabled' column by instance index
/// @param inst The instance to get
/// @return True when enabled else false
///
inline bool isEnabled(const quint8& inst)
{
VectorPair cond;
cond.append(CPair("instance", inst));
QVariantMap results;
getRecord(cond, results);
return results["enabled"].toBool();
}
private:
///
/// @brief Create first Hyperion instance entry, if index 0 is not found.
///
inline void createInstance()
{
if(instanceExist(0))
setEnable(0, true);
else
{
QVariantMap data;
data["friendly_name"] = "First LED Hardware instance";
VectorPair cond;
cond.append(CPair("instance", 0));
if(createRecord(cond, data))
setEnable(0, true);
else
throw std::runtime_error("Failed to create Hyperion root instance in db! This should never be the case...");
}
}
};

62
include/db/MetaTable.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
// hyperion
#include <db/DBManager.h>
// qt
#include <QDateTime>
#include <QUuid>
#include <QNetworkInterface>
#include <QCryptographicHash>
///
/// @brief meta table specific database interface
///
class MetaTable : public DBManager
{
public:
/// construct wrapper with plugins table and columns
MetaTable(QObject* parent = nullptr)
: DBManager(parent)
{
setTable("meta");
createTable(QStringList()<<"uuid TEXT"<<"created_at TEXT");
};
~MetaTable(){};
///
/// @brief Get the uuid, if the uuid is not set it will be created
/// @return The uuid
///
inline const QString getUUID()
{
QVector<QVariantMap> results;
getRecords(results, QStringList() << "uuid");
for(const auto & entry : results)
{
if(!entry["uuid"].toString().isEmpty())
return entry["uuid"].toString();
}
// create new uuidv5 based on net adapter MAC, save to db and return
QString hash;
foreach(QNetworkInterface interface, QNetworkInterface::allInterfaces())
{
if (!(interface.flags() & QNetworkInterface::IsLoopBack))
{
hash = QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit(),QCryptographicHash::Sha1).toHex();
break;
}
}
const QString newUuid = QUuid::createUuidV5(QUuid(), hash).toString().mid(1, 36);
VectorPair cond;
cond.append(CPair("uuid",newUuid));
QVariantMap map;
map["created_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
createRecord(cond, map);
return newUuid;
}
};

124
include/db/SettingsTable.h Normal file
View File

@ -0,0 +1,124 @@
#pragma once
// hyperion
#include <db/DBManager.h>
// qt
#include <QDateTime>
#include <QJsonDocument>
///
/// @brief settings table db interface
///
class SettingsTable : public DBManager
{
public:
/// construct wrapper with settings table
SettingsTable(const quint8& instance, QObject* parent = nullptr)
: DBManager(parent)
, _hyperion_inst(instance)
{
setTable("settings");
// create table columns
createTable(QStringList()<<"type TEXT"<<"config TEXT"<<"hyperion_inst INTEGER"<<"updated_at TEXT");
};
~SettingsTable(){};
///
/// @brief Create or update a settings record
/// @param[in] type type of setting
/// @param[in] config The configuration data
/// @return true on success else false
///
inline bool createSettingsRecord(const QString& type, const QString& config) const
{
QVariantMap map;
map["config"] = config;
map["updated_at"] = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
VectorPair cond;
cond.append(CPair("type",type));
// when a setting is not global we are searching also for the instance
if(!isSettingGlobal(type))
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
return createRecord(cond, map);
}
///
/// @brief Test if record exist, type can be global setting or local (instance)
/// @param[in] type type of setting
/// @param[in] hyperion_inst The instance of hyperion assigned (might be empty)
/// @return true on success else false
///
inline bool recordExist(const QString& type) const
{
VectorPair cond;
cond.append(CPair("type",type));
// when a setting is not global we are searching also for the instance
if(!isSettingGlobal(type))
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
return recordExists(cond);
}
///
/// @brief Get 'config' column of settings entry as QJsonDocument
/// @param[in] type The settings type
/// @return The QJsonDocument
///
inline QJsonDocument getSettingsRecord(const QString& type) const
{
QVariantMap results;
VectorPair cond;
cond.append(CPair("type",type));
// when a setting is not global we are searching also for the instance
if(!isSettingGlobal(type))
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
getRecord(cond, results, QStringList("config"));
return QJsonDocument::fromJson(results["config"].toByteArray());
}
///
/// @brief Get 'config' column of settings entry as QString
/// @param[in] type The settings type
/// @return The QString
///
inline QString getSettingsRecordString(const QString& type) const
{
QVariantMap results;
VectorPair cond;
cond.append(CPair("type",type));
// when a setting is not global we are searching also for the instance
if(!isSettingGlobal(type))
cond.append(CPair("AND hyperion_inst",_hyperion_inst));
getRecord(cond, results, QStringList("config"));
return results["config"].toString();
}
///
/// @brief Delete all settings entries associated with this instance, called from InstanceTable of HyperionIManager
///
inline void deleteInstance() const
{
VectorPair cond;
cond.append(CPair("hyperion_inst",_hyperion_inst));
deleteRecord(cond);
}
inline bool isSettingGlobal(const QString& type) const
{
// list of global settings
QStringList list;
// server port services
list << "jsonServer" << "protoServer" << "flatbufServer" << "forwarder" << "webConfig" << "network"
// capture
<< "framegrabber" << "grabberV4L2"
// other
<< "logger";
return list.contains(type);
}
private:
const quint8 _hyperion_inst;
};

View File

@ -46,13 +46,13 @@ public:
/// @brief Set manual interuption to true,
/// Note: DO NOT USE QThread::interuption!
///
void setInteruptionFlag() { _interupt = true; };
void requestInterruption() { _interupt = true; };
///
/// @brief Check if the interuption flag has been set
/// @return The flag state
///
bool hasInteruptionFlag() { return _interupt; };
bool isInterruptionRequested() { return _interupt; };
QString getScript() const { return _script; }
QString getName() const { return _name; }

View File

@ -49,5 +49,4 @@ public:
static PyObject* wrapImageCOffset (PyObject *self, PyObject *args);
static PyObject* wrapImageCShear (PyObject *self, PyObject *args);
static PyObject* wrapImageResetT (PyObject *self, PyObject *args);
static Effect * getEffect();
};

View File

@ -9,6 +9,8 @@
class QTcpServer;
class FlatBufferClient;
class NetOrigin;
///
/// @brief A TcpServer to receive images of different formats with Google Flatbuffer
@ -56,6 +58,7 @@ private:
private:
QTcpServer* _server;
NetOrigin* _netOrigin;
Logger* _log;
int _timeout;
quint16 _port;

View File

@ -26,6 +26,9 @@ public slots:
void setSignalDetectionEnable(bool enable);
void setDeviceVideoStandard(QString device, VideoStandard videoStandard);
signals:
void componentStateChanged(const hyperion::Components component, bool enable);
private slots:
void newFrame(const Image<ColorRgb> & image);
void readError(const char* err);

View File

@ -0,0 +1,173 @@
#pragma once
#include <utils/Logger.h>
#include <utils/settings.h>
//qt
#include <QMap>
class AuthTable;
class MetaTable;
class QTimer;
///
/// @brief Manage the authorization of user and tokens. This class is created once as part of the HyperionDaemon
/// To work with the global instance use AuthManager::getInstance()
///
class AuthManager : public QObject
{
Q_OBJECT
private:
friend class HyperionDaemon;
/// constructor is private, can be called from HyperionDaemon
AuthManager(QObject* parent = 0);
public:
struct AuthDefinition{
QString id;
QString comment;
QObject* caller;
uint64_t timeoutTime;
QString token;
QString lastUse;
};
///
/// @brief Get the unique id (imported from removed class 'Stats')
/// @return The unique id
///
const QString & getID() { return _uuid; };
///
/// @brief Get all available token entries
///
const QVector<AuthDefinition> getTokenList();
///
/// @brief Check authorization is required according to the user setting
/// @return True if authorization required else false
///
bool & isAuthRequired();
///
/// @brief Check if authorization is required for local network connections
/// @return True if authorization required else false
///
bool & isLocalAuthRequired();
///
/// @brief Create a new token and skip the usual chain
/// @param comment The comment that should be used for
/// @return The new Auth definition
///
const AuthDefinition createToken(const QString& comment);
///
/// @brief Check if user is authorized
/// @param user The username
/// @param pw The password
/// @return True if authorized else false
///
bool isUserAuthorized(const QString& user, const QString& pw);
///
/// @brief Check if token is authorized
/// @param token The token
/// @return True if authorized else false
///
bool isTokenAuthorized(const QString& token);
///
/// @brief Generate a new pending token request with the provided comment and id as identifier helper
/// @param caller The QObject of the caller to deliver the reply
/// @param comment The comment as ident helper
/// @param id The id created by the caller
///
void setNewTokenRequest(QObject* caller, const QString& comment, const QString& id);
///
/// @brief Accept a token request by id, generate token and inform token caller
/// @param id The id of the request
/// @return True on success, false if not found
///
bool acceptTokenRequest(const QString& id);
///
/// @brief Deny a token request by id, inform the requester
/// @param id The id of the request
/// @return True on success, false if not found
///
bool denyTokenRequest(const QString& id);
///
/// @brief Get pending requests
/// @return All pending requests
///
const QMap<QString, AuthDefinition> getPendingRequests();
///
/// @brief Delete a token by id
/// @param id The token id
/// @return True on success else false (or not found)
///
bool deleteToken(const QString& id);
/// Pointer of this instance
static AuthManager* manager;
/// Get Pointer of this instance
static AuthManager* getInstance() { return manager; };
public slots:
///
/// @brief Handle settings update from Hyperion Settingsmanager emit
/// @param type settings type from enum
/// @param config configuration object
///
void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config);
signals:
///
/// @brief Emits whenever a new token Request has been created along with the id and comment
/// @param id The id of the request
/// @param comment The comment of the request
///
void newPendingTokenRequest(const QString& id, const QString& comment);
///
/// @brief Emits when the user has accepted or denied a token
/// @param success If true the request was accepted else false and no token will be available
/// @param caller The origin caller instance who requested this token
/// @param token The new token that is now valid
/// @param comment The comment that was part of the request
/// @param id The id that was part of the request
///
void tokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id);
private:
/// Database interface for auth table
AuthTable* _authTable;
/// Database interface for meta table
MetaTable* _metaTable;
/// Unique ID (imported from removed class 'Stats')
QString _uuid;
/// All pending requests
QMap<QString,AuthDefinition> _pendingRequests;
/// Reflect state of global auth
bool _authRequired;
/// Reflect state of local auth
bool _localAuthRequired;
/// Timer for counting against pendingRequest timeouts
QTimer* _timer;
private slots:
///
/// @brief Check timeout of pending requests
///
void checkTimeout();
};

View File

@ -9,6 +9,7 @@
#include <grabber/VideoStandard.h>
#include <utils/ImageResampler.h>
#include <utils/Logger.h>
#include <utils/Components.h>
///
/// @brief The Grabber class is responsible to apply image resizes (with or without ImageResampler)
@ -96,6 +97,12 @@ public:
///
void setEnabled(bool enable);
signals:
///
/// @brief PIPE component state changes from HyperionDaemon to V4L2Grabber
///
void componentStateChanged(const hyperion::Components component, bool enable);
protected:
ImageResampler _imageResampler;

View File

@ -11,7 +11,6 @@
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QFileSystemWatcher>
#include <QMutex>
// hyperion-utils includes
@ -36,7 +35,6 @@
#include <utils/settings.h>
// Forward class declaration
class QTimer;
class HyperionDaemon;
class ImageProcessor;
class MessageForwarder;
@ -71,28 +69,13 @@ public:
///
/// Destructor; cleans up resources
///
~Hyperion();
virtual ~Hyperion();
///
/// free all alocated objects, should be called only from constructor or before restarting hyperion
///
void freeObjects(bool emitCloseSignal=false);
///
/// @brief creates a new Hyperion instance, usually called from the Hyperion Daemon
/// @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(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath);
///
/// @brief Get a pointer of this Hyperion instance
/// @return Hyperion instance pointer
///
static Hyperion* getInstance();
///
/// @brief Get a pointer to the effect engine
/// @return EffectEngine instance pointer
@ -122,6 +105,12 @@ public:
///
bool saveSettings(QJsonObject config, const bool& correct = false);
///
/// @brief Get instance index of this instance
/// @return The index of this instance
///
const quint8 & getInstanceIndex() { return _instIndex; };
///
/// Returns the number of attached leds
///
@ -194,26 +183,6 @@ public:
/// @return json config
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() const;
///
/// @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 Specific 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);
/// enable/disable automatic/priorized source selection
/// @param enabled the state
void setSourceAutoSelectEnabled(bool enabled);
@ -244,21 +213,9 @@ public:
ComponentRegister& getComponentRegister() { return _componentRegister; };
bool configModified() { return _configMod; };
bool configWriteable() { return _configWrite; };
/// gets the methode how image is maped to leds
const int & getLedMappingType();
/// get the root path for all hyperion user data files
const QString &getRootPath() { return _rootPath; };
/// 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
@ -273,6 +230,17 @@ public:
const QString & getActiveDevice();
public slots:
///
/// @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 Specific 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);
///
/// @brief Update the current color of a priority (prev registered with registerInput())
/// DO NOT use this together with setInputImage() at the same time!
@ -293,14 +261,7 @@ public slots:
/// @param clearEffect Should be true when NOT called from an effect
/// @return True on success, false when priority is not found
///
bool setInputImage(const int priority, const Image<ColorRgb>& image, int64_t timeout_ms = -1, const bool& clearEffect = true);
///
/// @brief Set the given priority to inactive
/// @param priority The priority
/// @return True on success false if not found
///
bool setInputInactive(const quint8& priority);
bool setInputImage(const int priority, const Image<ColorRgb>& image, const int64_t timeout_ms = -1, const bool& clearEffect = true);
///
/// Writes a single color to all the leds for the given time and priority
@ -309,10 +270,18 @@ public slots:
///
/// @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]
/// @param[in] origin The setter
/// @param clearEffect Should be true when NOT called from an effect
///
void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms = -1, const QString& origin = "System" ,bool clearEffects = true);
void setColor(const int priority, const ColorRgb &ledColor, const int timeout_ms = -1, const QString& origin = "System" ,bool clearEffects = true);
///
/// @brief Set the given priority to inactive
/// @param priority The priority
/// @return True on success false if not found
///
bool setInputInactive(const quint8& priority);
///
/// Returns the list with unique adjustment identifiers
@ -336,7 +305,7 @@ public slots:
/// @param[in] priority The priority channel
/// @return True on success else false (not found)
///
bool clear(int priority);
bool clear(const int priority);
///
/// @brief Clears all priority channels. This will switch the leds off until a new priority is written.
@ -372,8 +341,15 @@ public slots:
///
void setVideoMode(const VideoMode& mode);
public:
static Hyperion *_hyperion;
///
/// @brief Init after thread start
///
void start();
///
/// @brief Stop the execution of this thread, helper to properly track eventing
///
void stop();
signals:
/// Signal which is emitted when a priority channel is actively cleared
@ -453,15 +429,24 @@ signals:
///
void rawLedColors(const std::vector<ColorRgb>& ledValues);
private slots:
///
/// @brief Emits before thread quit is requested
///
void finished();
///
/// @brief Emits after thread has been started
///
void started();
public slots:
///
/// Updates the priority muxer with the current time and (re)writes the led color with applied
/// transforms.
///
void update();
/// check for configWriteable and modified changes, called by _fsWatcher or fallback _cTimer
void checkConfigState(QString cfile = NULL);
private slots:
///
/// @brief Apply ComponentRegister emits for COMP_ALL. Enables/Disables core timers
@ -477,17 +462,23 @@ private slots:
///
void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config);
///
/// @brief Apply new videoMode from Daemon to _currVideoMode
///
void handleNewVideoMode(const VideoMode& mode) { _currVideoMode = mode; };
private:
friend class HyperionDaemon;
friend class HyperionIManager;
///
/// Constructs the Hyperion instance based on the given Json configuration
/// @brief Constructs the Hyperion instance, just accessible for HyperionIManager
/// @param instance The instance index
///
/// @param[in] qjsonConfig The Json configuration
///
Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath);
Hyperion(const quint8& instance);
/// The parent Hyperion Daemon
HyperionDaemon* _daemon;
/// instance index
const quint8 _instIndex;
/// Settings manager of this instance
SettingsManager* _settingsManager;
@ -524,40 +515,17 @@ private:
// Message forwarder
MessageForwarder * _messageForwarder;
/// the name of config file
QString _configFile;
/// root path for all hyperion user data files
QString _rootPath;
/// unique id per instance
QString _id;
/// Logger instance
Logger * _log;
/// count of hardware leds
unsigned _hwLedCount;
QByteArray _configHash;
QSize _ledGridSize;
/// Store the previous compID for smarter update()
hyperion::Components _prevCompId;
/// Observe filesystem changes (_configFile), if failed use Timer
QFileSystemWatcher _fsWatcher;
QTimer* _cTimer;
/// holds the prev states of configWriteable and modified
bool _prevConfigMod = false;
bool _prevConfigWrite = true;
/// holds the current states of configWriteable and modified
bool _configMod = false;
bool _configWrite = true;
/// Background effect instance, kept active to react on setting changes
BGEffectHandler* _BGEffectHandler;
/// Capture control for Daemon native capture
@ -566,6 +534,8 @@ private:
/// buffer for leds (with adjustment)
std::vector<ColorRgb> _ledBuffer;
VideoMode _currVideoMode = VIDEO_2D;
/// Boblight instance
BoblightServer* _boblightServer;

View File

@ -0,0 +1,176 @@
#pragma once
// util
#include <utils/Logger.h>
#include <utils/VideoMode.h>
#include <utils/settings.h>
#include <utils/Components.h>
// qt
#include <QMap>
class Hyperion;
class InstanceTable;
enum instanceState{
H_STARTED,
H_ON_STOP,
H_STOPPED,
H_CREATED,
H_DELETED
};
///
/// @brief HyperionInstanceManager manages the instances of the the Hyperion class
///
class HyperionIManager : public QObject
{
Q_OBJECT
public:
// global instance pointer
static HyperionIManager* getInstance() { return HIMinstance; };
static HyperionIManager* HIMinstance;
///
/// @brief Is given instance running?
/// @param inst The instance to check
/// @return True when running else false
///
bool IsInstanceRunning(const quint8& inst) { return _runningInstances.contains(inst); };
///
/// @brief Get a Hyperion instance by index
/// @param intance the index
/// @return Hyperion instance, if index is not found returns instance 0
///
Hyperion* getHyperionInstance(const quint8& instance = 0);
///
/// @brief Get instance data of all instaces in db + running state
///
const QVector<QVariantMap> getInstanceData();
///
/// @brief Start a Hyperion instance
/// @param instance Instance index
/// @param block If true return when thread has been started
/// @return Return true on success, false if not found in db
///
bool startInstance(const quint8& inst, const bool& block = false);
///
/// @brief Stop a Hyperion instance
/// @param instance Instance index
/// @return Return true on success, false if not found in db
///
bool stopInstance(const quint8& inst);
///
/// @brief Create a new Hyperion instance entry in db
/// @param name The friendly name of the instance
/// @param start If true it will be started after creation (async)
/// @return Return true on success false if name is already in use or a db error occurred
///
bool createInstance(const QString& name, const bool& start = false);
///
/// @brief Delete Hyperion instance entry in db. Cleanup also all associated table data for this instance
/// @param inst The instance index
/// @return Return true on success, false if not found or not allowed
///
bool deleteInstance(const quint8& inst);
///
/// @brief Assign a new name to the given instance
/// @param inst The instance index
/// @param name The instance name index
/// @return Return true on success, false if not found
///
bool saveName(const quint8& inst, const QString& name);
signals:
///
/// @brief Emits whenever the state of a instance changes according to enum instanceState
/// @param instaneState A state from enum
/// @param instance The index of instance
/// @param name The name of the instance, just available with H_CREATED
///
void instanceStateChanged(const instanceState& state, const quint8& instance, const QString& name = QString());
///
/// @brief Emits whenever something changes, the lazy version of instanceStateChanged (- H_ON_STOP) + saveName() emit
///
void change();
signals:
///////////////////////////////////////
/// FROM HYPERIONDAEMON TO HYPERION ///
///////////////////////////////////////
///
/// @brief PIPE videoMode back to Hyperion
///
void newVideoMode(const VideoMode& mode);
///////////////////////////////////////
/// FROM HYPERION TO HYPERIONDAEMON ///
///////////////////////////////////////
///
/// @brief PIPE settings events from Hyperion
///
void settingsChanged(const settings::type& type, const QJsonDocument& data);
///
/// @brief PIPE videoMode request changes from Hyperion to HyperionDaemon
///
void requestVideoMode(const VideoMode& mode);
///
/// @brief PIPE component state changes from Hyperion to HyperionDaemon
///
void componentStateChanged(const hyperion::Components component, bool enable);
private slots:
///
/// @brief handle started signal of Hyperion instances
///
void handleStarted();
///
/// @brief handle finished signal of Hyperion instances
///
void handleFinished();
private:
friend class HyperionDaemon;
///
/// @brief Construct the Manager
/// @param The root path of all userdata
///
HyperionIManager(const QString& rootPath, QObject* parent = nullptr);
///
/// @brief Start all instances that are marked as enabled in db. Non blocking
///
void startAll();
///
/// @brief Stop all instances, used from hyperiond
///
void stopAll();
///
/// @brief check if a instance is allowed for management. Instance 0 represents the root instance
/// @apram inst The instance to check
///
bool isInstAllowed(const quint8& inst) { return (inst > 0); };
private:
Logger* _log;
InstanceTable* _instanceTable;
const QString _rootPath;
QMap<quint8, Hyperion*> _runningInstances;
QList<quint8> _startQueue;
};

View File

@ -182,6 +182,11 @@ public:
///
void clearAll(bool forceClearAll=false);
///
/// @brief Queue a manual push where muxer doesn't recognize them (e.g. continous single color pushes)
///
void queuePush(void){ emit timeRunner(); };
signals:
///
/// @brief Signal which emits when a effect or color with timeout > -1 is running, once per second

View File

@ -7,6 +7,7 @@
#include <QJsonObject>
class Hyperion;
class SettingsTable;
///
/// @brief Manage the settings read write from/to config file, on settings changed will emit a signal to update components accordingly
@ -17,43 +18,37 @@ class SettingsManager : public QObject
public:
///
/// @brief Construct a settings manager and assign a hyperion instance
/// @params hyperion The parent hyperion instance
/// @params instance Instance number of Hyperion
/// @params instance Instance index of HyperionInstanceManager
/// @params parent The parent hyperion instance
///
SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile);
///
/// @brief Construct a settings manager for HyperionDaemon
///
SettingsManager(const quint8& instance, const QString& configFile);
~SettingsManager();
SettingsManager(const quint8& instance, QObject* parent = nullptr);
///
/// @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
/// @return True on success else false
///
bool saveSettings(QJsonObject config, const bool& correct = false);
///
/// @brief get a single setting json from config
/// @param type The settings::type from enum
/// @return The requested json data as QJsonDocument
/// @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
/// @return The requested json
///
const QJsonObject & getSettings() { return _qconfig; };
signals:
///
/// @brief Emits whenever a config part changed.
/// @param type The settings type from enum
/// @param data The data as QJsonDocument
/// @param type The settings type from enum
/// @param data The data as QJsonDocument
///
void settingsChanged(const settings::type& type, const QJsonDocument& data);
@ -64,6 +59,9 @@ private:
/// Logger instance
Logger* _log;
/// instance of database table interface
SettingsTable* _sTable;
/// the schema
static QJsonObject schemaJson;

View File

@ -1,18 +1,18 @@
#pragma once
// qt includes
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QTimer>
// STL incldues
// STL includes
#include <vector>
#include <map>
#include <algorithm>
#include <QTimer>
// Utility includes
#include <utils/ColorRgb.h>
#include <utils/ColorRgbw.h>

View File

@ -9,6 +9,7 @@
class QTcpServer;
class ProtoClientConnection;
class NetOrigin;
///
/// @brief This class creates a TCP server which accepts connections wich can then send
@ -58,6 +59,7 @@ private:
private:
QTcpServer* _server;
NetOrigin* _netOrigin;
Logger* _log;
int _timeout;
quint16 _port;

View File

@ -1,109 +0,0 @@
#pragma once
// system includes
#include <cstdint>
// Qt includes
#include <QSet>
#include <QHostAddress>
#include <QJsonDocument>
// Hyperion includes
#include <utils/Logger.h>
#include <utils/Components.h>
#include <utils/ColorRgb.h>
// settings
#include <utils/settings.h>
class BonjourServiceRegister;
class QUdpSocket;
///
/// This class creates a UDP server which accepts connections from boblight clients.
///
class UDPListener : public QObject
{
Q_OBJECT
public:
///
/// UDPListener constructor
/// @param hyperion Hyperion instance
/// @param port port number on which to start listening for connections
///
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; };
public slots:
///
/// bind server to network
///
void start();
///
/// close server
///
void stop();
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:
///
/// @brief forward register data to HyperionDaemon
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief forward led data to HyperionDaemon
///
const bool setGlobalInput(const int priority, const std::vector<ColorRgb>& ledColors, const int timeout_ms = -1, const bool& clearEffect = true);
private slots:
///
/// Slot which is called when a client tries to create a new connection
///
void readPendingDatagrams();
void processTheDatagram(const QByteArray * datagram, const QHostAddress * sender);
private:
/// The UDP server object
QUdpSocket * _server;
/// hyperion priority
int _priority;
/// hyperion timeout
int _timeout;
/// Logger instance
Logger * _log;
/// Bonjour Service Register
BonjourServiceRegister* _serviceRegister = nullptr;
/// state of connection
bool _isActive;
/// address to bind
QHostAddress _listenAddress;
uint16_t _listenPort;
QAbstractSocket::BindFlag _bondage;
};

View File

@ -14,7 +14,6 @@ enum Components
COMP_SMOOTHING,
COMP_BLACKBORDER,
COMP_FORWARDER,
COMP_UDPLISTENER,
COMP_BOBLIGHTSERVER,
COMP_GRABBER,
COMP_V4L,
@ -34,7 +33,6 @@ inline const char* componentToString(Components c)
case COMP_SMOOTHING: return "Smoothing";
case COMP_BLACKBORDER: return "Blackborder detector";
case COMP_FORWARDER: return "Json/Proto forwarder";
case COMP_UDPLISTENER: return "UDP listener";
case COMP_BOBLIGHTSERVER:return "Boblight server";
case COMP_GRABBER: return "Framegrabber";
case COMP_V4L: return "V4L capture device";
@ -56,7 +54,6 @@ inline const char* componentToIdString(Components c)
case COMP_SMOOTHING: return "SMOOTHING";
case COMP_BLACKBORDER: return "BLACKBORDER";
case COMP_FORWARDER: return "FORWARDER";
case COMP_UDPLISTENER: return "UDPLISTENER";
case COMP_BOBLIGHTSERVER:return "BOBLIGHTSERVER";
case COMP_GRABBER: return "GRABBER";
case COMP_V4L: return "V4L";
@ -77,7 +74,6 @@ inline Components stringToComponent(QString component)
if (component == "SMOOTHING") return COMP_SMOOTHING;
if (component == "BLACKBORDER") return COMP_BLACKBORDER;
if (component == "FORWARDER") return COMP_FORWARDER;
if (component == "UDPLISTENER") return COMP_UDPLISTENER;
if (component == "BOBLIGHTSERVER")return COMP_BOBLIGHTSERVER;
if (component == "GRABBER") return COMP_GRABBER;
if (component == "V4L") return COMP_V4L;

View File

@ -55,12 +55,6 @@ QString getDirName( QString sourceFile);
///
bool removeFile(const QString& path, Logger* log, bool ignError=false);
///
/// @brief Convert a path that may contain special placeholders
/// @param[in] path The path to convert
///
QString convertPath(const QString path);
///
/// @brief resolve the file error and print a message
/// @param[in] file The file which caused the error

View File

@ -3,6 +3,7 @@
// util
#include <utils/Image.h>
#include <utils/ColorRgb.h>
#include <utils/Components.h>
// qt
#include <QObject>
@ -27,6 +28,10 @@ public:
void operator=(GlobalSignals const&) = delete;
signals:
///////////////////////////////////////
///////////// TO HYPERION /////////////
///////////////////////////////////////
///
/// @brief PIPE SystemCapture images from GrabberWrapper to Hyperion class
/// @param name The name of the platform capture that is currently active
@ -39,5 +44,56 @@ signals:
/// @param name The name of the v4l capture (path) that is currently active
/// @param image The prepared image
///
void setV4lImage(const QString& name, const Image<ColorRgb> & image);
void setV4lImage(const QString& name, const Image<ColorRgb>& image);
///
/// @brief PIPE the register command for a new global input over HyperionDaemon to Hyperion class
/// @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 Specific owner string, might be empty
/// @param[in] smooth_cfg The smooth id to use
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "External", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief PIPE the clear command for the global priority channel over HyperionDaemon to Hyperion class
/// @param[in] priority The priority channel
///
void clearGlobalInput(int priority);
///
/// @brief PIPE the clearAll command over HyperionDaemon to Hyperion class
///
void clearAllGlobalInput(bool forceClearAll=false);
///
/// @brief PIPE external images over HyperionDaemon to Hyperion class
/// @param[in] priority The priority of the channel
/// @param image The prepared image
/// @param[in] timeout_ms The timeout in milliseconds
/// @param clearEffect Should be true when NOT called from an effect
///
void setGlobalImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms, const bool& clearEffect = true);
///
/// @brief PIPE external color message over HyperionDaemon to Hyperion class
/// @param[in] priority The priority of the channel
/// @param image The prepared color
/// @param[in] timeout_ms The timeout in milliseconds
/// @param[in] origin The setter
/// @param clearEffect Should be true when NOT called from an effect
///
void setGlobalColor(const int priority, const ColorRgb &ledColor, const int timeout_ms, const QString& origin = "External" ,bool clearEffects = true);
///////////////////////////////////////
//////////// FROM HYPERION ////////////
///////////////////////////////////////
///
/// @brief PIPE a registration request from the Hyperion class to the priority channel
/// @param[in] priority The priority channel
///
void globalRegRequired(int priority);
};

54
include/utils/NetOrigin.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
// qt
#include <QHostAddress>
#include <QJsonArray>
// utils
#include <utils/Logger.h>
#include <utils/settings.h>
///
/// @brief Checks the origin ip addresses for access allowed
///
class NetOrigin : public QObject
{
Q_OBJECT
private:
friend class HyperionDaemon;
NetOrigin(QObject* parent = 0, Logger* log = Logger::getInstance("NETWORK"));
public:
///
/// @brief Check if address is allowed to connect. The local address is the network adapter ip this connection comes from
/// @param address The peer address
/// @param local The local address of the socket (Differs based on NetworkAdapter IP or localhost)
/// @return True when allowed, else false
///
bool accessAllowed(const QHostAddress& address, const QHostAddress& local);
///
/// @brief Check if address is in subnet of local
/// @return True or false
///
bool isLocalAddress(const QHostAddress& address, const QHostAddress& local);
static NetOrigin* getInstance(){ return instance; };
static NetOrigin* instance;
private slots:
///
/// @brief Handle settings update from SettingsManager
/// @param type settingyType from enum
/// @param config configuration object
///
void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config);
private:
Logger* _log;
/// True when internet access is allowed
bool _internetAccessAllowed;
/// Whitelisted ip addresses
QList<QHostAddress> _ipWhitelist;
};

View File

@ -24,7 +24,6 @@ enum type {
LEDS,
LOGGER,
SMOOTHING,
UDPLISTENER,
WEBSERVER,
INSTCAPTURE,
NETWORK,
@ -58,7 +57,6 @@ inline QString typeToString(const type& type)
case LEDS: return "leds";
case LOGGER: return "logger";
case SMOOTHING: return "smoothing";
case UDPLISTENER: return "udpListener";
case WEBSERVER: return "webConfig";
case INSTCAPTURE: return "instCapture";
case NETWORK: return "network";
@ -91,7 +89,6 @@ inline type stringToType(const QString& type)
else if (type == "leds") return LEDS;
else if (type == "logger") return LOGGER;
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;

View File

@ -12,11 +12,11 @@ add_subdirectory(protoserver)
add_subdirectory(bonjour)
add_subdirectory(ssdp)
add_subdirectory(boblightserver)
add_subdirectory(udplistener)
add_subdirectory(leddevice)
add_subdirectory(utils)
add_subdirectory(effectengine)
add_subdirectory(grabber)
add_subdirectory(webserver)
add_subdirectory(db)
add_subdirectory(api)
add_subdirectory(python)

View File

@ -0,0 +1,44 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["authorize"]
},
"subcommand" : {
"type" : "string",
"required" : true,
"enum" : ["requestToken","createToken","deleteToken","getTokenList","logout","login","required","answerRequest","getPendingRequests"]
},
"tan" : {
"type" : "integer"
},
"username": {
"type": "string",
"minLength" : 3
},
"password": {
"type": "string",
"minLength" : 8
},
"token": {
"type": "string",
"minLength" : 36
},
"comment" : {
"type" : "string",
"minLength" : 5
},
"id" : {
"type" : "string",
"minLength" : 5,
"maxLength" : 5
},
"accept" : {
"type" : "boolean"
}
},
"additionalProperties": false
}

View File

@ -21,7 +21,7 @@
"component":
{
"type" : "string",
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "UDPLISTENER", "BOBLIGHTSERVER", "GRABBER", "V4L", "LEDDEVICE"],
"enum" : ["ALL", "SMOOTHING", "BLACKBORDER", "FORWARDER", "BOBLIGHTSERVER", "GRABBER", "V4L", "LEDDEVICE"],
"required": true
},
"state":

View File

@ -0,0 +1,29 @@
{
"type":"object",
"required":true,
"properties":{
"command": {
"type" : "string",
"required" : true,
"enum" : ["instance"]
},
"subcommand" : {
"type" : "string",
"required" : true,
"enum" : ["createInstance","deleteInstance","startInstance","stopInstance","saveName","switchTo"]
},
"tan" : {
"type" : "integer"
},
"instance" : {
"type" : "integer",
"minimum" : 0,
"maximum" : 255
},
"name": {
"type": "string",
"minLength" : 5
}
},
"additionalProperties": false
}

View File

@ -5,7 +5,7 @@
"command": {
"type" : "string",
"required" : true,
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"]
"enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "transform", "correction" , "temperature"]
}
}
}

View File

@ -18,6 +18,8 @@
<file alias="schema-logging">JSONRPC_schema/schema-logging.json</file>
<file alias="schema-processing">JSONRPC_schema/schema-processing.json</file>
<file alias="schema-videomode">JSONRPC_schema/schema-videomode.json</file>
<file alias="schema-authorize">JSONRPC_schema/schema-authorize.json</file>
<file alias="schema-instance">JSONRPC_schema/schema-instance.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

View File

@ -35,29 +35,94 @@
// api includes
#include <api/JsonCB.h>
// auth manager
#include <hyperion/AuthManager.h>
using namespace hyperion;
JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener)
JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener)
: QObject(parent)
, _authManager(AuthManager::getInstance())
, _authorized(false)
, _userAuthorized(false)
, _apiAuthRequired(_authManager->isAuthRequired())
, _noListener(noListener)
, _peerAddress(peerAddress)
, _log(log)
, _hyperion(Hyperion::getInstance())
, _jsonCB(new JsonCB(this))
, _instanceManager(HyperionIManager::getInstance())
, _hyperion(nullptr)
, _jsonCB(nullptr)
, _streaming_logging_activated(false)
, _image_stream_timeout(0)
, _led_stream_timeout(0)
{
Q_INIT_RESOURCE(JSONRPC_schemas);
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// if this is localConnection and network allows unauth locals, set authorized flag
if(_apiAuthRequired && localConnection)
_authorized = !_authManager->isLocalAuthRequired();
// setup auth interface
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
// init Hyperion pointer
handleInstanceSwitch(0);
// notify hyperion about a jsonMessageForward
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
}
void JsonAPI::handleMessage(const QString& messageString)
bool JsonAPI::handleInstanceSwitch(const quint8& inst, const bool& forced)
{
// check if we are already on the requested instance
if(_hyperion != nullptr && _hyperion->getInstanceIndex() == inst)
return true;
if(_instanceManager->IsInstanceRunning(inst))
{
Debug(_log,"Client '%s' switch to Hyperion instance %d", QSTRING_CSTR(_peerAddress), inst);
// cut all connections between hyperion / plugins and this
if(_hyperion != nullptr)
disconnect(_hyperion, 0, this, 0);
// get new Hyperion pointer
_hyperion = _instanceManager->getHyperionInstance(inst);
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
QStringList cbCmds;
if(_jsonCB != nullptr)
{
cbCmds = _jsonCB->getSubscribedCommands();
delete _jsonCB;
}
_jsonCB = new JsonCB(_hyperion, this);
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// read subs
for(const auto & entry : cbCmds)
{
_jsonCB->subscribeFor(entry);
}
// // imageStream last state
// if(_ledcolorsImageActive)
// connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
//
// //ledColor stream last state
// if(_ledcolorsLedsActive)
// connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
return true;
}
return false;
}
void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader)
{
const QString ident = "JsonRpc@"+_peerAddress;
QJsonObject message;
@ -84,6 +149,29 @@ void JsonAPI::handleMessage(const QString& messageString)
}
int tan = message["tan"].toInt();
// client auth before everything else but not for http
if (!_noListener && command == "authorize")
{
handleAuthorizeCommand(message, command, tan);
return;
}
// on the fly auth available for http from http Auth header, on failure we return and auth handler sends a failure
if(_noListener && _apiAuthRequired && !_authorized)
{
// extract token from http header
QString cToken = httpAuthHeader.mid(5).trimmed();
if(!handleHTTPAuth(command, tan, cToken))
return;
}
// on strong api auth you need a auth for all cmds
if(_apiAuthRequired && !_authorized)
{
sendErrorReply("No Authorization", command, tan);
return;
}
// switch over all possible commands and handle them
if (command == "color") handleColorCommand (message, command, tan);
@ -102,15 +190,17 @@ void JsonAPI::handleMessage(const QString& messageString)
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 if (command == "instance") handleInstanceCommand (message, command, tan);
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall") handleClearallCommand(message, command, tan);
else if (command == "clearall")
handleClearallCommand(message, command, tan);
else if (command == "transform" || command == "correction" || command == "temperature")
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
// END
// handle not implemented commands
else handleNotImplemented ();
else handleNotImplemented();
}
void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan)
@ -120,35 +210,13 @@ void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& comm
// extract parameters
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
const QString origin = message["origin"].toString("Empty") + "@"+_peerAddress;
std::vector<ColorRgb> 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());
}
const ColorRgb color = {uint8_t(jsonColor.at(0).toInt()),uint8_t(jsonColor.at(1).toInt()),uint8_t(jsonColor.at(2).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));
}
// register and set color
_hyperion->registerInput(priority, hyperion::COMP_COLOR, origin);
_hyperion->setInput(priority, colorData, duration);
// set color
_hyperion->setColor(priority, color, duration, origin);
// send reply
sendSuccessReply(command, tan);
@ -251,7 +319,7 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, c
hyperion["channel" ] = QString(HYPERION_VERSION_CHANNEL);
hyperion["build" ] = QString(HYPERION_BUILD_ID);
hyperion["time" ] = QString(__DATE__ " " __TIME__);
hyperion["id" ] = _hyperion->getId();
hyperion["id" ] = _authManager->getID();
info["hyperion"] = hyperion;
// send the result
@ -446,14 +514,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
info["components"] = component;
info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
// Add Hyperion
QJsonObject hyperion;
hyperion["config_modified" ] = _hyperion->configModified();
hyperion["config_writeable"] = _hyperion->configWriteable();
hyperion["enabled"] = _hyperion->getComponentRegister().isComponentEnabled(hyperion::COMP_ALL) ? true : false;
info["hyperion"] = hyperion;
// add sessions
QJsonArray sessions;
for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices())
@ -470,6 +530,22 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
}
info["sessions"] = sessions;
// add instance info
QJsonArray instanceInfo;
for(const auto & entry : _instanceManager->getInstanceData())
{
QJsonObject obj;
obj.insert("friendly_name", entry["friendly_name"].toString());
obj.insert("instance", entry["instance"].toInt());
//obj.insert("last_use", entry["last_use"].toString());
obj.insert("running", entry["running"].toBool());
instanceInfo.append(obj);
}
info["instance"] = instanceInfo;
// add leds configs
info["leds"] = _hyperion->getSetting(settings::LEDS).array();
// BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control
// TODO Output the real transformation information instead of default
@ -588,7 +664,8 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
}
for(const auto & entry : subsArr)
{
if(entry == "settings-update")
// config callbacks just if auth is set
if(entry == "settings-update" && !_authorized)
continue;
if(!_jsonCB->subscribeFor(entry.toString()))
@ -617,6 +694,17 @@ void JsonAPI::handleClearCommand(const QJsonObject& message, const QString& comm
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// clear priority
_hyperion->clearall();
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan)
{
const QJsonObject & adjustment = message["adjustment"].toObject();
@ -848,8 +936,6 @@ void JsonAPI::handleComponentStateCommand(const QJsonObject& message, const QStr
{
if(_hyperion->getComponentRegister().setHyperionEnable(compState))
sendSuccessReply(command, tan);
else
sendErrorReply(QString("Hyperion is already %1").arg(compState ? "enabled" : "disabled"), command, tan );
return;
}
@ -885,6 +971,7 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &
_streaming_image_reply["command"] = command+"-imagestream-update";
_streaming_image_reply["tan"] = tan;
connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
_hyperion->update();
}
else if (subcommand == "imagestream-stop")
{
@ -892,7 +979,6 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &
}
else
{
sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan);
return;
}
@ -928,7 +1014,6 @@ void JsonAPI::handleLoggingCommand(const QJsonObject& message, const QString &co
}
else
{
sendErrorReply("unknown subcommand",command,tan);
return;
}
@ -949,15 +1034,282 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
{
emit forwardJsonMessage(message);
const QString& subc = message["subcommand"].toString().trimmed();
// catch test if auth is required
if(subc == "required")
{
QJsonObject req;
req["required"] = _apiAuthRequired;
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
return;
}
// clear priority
_hyperion->clearall();
// catch logout
if(subc == "logout")
{
_authorized = false;
_userAuthorized = false;
sendSuccessReply(command+"-"+subc, tan);
return;
}
// send reply
sendSuccessReply(command, tan);
// token created from ui
if(subc == "createToken")
{
const QString& c = message["comment"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
AuthManager::AuthDefinition def = _authManager->createToken(c);
QJsonObject newTok;
newTok["comment"] = def.comment;
newTok["id"] = def.id;
newTok["token"] = def.token;
sendSuccessDataReply(QJsonDocument(newTok), command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// delete token
if(subc == "deleteToken")
{
const QString& did = message["id"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
_authManager->deleteToken(did);
sendSuccessReply(command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// catch token request
if(subc == "requestToken")
{
const QString& comment = message["comment"].toString().trimmed();
const QString& id = message["id"].toString().trimmed();
_authManager->setNewTokenRequest(this, comment, id);
// client should wait for answer
return;
}
// get pending token requests
if(subc == "getPendingRequests")
{
if(_userAuthorized)
{
QMap<QString, AuthManager::AuthDefinition> map = _authManager->getPendingRequests();
QJsonArray arr;
for(const auto& entry : map)
{
QJsonObject obj;
obj["comment"] = entry.comment;
obj["id"] = entry.id;
obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch());
arr.append(obj);
}
sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// accept/deny token request
if(subc == "answerRequest")
{
const QString& id = message["id"].toString().trimmed();
const bool& accept = message["accept"].toBool(false);
if(_userAuthorized)
{
if(accept)
_authManager->acceptTokenRequest(id);
else
_authManager->denyTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// deny token request
if(subc == "acceptRequest")
{
const QString& id = message["id"].toString().trimmed();
if(_userAuthorized)
{
_authManager->acceptTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// cath get token list
if(subc == "getTokenList")
{
if(_userAuthorized)
{
QVector<AuthManager::AuthDefinition> defVect = _authManager->getTokenList();
QJsonArray tArr;
for(const auto& entry : defVect)
{
QJsonObject subO;
subO["comment"] = entry.comment;
subO["id"] = entry.id;
subO["last_use"] = entry.lastUse;
tArr.append(subO);
}
sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// login
if(subc == "login")
{
// catch token auth
const QString& token = message["token"].toString().trimmed();
if(!token.isEmpty())
{
if(token.count() >= 36)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("Token is too short", command+"-"+subc, tan);
return;
}
// user & password
const QString& user = message["username"].toString().trimmed();
const QString& password = message["password"].toString().trimmed();
if(user.count() >= 3 && password.count() >= 8)
{
if(_authManager->isUserAuthorized(user, password))
{
_authorized = true;
_userAuthorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("User or password string too short", command+"-"+subc, tan);
}
}
bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
return true;
}
sendErrorReply("No Authorization", command, tan);
return false;
}
void JsonAPI::handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan)
{
const QString & subc = message["subcommand"].toString();
const quint8 & inst = message["instance"].toInt();
const QString & name = message["name"].toString();
if(subc == "switchTo")
{
if(handleInstanceSwitch(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply("Selected Hyperion instance isn't running",command+"-"+subc, tan);
return;
}
if(subc == "startInstance")
{
// silent fail
_instanceManager->startInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "stopInstance")
{
// silent fail
_instanceManager->stopInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "deleteInstance")
{
if(_userAuthorized)
{
if(_instanceManager->deleteInstance(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("Failed to delete instance '%1'").arg(inst), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// create and save name requires name
if(name.isEmpty())
sendErrorReply("Name string required for this command",command+"-"+subc, tan);
if(subc == "createInstance")
{
if(_userAuthorized)
{
if(_instanceManager->createInstance(name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
if(subc == "saveName")
{
if(_userAuthorized)
{
// silent fail
if(_instanceManager->saveName(inst,name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
}
void JsonAPI::handleNotImplemented()
@ -1093,3 +1445,49 @@ void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
// send the result
emit callbackMessage(_streaming_logging_reply);
}
void JsonAPI::handlePendingTokenRequest(const QString& id, const QString& comment)
{
// just user sessions are allowed to react on this, to prevent that token authorized instances authorize new tokens on their own
if(_userAuthorized)
{
QJsonObject obj;
obj["command"] = "authorize-event";
obj["comment"] = comment;
obj["id"] = id;
emit callbackMessage(obj);
}
}
void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id)
{
// if this is the requester, we send the reply
if(this == caller)
{
const QString cmd = "authorize-requestToken";
QJsonObject result;
result["token"] = token;
result["comment"] = comment;
result["id"] = id;
if(success)
sendSuccessDataReply(QJsonDocument(result), cmd);
else
sendErrorReply("Token request timeout or denied", cmd);
}
}
void JsonAPI::handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name)
{
switch(state){
case H_ON_STOP:
if(_hyperion->getInstanceIndex() == instance)
{
handleInstanceSwitch();
}
break;
default:
break;
}
}

View File

@ -3,13 +3,23 @@
// hyperion
#include <hyperion/Hyperion.h>
// HyperionIManager
#include <hyperion/HyperionIManager.h>
// components
#include <hyperion/ComponentRegister.h>
// bonjour wrapper
#include <bonjour/bonjourbrowserwrapper.h>
// priorityMuxer
#include <hyperion/PriorityMuxer.h>
// utils
#include <utils/ColorSys.h>
// qt
#include <QDateTime>
// Image to led map helper
@ -17,15 +27,15 @@
using namespace hyperion;
JsonCB::JsonCB(QObject* parent)
JsonCB::JsonCB(Hyperion* hyperion, QObject* parent)
: QObject(parent)
, _hyperion(Hyperion::getInstance())
, _hyperion(hyperion)
, _componentRegister(& _hyperion->getComponentRegister())
, _bonjour(BonjourBrowserWrapper::getInstance())
, _prioMuxer(_hyperion->getMuxerInstance())
{
_availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update"
<< "adjustment-update" << "videomode-update" << "effects-update" << "settings-update";
<< "adjustment-update" << "videomode-update" << "effects-update" << "settings-update" << "leds-update" << "instance-update";
}
bool JsonCB::subscribeFor(const QString& type)
@ -82,6 +92,19 @@ bool JsonCB::subscribeFor(const QString& type)
connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
}
if(type == "leds-update")
{
_subscribedCommands << type;
connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
}
if(type == "instance-update")
{
_subscribedCommands << type;
connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
}
return true;
}
@ -301,3 +324,29 @@ void JsonCB::handleSettingsChange(const settings::type& type, const QJsonDocumen
doCallback("settings-update", QVariant(dat));
}
void JsonCB::handleLedsConfigChange(const settings::type& type, const QJsonDocument& data)
{
if(type == settings::LEDS)
{
QJsonObject dat;
dat[typeToString(type)] = data.array();
doCallback("leds-update", QVariant(dat));
}
}
void JsonCB::handleInstanceChange()
{
QJsonArray arr;
for(const auto & entry : HyperionIManager::getInstance()->getInstanceData())
{
QJsonObject obj;
obj.insert("friendly_name", entry["friendly_name"].toString());
obj.insert("instance", entry["instance"].toInt());
//obj.insert("last_use", entry["last_use"].toString());
obj.insert("running", entry["running"].toBool());
arr.append(obj);
}
doCallback("instance-update", QVariant(arr));
}

View File

@ -44,12 +44,9 @@ BoblightClientConnection::BoblightClientConnection(Hyperion* hyperion, QTcpSocke
BoblightClientConnection::~BoblightClientConnection()
{
if (_priority < 255)
{
// clear the current channel
// clear the current channel
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->clear(_priority);
_priority = 255;
}
delete _socket;
}
@ -83,12 +80,9 @@ void BoblightClientConnection::readData()
void BoblightClientConnection::socketClosed()
{
if (_priority < 255)
{
// clear the current channel
// clear the current channel
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->clear(_priority);
_priority = 255;
}
emit connectionClosed(this);
}
@ -163,8 +157,11 @@ void BoblightClientConnection::handleMessage(const QString & message)
rgb.green = green;
rgb.blue = blue;
if (_priority == 0 || _priority < 128 || _priority >= 254)
return;
// 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)
if (ledIndex == _ledColors.size() -1)
{
_hyperion->setInput(_priority, _ledColors);
}
@ -188,27 +185,38 @@ void BoblightClientConnection::handleMessage(const QString & message)
int prio = messageParts[2].toInt(&rc);
if (rc && prio != _priority)
{
if (_priority < 255)
{
// clear the current channel
if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER)
_hyperion->clear(_priority);
if (prio < 128 || prio >= 254)
{
_priority = 128;
while (_hyperion->getActivePriorities().contains(_priority))
{
_priority += 1;
}
// warn against invalid priority
Warning(_log, "The priority %i is not in the priority range between 128 and 253. Priority %i is used instead.", prio, _priority);
// register new priority (previously modified)
_hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
}
else
{
// register new priority
_hyperion->registerInput(prio, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(_socket->peerAddress().toString()));
_priority = prio;
}
_priority = prio;
return;
}
}
}
else if (messageParts[0] == "sync")
{
// send current color values to hyperion
if (_priority < 255)
{
_hyperion->setInput(_priority, _ledColors);
}
if (_priority != 0 && _priority >= 128 && _priority < 254)
_hyperion->setInput(_priority, _ledColors); // send current color values to hyperion
return;
}
}
@ -216,12 +224,6 @@ void BoblightClientConnection::handleMessage(const QString & message)
Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
}
void BoblightClientConnection::sendMessage(const QByteArray & message)
{
//std::cout << "send boblight message: " << message;
_socket->write(message);
}
void BoblightClientConnection::sendLightMessage()
{
char buffer[256];

View File

@ -63,7 +63,7 @@ private:
///
/// @param message The boblight message to send
///
void sendMessage(const QByteArray &message);
void sendMessage(const QByteArray &message) { _socket->write(message); };
///
/// Send a lights message the to connected client

View File

@ -7,9 +7,13 @@
// hyperion includes
#include <hyperion/Hyperion.h>
// qt incl
#include <QTcpServer>
// netUtil
#include <utils/NetUtils.h>
using namespace hyperion;
BoblightServer::BoblightServer(Hyperion* hyperion,const QJsonDocument& config)
@ -42,11 +46,9 @@ void BoblightServer::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;
}
if (NetUtils::portAvailable(_port, _log))
_server->listen(QHostAddress::Any, _port);
Info(_log, "Started on port %d", _port);
_hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening());

View File

@ -34,7 +34,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <utils/Logger.h>
#include <HyperionConfig.h>
#include <hyperion/Hyperion.h>
#include <hyperion/AuthManager.h>
BonjourServiceRegister::BonjourServiceRegister(QObject *parent)
: QObject(parent), dnssref(0), bonjourSocket(0)
@ -79,7 +79,7 @@ void BonjourServiceRegister::registerService(const BonjourRecord &record, quint1
}
#endif
// base txtRec
std::vector<std::pair<std::string, std::string> > txtBase = {{"id",Hyperion::getInstance()->getId().toStdString()},{"version",HYPERION_VERSION}};
std::vector<std::pair<std::string, std::string> > txtBase = {{"id",AuthManager::getInstance()->getID().toStdString()},{"version",HYPERION_VERSION}};
// create txt record
TXTRecordRef txtRec;
TXTRecordCreate(&txtRec,0,NULL);

18
libsrc/db/CMakeLists.txt Normal file
View File

@ -0,0 +1,18 @@
find_package(Qt5 COMPONENTS Sql REQUIRED)
# Define the current source locations
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/db)
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/db)
FILE ( GLOB DB_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
add_library(database
${DB_SOURCES}
)
target_link_libraries(database
hyperion
hyperion-utils
Qt5::Core
Qt5::Sql
)

396
libsrc/db/DBManager.cpp Normal file
View File

@ -0,0 +1,396 @@
#include <db/DBManager.h>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QThreadStorage>
#include <QUuid>
#include <QDir>
// not in header because of linking
static QString _rootPath;
static QThreadStorage<QSqlDatabase> _databasePool;
DBManager::DBManager(QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("DB"))
{
}
DBManager::~DBManager()
{
}
void DBManager::setRootPath(const QString& rootPath)
{
_rootPath = rootPath;
// create directory
QDir().mkpath(_rootPath+"/db");
}
void DBManager::setTable(const QString& table)
{
_table = table;
}
QSqlDatabase DBManager::getDB() const
{
if(_databasePool.hasLocalData())
return _databasePool.localData();
else
{
auto db = QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString());
_databasePool.setLocalData(db);
db.setDatabaseName(_rootPath+"/db/"+_dbn+".db");
if(!db.open())
{
Error(_log, QSTRING_CSTR(db.lastError().text()));
throw std::runtime_error("Failed to open database connection!");
}
return db;
}
}
bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const
{
if(recordExists(conditions))
{
// if there is no column data, return
if(columns.isEmpty())
return true;
if(!updateRecord(conditions, columns))
return false;
return true;
}
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QVariantList cValues;
QStringList prep;
QStringList placeh;
// prep merge columns & condition
QVariantMap::const_iterator i = columns.constBegin();
while (i != columns.constEnd()) {
prep.append(i.key());
cValues += i.value();
placeh.append("?");
++i;
}
for(const auto& pair : conditions)
{
// remove the condition statements
QString tmp = pair.first;
prep << tmp.remove("AND");
cValues << pair.second;
placeh.append("?");
}
query.prepare(QString("INSERT INTO %1 ( %2 ) VALUES ( %3 )").arg(_table,prep.join(", ")).arg(placeh.join(", ")));
// add column & condition values
doAddBindValue(query, cValues);
if(!query.exec())
{
Error(_log, "Failed to create record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prep.join(", ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::recordExists(const VectorPair& conditions) const
{
if(conditions.isEmpty())
return false;
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QStringList prepCond;
QVariantList bindVal;
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindVal << pair.second;
}
query.prepare(QString("SELECT * FROM %1 %2").arg(_table,prepCond.join(" ")));
doAddBindValue(query, bindVal);
if(!query.exec())
{
Error(_log, "Failed recordExists(): '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
int entry = 0;
while (query.next()) {
entry++;
}
if(entry)
return true;
return false;
}
bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QVariantList values;
QStringList prep;
// prepare columns valus
QVariantMap::const_iterator i = columns.constBegin();
while (i != columns.constEnd()) {
prep += i.key()+"=?";
values += i.value();
++i;
}
// prepare condition values
QStringList prepCond;
QVariantList prepBindVal;
if(!conditions.isEmpty())
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
prepBindVal << pair.second;
}
query.prepare(QString("UPDATE %1 SET %2 %3").arg(_table,prep.join(", ")).arg(prepCond.join(" ")));
// add column values
doAddBindValue(query, values);
// add condition values
doAddBindValue(query, prepBindVal);
if(!query.exec())
{
Error(_log, "Failed to update record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::getRecord(const VectorPair& conditions, QVariantMap& results, const QStringList& tColumns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QString sColumns("*");
if(!tColumns.isEmpty())
sColumns = tColumns.join(", ");
// prep conditions
QStringList prepCond;
QVariantList bindVal;
if(!conditions.isEmpty())
prepCond << "WHERE";
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindVal << pair.second;
}
query.prepare(QString("SELECT %1 FROM %2 %3").arg(sColumns,_table).arg(prepCond.join(" ")));
doAddBindValue(query, bindVal);
if(!query.exec())
{
Error(_log, "Failed to get record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
// go to first row
query.next();
QSqlRecord rec = query.record();
for(int i = 0; i<rec.count(); i++)
{
results[rec.fieldName(i)] = rec.value(i);
}
return true;
}
bool DBManager::getRecords(QVector<QVariantMap>& results, const QStringList& tColumns) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
query.setForwardOnly(true);
QString sColumns("*");
if(!tColumns.isEmpty())
sColumns = tColumns.join(", ");
query.prepare(QString("SELECT %1 FROM %2").arg(sColumns,_table));
if(!query.exec())
{
Error(_log, "Failed to get records: '%s' in table: '%s' Error: %s", QSTRING_CSTR(sColumns), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
// iterate through all found records
while(query.next())
{
QVariantMap entry;
QSqlRecord rec = query.record();
for(int i = 0; i<rec.count(); i++)
{
entry[rec.fieldName(i)] = rec.value(i);
}
results.append(entry);
}
return true;
}
bool DBManager::deleteRecord(const VectorPair& conditions) const
{
if(conditions.isEmpty())
{
Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table));
return false;
}
if(recordExists(conditions))
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
// prep conditions
QStringList prepCond("WHERE");
QVariantList bindValues;
for(const auto& pair : conditions)
{
prepCond << pair.first+"=?";
bindValues << pair.second;
}
query.prepare(QString("DELETE FROM %1 %2").arg(_table,prepCond.join(" ")));
doAddBindValue(query, bindValues);
if(!query.exec())
{
Error(_log, "Failed to delete record: '%s' in table: '%s' Error: %s", QSTRING_CSTR(prepCond.join(" ")), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
return false;
}
bool DBManager::createTable(QStringList& columns) const
{
if(columns.isEmpty())
{
Error(_log,"Empty tables aren't supported!");
return false;
}
QSqlDatabase idb = getDB();
// create table if required
QSqlQuery query(idb);
if(!tableExists(_table))
{
// empty tables aren't supported by sqlite, add one column
QString tcolumn = columns.takeFirst();
// default CURRENT_TIMESTAMP is not supported by ALTER TABLE
if(!query.exec(QString("CREATE TABLE %1 ( %2 )").arg(_table,tcolumn)))
{
Error(_log, "Failed to create table: '%s' Error: %s", QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
}
// create columns if required
QSqlRecord rec = idb.record(_table);
int err = 0;
for(const auto& column : columns)
{
QStringList id = column.split(' ');
if(rec.indexOf(id.at(0)) == -1)
{
if(!createColumn(column))
{
err++;
}
}
}
if(err)
return false;
return true;
}
bool DBManager::createColumn(const QString& column) const
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column)))
{
Error(_log, "Failed to create column: '%s' in table: '%s' Error: %s", QSTRING_CSTR(column), QSTRING_CSTR(_table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
return true;
}
bool DBManager::tableExists(const QString& table) const
{
QSqlDatabase idb = getDB();
QStringList tables = idb.tables();
if(tables.contains(table))
return true;
return false;
}
bool DBManager::deleteTable(const QString& table) const
{
if(tableExists(table))
{
QSqlDatabase idb = getDB();
QSqlQuery query(idb);
if(!query.exec(QString("DROP TABLE %1").arg(table)))
{
Error(_log, "Failed to delete table: '%s' Error: %s", QSTRING_CSTR(table), QSTRING_CSTR(idb.lastError().text()));
return false;
}
}
return true;
}
void DBManager::doAddBindValue(QSqlQuery& query, const QVariantList& variants) const
{
for(const auto& variant : variants)
{
QVariant::Type t = variant.type();
switch(t)
{
case QVariant::UInt:
case QVariant::Int:
case QVariant::Bool:
query.addBindValue(variant.toInt());
break;
case QVariant::Double:
query.addBindValue(variant.toFloat());
break;
case QVariant::ByteArray:
query.addBindValue(variant.toByteArray());
break;
default:
query.addBindValue(variant.toString());
break;
}
}
}

View File

@ -79,7 +79,7 @@ void Effect::run()
PyObject * module = PyImport_ImportModule("hyperion");
// add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function
PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr));
PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr));
// add ledCount variable to the interpreter
PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _hyperion->getLedCount()));

View File

@ -168,10 +168,11 @@ int EffectEngine::runEffectScript(const QString &script, const QString &name, co
channelCleared(priority);
// create the effect
Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData);
Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData);
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);
connect(_hyperion, &Hyperion::finished, effect, &Effect::requestInterruption, Qt::DirectConnection);
_activeEffects.push_back(effect);
// start the effect
@ -185,9 +186,9 @@ void EffectEngine::channelCleared(int priority)
{
for (Effect * effect : _activeEffects)
{
if (effect->getPriority() == priority)
if (effect->getPriority() == priority && !effect->isInterruptionRequested())
{
effect->setInteruptionFlag();
effect->requestInterruption();
}
}
}
@ -196,9 +197,9 @@ void EffectEngine::allChannelsCleared()
{
for (Effect * effect : _activeEffects)
{
if (effect->getPriority() != 254)
if (effect->getPriority() != 254 && !effect->isInterruptionRequested())
{
effect->setInteruptionFlag();
effect->requestInterruption();
}
}
}
@ -206,7 +207,7 @@ void EffectEngine::allChannelsCleared()
void EffectEngine::effectFinished()
{
Effect* effect = qobject_cast<Effect*>(sender());
if (!effect->hasInteruptionFlag())
if (!effect->isInterruptionRequested())
{
// effect stopped by itself. Clear the channel
_hyperion->clear(effect->getPriority());

View File

@ -13,6 +13,9 @@
#include <QImageReader>
#include <QBuffer>
// Get the effect from the capsule
#define getEffect() static_cast<Effect*>((Effect*)PyCapsule_Import("hyperion.__effectObj", 0))
// create the hyperion module
struct PyModuleDef EffectModule::moduleDef = {
PyModuleDef_HEAD_INIT,
@ -41,9 +44,9 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
switch (jsonData.type())
{
case QJsonValue::Null:
return Py_BuildValue("");
Py_RETURN_NONE;
case QJsonValue::Undefined:
return Py_BuildValue("");
Py_RETURN_NOTIMPLEMENTED;
case QJsonValue::Double:
{
if (std::round(jsonData.toDouble()) != jsonData.toDouble())
@ -84,7 +87,7 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
}
assert(false);
return nullptr;
Py_RETURN_NONE;
}
// Python method table
@ -118,26 +121,17 @@ PyMethodDef EffectModule::effectMethods[] = {
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("");
}
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
// check the number of arguments
@ -148,9 +142,9 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
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("");
getEffect()->_colors.fill(color);
getEffect()->setInput(getEffect()->_priority, getEffect()->_colors.toStdVector(), timeout, false);
Py_RETURN_NONE;
}
return nullptr;
}
@ -163,12 +157,12 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
if (PyByteArray_Check(bytearray))
{
size_t length = PyByteArray_Size(bytearray);
if (length == 3 * effect->_hyperion->getLedCount())
if (length == 3 * getEffect()->_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("");
memcpy(getEffect()->_colors.data(), data, length);
getEffect()->setInput(getEffect()->_priority, getEffect()->_colors.toStdVector(), timeout, false);
Py_RETURN_NONE;
}
else
{
@ -196,26 +190,17 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
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("");
}
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
// bytearray of values
@ -231,8 +216,8 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
Image<ColorRgb> image(width, height);
char * data = PyByteArray_AS_STRING(bytearray);
memcpy(image.memptr(), data, length);
effect->setInputImage(effect->_priority, image, timeout, false);
return Py_BuildValue("");
getEffect()->setInputImage(getEffect()->_priority, image, timeout, false);
Py_RETURN_NONE;
}
else
{
@ -258,13 +243,14 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
{
Effect *effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
QString file;
QBuffer buffer;
QImageReader reader;
if (effect->_imageData.isEmpty())
if (getEffect()->_imageData.isEmpty())
{
Q_INIT_RESOURCE(EffectEngine);
@ -272,7 +258,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
if(!PyArg_ParseTuple(args, "s", &source))
{
PyErr_SetString(PyExc_TypeError, "String required");
return NULL;
return nullptr;
}
file = QString::fromUtf8(source);
@ -285,7 +271,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
}
else
{
buffer.setData(QByteArray::fromBase64(effect->_imageData.toUtf8()));
buffer.setData(QByteArray::fromBase64(getEffect()->_imageData.toUtf8()));
buffer.open(QBuffer::ReadOnly);
reader.setDecideFormatFromContent(true);
reader.setDevice(&buffer);
@ -321,7 +307,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
else
{
PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData());
return NULL;
return nullptr;
}
}
return result;
@ -329,39 +315,29 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args)
else
{
PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData());
return NULL;
return nullptr;
}
}
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);
return Py_BuildValue("i", getEffect()->isInterruptionRequested() ? 1 : 0);
}
PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
// determine the timeout
int timeout = effect->_timeout;
int timeout = getEffect()->_timeout;
if (timeout > 0)
{
timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch();
timeout = getEffect()->_endTime - QDateTime::currentMSecsSinceEpoch();
// we are done if the time has passed
if (timeout <= 0)
{
return Py_BuildValue("");
}
if (timeout <= 0) Py_RETURN_NONE;
}
int argCount = PyTuple_Size(args);
@ -372,13 +348,13 @@ PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
argsOk = true;
}
if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size()))
if ( ! argsOk || (imgId>-1 && imgId >= getEffect()->_imageStack.size()))
{
return nullptr;
}
QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]);
QImage * qimage = (imgId<0) ? &(getEffect()->_image) : &(getEffect()->_imageStack[imgId]);
int width = qimage->width();
int height = qimage->height();
@ -397,14 +373,15 @@ PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args)
}
memcpy(image.memptr(), binaryImage.data(), binaryImage.size());
effect->setInputImage(effect->_priority, image, timeout, false);
getEffect()->setInputImage(getEffect()->_priority, image, timeout, false);
return Py_BuildValue("");
}
PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
PyObject * bytearray = nullptr;
@ -412,8 +389,8 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
int startRY = 0;
int startX = 0;
int startY = 0;
int endX, width = effect->_imageSize.width();
int endY, height = effect->_imageSize.height();
int endX, width = getEffect()->_imageSize.width();
int endY, height = getEffect()->_imageSize.height();
int spread = 0;
bool argsOK = false;
@ -452,9 +429,9 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
}
gradient.setSpread(static_cast<QGradient::Spread>(spread));
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@ -473,15 +450,16 @@ PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@ -519,9 +497,9 @@ PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
));
}
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@ -541,15 +519,16 @@ PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@ -600,9 +579,9 @@ PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
}
gradient.setSpread(static_cast<QGradient::Spread>(spread));
effect->_painter->fillRect(myQRect, gradient);
getEffect()->_painter->fillRect(myQRect, gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@ -621,7 +600,9 @@ PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
PyObject * bytearray = nullptr;
int argCount = PyTuple_Size(args);
@ -654,14 +635,14 @@ PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
points.append(QPoint((int)(data[idx]),(int)(data[idx+1])));
}
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_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("");
Py_RETURN_NONE;
}
else
{
@ -680,7 +661,9 @@ PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
PyObject * bytearray = nullptr;
QString brush;
@ -714,7 +697,7 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
startAngle = qMax(qMin(startAngle,360),0);
spanAngle = qMax(qMin(spanAngle,360),-360);
@ -745,7 +728,7 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
}
painter->setBrush(gradient);
return Py_BuildValue("");
Py_RETURN_NONE;
}
else
{
@ -768,22 +751,23 @@ PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args)
painter->setPen(newPen);
painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@ -807,8 +791,8 @@ PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
if (argsOK)
{
QRect myQRect(startX,startY,width,height);
effect->_painter->fillRect(myQRect, QColor(r,g,b,a));
return Py_BuildValue("");
getEffect()->_painter->fillRect(myQRect, QColor(r,g,b,a));
Py_RETURN_NONE;
}
return nullptr;
}
@ -816,7 +800,8 @@ PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b;
@ -824,8 +809,8 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
int startX = 0;
int startY = 0;
int thick = 1;
int endX = effect->_imageSize.width();
int endY = effect->_imageSize.height();
int endX = getEffect()->_imageSize.width();
int endY = getEffect()->_imageSize.height();
bool argsOK = false;
@ -840,7 +825,7 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QRect myQRect(startX, startY, endX, endY);
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
@ -849,14 +834,15 @@ PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args)
painter->drawLine(startX, startY, endX, endY);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b, x, y;
@ -876,7 +862,7 @@ PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
newPen.setWidth(thick);
@ -884,14 +870,15 @@ PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args)
painter->drawPoint(x, y);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int r, g, b;
@ -899,8 +886,8 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
int startX = 0;
int startY = 0;
int thick = 1;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
bool argsOK = false;
@ -915,7 +902,7 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
if (argsOK)
{
QPainter * painter = effect->_painter;
QPainter * painter = getEffect()->_painter;
QRect myQRect(startX,startY,width,height);
QPen oldPen = painter->pen();
QPen newPen(QColor(r,g,b,a));
@ -924,7 +911,7 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
painter->drawRect(startX, startY, width, height);
painter->setPen(oldPen);
return Py_BuildValue("");
Py_RETURN_NONE;
}
return nullptr;
}
@ -932,15 +919,16 @@ PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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("");
getEffect()->_image.setPixel(x,y,qRgb(r,g,b));
Py_RETURN_NONE;
}
return nullptr;
@ -949,14 +937,15 @@ PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int x, y;
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) )
{
QRgb rgb = effect->_image.pixel(x,y);
QRgb rgb = getEffect()->_image.pixel(x,y);
return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb));
}
return nullptr;
@ -964,52 +953,60 @@ PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args)
PyObject* EffectModule::wrapImageSave(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
QImage img(effect->_image.copy());
effect->_imageStack.append(img);
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", effect->_imageStack.size()-1);
QImage img(getEffect()->_image.copy());
getEffect()->_imageStack.append(img);
return Py_BuildValue("i", getEffect()->_imageStack.size()-1);
}
PyObject* EffectModule::wrapImageMinSize(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int w, h;
int width = effect->_imageSize.width();
int height = effect->_imageSize.height();
int width = getEffect()->_imageSize.width();
int height = getEffect()->_imageSize.height();
if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) )
{
if (width<w || height<h)
{
delete effect->_painter;
delete getEffect()->_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));
getEffect()->_image = getEffect()->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
getEffect()->_imageSize = getEffect()->_image.size();
getEffect()->_painter = new QPainter(&(getEffect()->_image));
}
return Py_BuildValue("ii", effect->_image.width(), effect->_image.height());
return Py_BuildValue("ii", getEffect()->_image.width(), getEffect()->_image.height());
}
return nullptr;
}
PyObject* EffectModule::wrapImageWidth(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
return Py_BuildValue("i", effect->_imageSize.width());
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", getEffect()->_imageSize.width());
}
PyObject* EffectModule::wrapImageHeight(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
return Py_BuildValue("i", effect->_imageSize.height());
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
return Py_BuildValue("i", getEffect()->_imageSize.height());
}
PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int argCount = PyTuple_Size(args);
int angle;
@ -1017,15 +1014,16 @@ PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args)
if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) )
{
angle = qMax(qMin(angle,360),0);
effect->_painter->rotate(angle);
return Py_BuildValue("");
getEffect()->_painter->rotate(angle);
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
int offsetX = 0;
int offsetY = 0;
@ -1036,60 +1034,31 @@ PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args)
PyArg_ParseTuple(args, "ii", &offsetX, &offsetY );
}
effect->_painter->translate(QPoint(offsetX,offsetY));
return Py_BuildValue("");
getEffect()->_painter->translate(QPoint(offsetX,offsetY));
Py_RETURN_NONE;
}
PyObject* EffectModule::wrapImageCShear(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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("");
getEffect()->_painter->shear(sh,sv);
Py_RETURN_NONE;
}
return nullptr;
}
PyObject* EffectModule::wrapImageResetT(PyObject *self, PyObject *args)
{
Effect * effect = getEffect();
// check if we have aborted already
if (getEffect()->isInterruptionRequested()) Py_RETURN_NONE;
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<Effect *>(PyCapsule_GetPointer(effectCapsule, nullptr));
Py_XDECREF(effectCapsule);
return effect;
getEffect()->_painter->resetTransform();
Py_RETURN_NONE;
}

View File

@ -6,7 +6,6 @@
#include <QTimer>
#include <QRgb>
#include <hyperion/Hyperion.h>
FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent)
: QObject(parent)
, _log(Logger::getInstance("FLATBUFSERVER"))
@ -15,7 +14,6 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObje
, _timeoutTimer(new QTimer(this))
, _timeout(timeout * 1000)
, _priority()
, _hyperion(Hyperion::getInstance())
{
// timer setup
_timeoutTimer->setSingleShot(true);
@ -70,8 +68,10 @@ void FlatBufferClient::forceClose()
void FlatBufferClient::disconnected()
{
Debug(_log, "Socket Closed");
_socket->deleteLater();
_hyperion->clear(_priority);
_socket->deleteLater();
if (_priority != 0 && _priority >= 100 && _priority < 200)
emit clearGlobalInput(_priority);
emit clientDisconnected();
}
@ -101,16 +101,35 @@ void FlatBufferClient::handleColorCommand(const hyperionnet::Color *colorReq)
color.blue = qBlue(rgbData);
// set output
_hyperion->setColor(_priority, color, colorReq->duration());
emit setGlobalInputColor(_priority, color, colorReq->duration());
// send reply
sendSuccessReply();
}
void FlatBufferClient::registationRequired(const int priority)
{
if (_priority == priority)
{
auto reply = hyperionnet::CreateReplyDirect(_builder, nullptr, -1, -1);
_builder.Finish(reply);
// send reply
sendMessage();
}
}
void FlatBufferClient::handleRegisterCommand(const hyperionnet::Register *regReq)
{
if (regReq->priority() < 100 || regReq->priority() >= 200)
{
// Warning(_log, "Register request from client %s contains invalid priority %d. Valid rage is between 100 and 199.", QSTRING_CSTR(_clientAddress), regReq->priority());
sendErrorReply("The priority " + std::to_string(regReq->priority()) + " is not in the priority range between 100 and 199.");
return;
}
_priority = regReq->priority();
_hyperion->registerInput(_priority, hyperion::COMP_FLATBUFSERVER, regReq->origin()->c_str()+_clientAddress);
emit registerGlobalInput(_priority, hyperion::COMP_FLATBUFSERVER, regReq->origin()->c_str()+_clientAddress);
auto reply = hyperionnet::CreateReplyDirect(_builder, nullptr, -1, (_priority ? _priority : -1));
_builder.Finish(reply);
@ -140,7 +159,7 @@ void FlatBufferClient::handleImageCommand(const hyperionnet::Image *image)
Image<ColorRgb> imageDest(width, height);
memmove(imageDest.memptr(), imageData->data(), imageData->size());
_hyperion->setInputImage(_priority, imageDest, duration);
emit setGlobalInputImage(_priority, imageDest, duration);
}
// send reply
@ -154,7 +173,7 @@ void FlatBufferClient::handleClearCommand(const hyperionnet::Clear *clear)
const int priority = clear->priority();
if (priority == -1) {
_hyperion->clearall();
emit clearAllGlobalInput();
}
else {
// Check if we are clearing ourselves.
@ -162,7 +181,7 @@ void FlatBufferClient::handleClearCommand(const hyperionnet::Clear *clear)
_priority = -1;
}
_hyperion->clear(priority);
emit clearGlobalInput(priority);
}
sendSuccessReply();

View File

@ -12,10 +12,9 @@
class QTcpSocket;
class QTimer;
class Hyperion;
namespace flatbuf {
class HyperionRequest;
class HyperionRequest;
}
///
@ -37,12 +36,27 @@ signals:
///
/// @brief forward register data to HyperionDaemon
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0);
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "FlatBuffer", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief Forward clear command to HyperionDaemon
///
void clearGlobalInput(const int priority);
///
/// @brief Forward clearAll command to HyperionDaemon
///
void clearAllGlobalInput(bool forceClearAll=false);
///
/// @brief forward prepared image to HyperionDaemon
///
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms = -1);
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms, const bool& clearEffect = false);
///
/// @brief Forward requested color
///
void setGlobalInputColor(const int priority, const ColorRgb &ledColor, const int timeout_ms, const QString& origin = "FlatBuffer" ,bool clearEffects = true);
///
/// @brief Emits whenever the client disconnected
@ -50,6 +64,11 @@ signals:
void clientDisconnected();
public slots:
///
/// @brief Requests a registration from the client
///
void registationRequired(const int priority);
///
/// @brief close the socket and call disconnected()
///
@ -125,7 +144,6 @@ private:
QTimer *_timeoutTimer;
int _timeout;
int _priority;
Hyperion* _hyperion;
QByteArray _receiveBuffer;

View File

@ -4,7 +4,7 @@
// Qt includes
#include <QRgb>
// protoserver includes
// flatbuffer includes
#include <flatbufserver/FlatBufferConnection.h>
FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString & address, const int& priority, const bool& skipReply)
@ -210,12 +210,15 @@ bool FlatBufferConnection::parseReply(const hyperionnet::Reply *reply)
}
// We got a registered reply.
if (registered != -1 && registered != _priority)
if (registered == -1 || registered != _priority)
_registered = false;
else
_registered = true;
return true;
}
else
throw std::runtime_error(reply->error()->str());
return false;
}

View File

@ -1,6 +1,10 @@
#include <flatbufserver/FlatBufferServer.h>
#include "FlatBufferClient.h"
// util
#include <utils/NetOrigin.h>
#include <utils/GlobalSignals.h>
// qt
#include <QJsonObject>
#include <QTcpServer>
@ -24,6 +28,7 @@ FlatBufferServer::~FlatBufferServer()
void FlatBufferServer::initServer()
{
_netOrigin = NetOrigin::getInstance();
connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection);
// apply config
@ -58,11 +63,22 @@ void FlatBufferServer::newConnection()
{
if(QTcpSocket* socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
_openConnections.append(client);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
connect(client, &FlatBufferClient::registerGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput);
connect(client, &FlatBufferClient::clearGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearGlobalInput);
connect(client, &FlatBufferClient::clearAllGlobalInput, GlobalSignals::getInstance(), &GlobalSignals::clearAllGlobalInput);
connect(client, &FlatBufferClient::setGlobalInputImage, GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage);
connect(client, &FlatBufferClient::setGlobalInputColor, GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor);
connect(GlobalSignals::getInstance(), &GlobalSignals::globalRegRequired, client, &FlatBufferClient::registationRequired);
_openConnections.append(client);
}
else
socket->close();
}
}
}

View File

@ -96,6 +96,7 @@ void QtGrabber::geometryChanged(const QRect &geo)
int QtGrabber::grabFrame(Image<ColorRgb> & image)
{
if (!_enabled) return 0;
if(_screen == nullptr)
{
// reinit, this will disable capture on failure

View File

@ -17,6 +17,7 @@
#include <linux/videodev2.h>
#include <hyperion/Hyperion.h>
#include <hyperion/HyperionIManager.h>
#include <QDirIterator>
#include <QFileInfo>
@ -57,9 +58,9 @@ V4L2Grabber::V4L2Grabber(const QString & device
setPixelDecimation(pixelDecimation);
getV4Ldevices();
// listen for component change for build-in grabber only
if (Hyperion::_hyperion)
connect(Hyperion::getInstance(), &Hyperion::componentStateChanged, this, &V4L2Grabber::componentStateChanged);
// connect componentStateChange only for build-in grabber
if (HyperionIManager::HIMinstance)
connect(this, &Grabber::componentStateChanged, this, &V4L2Grabber::componentStateChanged);
// init
setDeviceVideoStandard(device, videoStandard);

View File

@ -21,8 +21,10 @@ V4L2Wrapper::V4L2Wrapper(const QString &device,
qRegisterMetaType<Image<ColorRgb>>("Image<ColorRgb>");
// Handle the image in the captured thread using a direct connection
QObject::connect(&_grabber, SIGNAL(newFrame(Image<ColorRgb>)), this, SLOT(newFrame(Image<ColorRgb>)), Qt::DirectConnection);
QObject::connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection);
connect(&_grabber, SIGNAL(newFrame(Image<ColorRgb>)), this, SLOT(newFrame(Image<ColorRgb>)), Qt::DirectConnection);
connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection);
connect(this, &V4L2Wrapper::componentStateChanged, _ggrabber, &Grabber::componentStateChanged);
}
bool V4L2Wrapper::start()

View File

@ -0,0 +1,169 @@
#include <hyperion/AuthManager.h>
// db
#include <db/AuthTable.h>
#include <db/MetaTable.h>
// qt
#include <QJsonObject>
#include <QTimer>
AuthManager* AuthManager::manager = nullptr;
AuthManager::AuthManager(QObject* parent)
: QObject(parent)
, _authTable(new AuthTable(this))
, _metaTable(new MetaTable(this))
, _pendingRequests()
, _authRequired(true)
, _timer(new QTimer(this))
{
AuthManager::manager = this;
// get uuid
_uuid = _metaTable->getUUID();
// setup timer
_timer->setInterval(1000);
connect(_timer, &QTimer::timeout, this, &AuthManager::checkTimeout);
// init with default user and password
if(!_authTable->userExist("Hyperion"))
{
_authTable->createUser("Hyperion","hyperion");
}
}
bool & AuthManager::isAuthRequired()
{
return _authRequired;
}
bool & AuthManager::isLocalAuthRequired()
{
return _localAuthRequired;
}
const AuthManager::AuthDefinition AuthManager::createToken(const QString& comment)
{
const QString token = QUuid::createUuid().toString().mid(1, 36);
const QString id = QUuid::createUuid().toString().mid(1, 36).left(5);
_authTable->createToken(token, comment, id);
AuthDefinition def;
def.comment = comment;
def.token = token;
def.id = id;
return def;
}
const QVector<AuthManager::AuthDefinition> AuthManager::getTokenList()
{
QVector<QVariantMap> vector = _authTable->getTokenList();
QVector<AuthManager::AuthDefinition> finalVec;
for(const auto& entry : vector)
{
AuthDefinition def;
def.comment = entry["comment"].toString();
def.id = entry["id"].toString();
def.lastUse = entry["last_use"].toString();
// don't add empty ids
if(!entry["id"].toString().isEmpty())
finalVec.append(def);
}
return finalVec;
}
bool AuthManager::isUserAuthorized(const QString& user, const QString& pw)
{
return _authTable->isUserAuthorized(user, pw);
}
bool AuthManager::isTokenAuthorized(const QString& token)
{
return _authTable->tokenExist(token);
}
void AuthManager::setNewTokenRequest(QObject* caller, const QString& comment, const QString& id)
{
if(!_pendingRequests.contains(id))
{
AuthDefinition newDef {id, comment, caller, uint64_t(QDateTime::currentMSecsSinceEpoch()+60000)};
_pendingRequests[id] = newDef;
_timer->start();
emit newPendingTokenRequest(id, comment);
}
}
bool AuthManager::acceptTokenRequest(const QString& id)
{
if(_pendingRequests.contains(id))
{
const QString token = QUuid::createUuid().toString().remove("{").remove("}");
AuthDefinition def = _pendingRequests.take(id);
_authTable->createToken(token, def.comment, id);
emit tokenResponse(true, def.caller, token, def.comment, id);
return true;
}
return false;
}
bool AuthManager::denyTokenRequest(const QString& id)
{
if(_pendingRequests.contains(id))
{
AuthDefinition def = _pendingRequests.take(id);
emit tokenResponse(false, def.caller, QString(), def.comment, id);
return true;
}
return false;
}
const QMap<QString, AuthManager::AuthDefinition> AuthManager::getPendingRequests()
{
return _pendingRequests;
}
bool AuthManager::deleteToken(const QString& id)
{
if(_authTable->deleteToken(id))
{
//emit tokenDeleted(token);
return true;
}
return false;
}
void AuthManager::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
if(type == settings::NETWORK)
{
const QJsonObject& obj = config.object();
_authRequired = obj["apiAuth"].toBool(true);
_localAuthRequired = obj["localApiAuth"].toBool(false);
}
}
void AuthManager::checkTimeout()
{
const uint64_t now = QDateTime::currentMSecsSinceEpoch();
QMapIterator<QString, AuthDefinition> i(_pendingRequests);
while (i.hasNext())
{
i.next();
const AuthDefinition& def = i.value();
if(def.timeoutTime <= now)
{
emit tokenResponse(false, def.caller, QString(), def.comment, def.id);
_pendingRequests.remove(i.key());
}
}
// abort if empty
if(_pendingRequests.isEmpty())
_timer->stop();
}

View File

@ -25,5 +25,6 @@ target_link_libraries(hyperion
bonjour
boblightserver
effectengine
database
${QT_LIBRARIES}
)

View File

@ -81,6 +81,8 @@ void CaptureCont::setSystemCaptureEnable(const bool& enable)
{
disconnect(GlobalSignals::getInstance(), &GlobalSignals::setSystemImage, 0, 0);
_hyperion->clear(_systemCaptPrio);
_systemInactiveTimer->stop();
_systemCaptName = "";
}
_systemCaptEnabled = enable;
_hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, enable);
@ -103,6 +105,7 @@ void CaptureCont::setV4LCaptureEnable(const bool& enable)
disconnect(GlobalSignals::getInstance(), &GlobalSignals::setV4lImage, 0, 0);
_hyperion->clear(_v4lCaptPrio);
_v4lInactiveTimer->stop();
_v4lCaptName = "";
}
_v4lCaptEnabled = enable;
_hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, enable);

View File

@ -11,7 +11,7 @@ ComponentRegister::ComponentRegister(Hyperion* hyperion)
{
// init all comps to false
QVector<hyperion::Components> vect;
vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_UDPLISTENER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE;
vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE;
for(auto e : vect)
{
_componentStates.emplace(e, ((e == COMP_ALL) ? true : false));

View File

@ -25,7 +25,7 @@ GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned
connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action);
// connect the image forwarding
_grabberName.startsWith("V4L")
(_grabberName.startsWith("V4L"))
? connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setV4lImage)
: connect(this, &GrabberWrapper::systemImage, GlobalSignals::getInstance(), &GlobalSignals::setSystemImage);
}

View File

@ -5,17 +5,9 @@
#include <unistd.h>
// QT includes
#include <QDateTime>
#include <QThread>
#include <QRegExp>
#include <QString>
#include <QStringList>
#include <QCryptographicHash>
#include <QTimer>
#include <QFile>
#include <QFileInfo>
#include <QHostInfo>
#include <QCryptographicHash>
#include <QThread>
// hyperion include
#include <hyperion/Hyperion.h>
@ -25,6 +17,7 @@
// utils
#include <utils/hyperion.h>
#include <utils/GlobalSignals.h>
// Leddevice includes
#include <leddevice/LedDeviceWrapper.h>
@ -35,9 +28,6 @@
// effect engine includes
#include <effectengine/EffectEngine.h>
// Hyperion Daemon
#include <../src/hyperiond/hyperiond.h>
// settingsManagaer
#include <hyperion/SettingsManager.h>
@ -50,28 +40,10 @@
// Boblight
#include <boblightserver/BoblightServer.h>
Hyperion* Hyperion::_hyperion = nullptr;
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(daemon, instance, configFile, rootPath);
return Hyperion::_hyperion;
}
Hyperion* Hyperion::getInstance()
{
if ( Hyperion::_hyperion == nullptr )
throw std::runtime_error("Hyperion::getInstance used without call of Hyperion::initInstance before");
return Hyperion::_hyperion;
}
Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath)
: _daemon(daemon)
, _settingsManager(new SettingsManager(this, instance, configFile))
Hyperion::Hyperion(const quint8& instance)
: QObject()
, _instIndex(instance)
, _settingsManager(new SettingsManager(instance, this))
, _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())))
@ -79,18 +51,33 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
, _muxer(_ledString.leds().size())
, _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()))
, _effectEngine(nullptr)
, _messageForwarder(new MessageForwarder(this))
, _configFile(configFile)
, _rootPath(rootPath)
, _messageForwarder(nullptr)
, _log(Logger::getInstance("HYPERION"))
, _hwLedCount()
, _configHash()
, _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array()))
, _prevCompId(hyperion::COMP_INVALID)
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
{
}
Hyperion::~Hyperion()
{
freeObjects(false);
}
void Hyperion::start()
{
// forward settings changed to Hyperion
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::settingsChanged);
// get newVideoMode from HyperionIManager
connect(this, &Hyperion::newVideoMode, this, &Hyperion::handleNewVideoMode);
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!");
}
// handle hwLedCount
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
@ -100,12 +87,14 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
{
_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);
@ -128,22 +117,14 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
_deviceSmooth = new LinearColorSmoothing(getSetting(settings::SMOOTHING), this);
connect(this, &Hyperion::settingsChanged, _deviceSmooth, &LinearColorSmoothing::handleSettingsUpdate);
// create the message forwarder only on main instance
if (_instIndex == 0)
_messageForwarder = new MessageForwarder(this);
// create the effect engine; needs to be initialized after smoothing!
_effectEngine = new EffectEngine(this);
connect(_effectEngine, &EffectEngine::effectListUpdated, this, &Hyperion::effectListUpdated);
// setup config state checks and initial shot
checkConfigState();
if(_fsWatcher.addPath(_configFile))
QObject::connect(&_fsWatcher, &QFileSystemWatcher::fileChanged, this, &Hyperion::checkConfigState);
else
{
_cTimer = new QTimer(this);
Warning(_log,"Filesystem Observer failed for file: %s, use fallback timer", _configFile.toStdString().c_str());
connect(_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState()));
_cTimer->start(2000);
}
// initial startup effect
hyperion::handleInitialEffect(this, getSetting(settings::FGEFFECT).object());
@ -153,6 +134,13 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
// create the Daemon capture interface
_captureCont = new CaptureCont(this);
// forwards global signals to the corresponding slots
connect(GlobalSignals::getInstance(), &GlobalSignals::registerGlobalInput, this, &Hyperion::registerInput);
connect(GlobalSignals::getInstance(), &GlobalSignals::clearGlobalInput, this, &Hyperion::clear);
connect(GlobalSignals::getInstance(), &GlobalSignals::clearAllGlobalInput, this, &Hyperion::clearall);
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor, this, &Hyperion::setColor);
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage, this, &Hyperion::setInputImage);
// 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();
@ -160,13 +148,15 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
_boblightServer = new BoblightServer(this, getSetting(settings::BOBLSERVER));
connect(this, &Hyperion::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate);
// set unique id
_id = QString(QCryptographicHash::hash(getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex());
// instance inited
emit started();
// enter thread event loop
}
Hyperion::~Hyperion()
void Hyperion::stop()
{
freeObjects(false);
emit finished();
thread()->wait();
}
void Hyperion::freeObjects(bool emitCloseSignal)
@ -209,9 +199,6 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
const QJsonArray leds = config.array();
// // lock update()
// _lockUpdate = true;
// stop and cache all running effects, as effects depend heavily on ledlayout
_effectEngine->cacheRunningEffects();
@ -238,23 +225,16 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
// handle hwLedCount update
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
// update led count in device
//_ledDeviceWrapper->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)
{
QMutexLocker lock(&_changes);
// _lockUpdate = true;
QJsonObject dev = config.object();
// handle hwLedCount update
@ -268,18 +248,11 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum
_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 until the led devices can handle dynamic changes
dev["currentLedCount"] = int(_hwLedCount); // Inject led count info
_ledDeviceWrapper->createLedDevice(dev);
// _lockUpdate = false;
}
// update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color
update();
}
@ -294,12 +267,6 @@ 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 _ledDeviceWrapper->getLatchTime();
@ -315,40 +282,6 @@ unsigned Hyperion::getLedCount() const
return _ledString.leds().size();
}
void Hyperion::checkConfigState(QString cfile)
{
// Check config modifications
QFile f(_configFile);
if (f.open(QFile::ReadOnly))
{
QCryptographicHash hash(QCryptographicHash::Sha1);
if (hash.addData(&f))
{
if (_configHash.size() == 0)
{
_configHash = hash.result();
}
_configMod = _configHash != hash.result() ? true : false;
}
}
f.close();
if(_prevConfigMod != _configMod)
{
_prevConfigMod = _configMod;
}
// Check config writeable
QFile file(_configFile);
QFileInfo fileInfo(file);
_configWrite = fileInfo.isWritable() && fileInfo.isReadable() ? true : false;
if(_prevConfigWrite != _configWrite)
{
_prevConfigWrite = _configWrite;
}
}
void Hyperion::setSourceAutoSelectEnabled(bool enabled)
{
if(_muxer.setSourceAutoSelectEnabled(enabled))
@ -372,6 +305,7 @@ void Hyperion::setNewComponentState(const hyperion::Components& component, const
void Hyperion::setComponentState(const hyperion::Components component, const bool state)
{
// TODO REMOVE THIS STEP
emit componentStateChanged(component, state);
}
@ -399,6 +333,12 @@ bool Hyperion::setInput(const int priority, const std::vector<ColorRgb>& ledColo
bool Hyperion::setInputImage(const int priority, const Image<ColorRgb>& image, int64_t timeout_ms, const bool& clearEffect)
{
if (!_muxer.hasPriority(priority))
{
emit GlobalSignals::getInstance()->globalRegRequired(priority);
return false;
}
if(_muxer.setInputImage(priority, image, timeout_ms))
{
// clear effect if this call does not come from an effect
@ -419,7 +359,7 @@ bool Hyperion::setInputInactive(const quint8& priority)
return _muxer.setInputInactive(priority);
}
void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, const QString& origin, bool clearEffects)
void Hyperion::setColor(const 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)
@ -431,8 +371,10 @@ void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_m
// register color
registerInput(priority, hyperion::COMP_COLOR, origin);
// write color to muxer
// write color to muxer & queuePush
setInput(priority, ledColors, timeout_ms);
if(timeout_ms <= 0)
_muxer.queuePush();
}
const QStringList & Hyperion::getAdjustmentIds() const
@ -451,7 +393,7 @@ void Hyperion::adjustmentsUpdated()
update();
}
bool Hyperion::clear(int priority)
bool Hyperion::clear(const int priority)
{
// send clear signal to the effect engine
// (outside the check so the effect gets cleared even when the effect is not sending colors)
@ -552,7 +494,7 @@ void Hyperion::setVideoMode(const VideoMode& mode)
const VideoMode & Hyperion::getCurrentVideoMode()
{
return _daemon->getVideoMode();
return _currVideoMode;
}
const QString & Hyperion::getActiveDevice()
@ -572,9 +514,6 @@ void Hyperion::updatedComponentState(const hyperion::Components comp, const bool
_prevCompId = comp;
_raw2ledAdjustment->setBacklightEnabled((_prevCompId != hyperion::COMP_COLOR && _prevCompId != hyperion::COMP_EFFECT));
}
if(comp == hyperion::COMP_ALL)
_muxer.setEnable(state); // first muxer to update all inputs
}
void Hyperion::update()

View File

@ -0,0 +1,203 @@
#include <hyperion/HyperionIManager.h>
// hyperion
#include <hyperion/Hyperion.h>
#include <db/InstanceTable.h>
// qt
#include <QThread>
HyperionIManager* HyperionIManager::HIMinstance;
HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("HYPERION"))
, _instanceTable( new InstanceTable(rootPath, this) )
, _rootPath( rootPath )
{
HIMinstance = this;
qRegisterMetaType<instanceState>("instanceState");
}
Hyperion* HyperionIManager::getHyperionInstance(const quint8& instance)
{
if(_runningInstances.contains(instance))
return _runningInstances.value(instance);
Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
return _runningInstances.value(0);
}
const QVector<QVariantMap> HyperionIManager::getInstanceData()
{
QVector<QVariantMap> instances = _instanceTable->getAllInstances();
for( auto & entry : instances)
{
// add running state
entry["running"] = _runningInstances.contains(entry["instance"].toInt());
}
return instances;
}
void HyperionIManager::startAll()
{
for(const auto entry : _instanceTable->getAllInstances(true))
{
startInstance(entry["instance"].toInt());
}
}
void HyperionIManager::stopAll()
{
// copy the instances due to loop corruption, even with .erase() return next iter
QMap<quint8, Hyperion*> instCopy = _runningInstances;
for(const auto instance : instCopy)
{
instance->stop();
}
}
bool HyperionIManager::startInstance(const quint8& inst, const bool& block)
{
if(_instanceTable->instanceExist(inst))
{
if(!_runningInstances.contains(inst) && !_startQueue.contains(inst))
{
QThread* hyperionThread = new QThread();
Hyperion* hyperion = new Hyperion(inst);
hyperion->moveToThread(hyperionThread);
// setup thread management
connect(hyperionThread, &QThread::started, hyperion, &Hyperion::start);
connect(hyperion, &Hyperion::started, this, &HyperionIManager::handleStarted);
connect(hyperion, &Hyperion::finished, this, &HyperionIManager::handleFinished);
connect(hyperion, &Hyperion::finished, hyperionThread, &QThread::quit, Qt::DirectConnection);
// setup further connections
// from Hyperion
connect(hyperion, &Hyperion::settingsChanged, this, &HyperionIManager::settingsChanged);
connect(hyperion, &Hyperion::videoMode, this, &HyperionIManager::requestVideoMode);
connect(hyperion, &Hyperion::componentStateChanged, this, &HyperionIManager::componentStateChanged);
// to Hyperion
connect(this, &HyperionIManager::newVideoMode, hyperion, &Hyperion::newVideoMode);
// add to queue and start
_startQueue << inst;
hyperionThread->start();
// update db
_instanceTable->setLastUse(inst);
_instanceTable->setEnable(inst, true);
if(block)
{
while(!hyperionThread->isRunning()){};
}
return true;
}
Debug(_log,"Can't start Hyperion instance index '%d' with name '%s' it's already running or queued for start", inst, QSTRING_CSTR(_instanceTable->getNamebyIndex(inst)));
return false;
}
Debug(_log,"Can't start Hyperion instance index '%d' it doesn't exist in DB", inst);
return false;
}
bool HyperionIManager::stopInstance(const quint8& inst)
{
// inst 0 can't be stopped
if(!isInstAllowed(inst))
return false;
if(_instanceTable->instanceExist(inst))
{
if(_runningInstances.contains(inst))
{
// notify a ON_STOP rather sooner than later, queued signal listener should have some time to drop the pointer before it's deleted
emit instanceStateChanged(H_ON_STOP, inst);
Hyperion* hyperion = _runningInstances.value(inst);
hyperion->stop();
// update db
_instanceTable->setEnable(inst, false);
return true;
}
Debug(_log,"Can't stop Hyperion instance index '%d' with name '%s' it's not running'", inst, QSTRING_CSTR(_instanceTable->getNamebyIndex(inst)));
return false;
}
Debug(_log,"Can't stop Hyperion instance index '%d' it doesn't exist in DB", inst);
return false;
}
bool HyperionIManager::createInstance(const QString& name, const bool& start)
{
quint8 inst;
if(_instanceTable->createInstance(name, inst))
{
Info(_log,"New Hyperion instance created with name '%s'",QSTRING_CSTR(name));
emit instanceStateChanged(H_CREATED, inst, name);
emit change();
if(start)
startInstance(inst);
return true;
}
return false;
}
bool HyperionIManager::deleteInstance(const quint8& inst)
{
// inst 0 can't be deleted
if(!isInstAllowed(inst))
return false;
// stop it if required as blocking and wait
stopInstance(inst);
if(_instanceTable->deleteInstance(inst))
{
Info(_log,"Hyperion instance with index '%d' has been deleted", inst);
emit instanceStateChanged(H_DELETED, inst);
emit change();
return true;
}
return false;
}
bool HyperionIManager::saveName(const quint8& inst, const QString& name)
{
if(_instanceTable->saveName(inst, name))
{
emit change();
return true;
}
return false;
}
void HyperionIManager::handleFinished()
{
Hyperion* hyperion = qobject_cast<Hyperion*>(sender());
const quint8 & instance = hyperion->getInstanceIndex();
Info(_log,"Hyperion instance '%s' has been stopped", QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
_runningInstances.remove(instance);
hyperion->deleteLater();
emit instanceStateChanged(H_STOPPED, instance);
emit change();
}
void HyperionIManager::handleStarted()
{
Hyperion* hyperion = qobject_cast<Hyperion*>(sender());
const quint8 & instance = hyperion->getInstanceIndex();
Info(_log,"Hyperion instance '%s' has been started", QSTRING_CSTR(_instanceTable->getNamebyIndex(instance)));
_startQueue.removeAll(instance);
_runningInstances.insert(instance, hyperion);
emit instanceStateChanged(H_STARTED, instance);
emit change();
}

View File

@ -180,8 +180,6 @@ void LinearColorSmoothing::componentStateChange(const hyperion::Components compo
void LinearColorSmoothing::setEnable(bool enable)
{
LedDevice::setEnable(enable);
if (!enable)
{
_timer->stop();

View File

@ -63,7 +63,7 @@ public:
///
/// @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)
/// @param force Overwrite in any case the current values (used for cfg 0 settings update)
///
/// @return On success return else false (and falls back to cfg 0)
///

View File

@ -22,9 +22,9 @@ PriorityMuxer::PriorityMuxer(int ledCount)
, _activeInputs()
, _lowestPriorityInfo()
, _sourceAutoSelectEnabled(true)
, _updateTimer(new QTimer(this))
, _timer(new QTimer(this))
, _blockTimer(new QTimer(this))
, _updateTimer(new QTimer())
, _timer(new QTimer())
, _blockTimer(new QTimer())
{
// init lowest priority info
_lowestPriorityInfo.priority = PriorityMuxer::LOWEST_PRIORITY;
@ -43,12 +43,12 @@ PriorityMuxer::PriorityMuxer(int ledCount)
// forward timeRunner signal to prioritiesChanged signal & threading workaround
connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged);
connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger);
connect(this, &PriorityMuxer::activeStateChanged, this, &PriorityMuxer::prioritiesChanged);
// start muxer timer
connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime);
_updateTimer->setInterval(250);
_updateTimer->start();
InputInfo ninfo;
}
PriorityMuxer::~PriorityMuxer()
@ -279,7 +279,8 @@ void PriorityMuxer::clearAll(bool forceClearAll)
void PriorityMuxer::setCurrentTime(void)
{
const int64_t now = QDateTime::currentMSecsSinceEpoch();
int newPriority = PriorityMuxer::LOWEST_PRIORITY;
int newPriority;
_activeInputs.contains(0) ? newPriority = 0 : newPriority = PriorityMuxer::LOWEST_PRIORITY;
for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();)
{

View File

@ -3,6 +3,7 @@
// util
#include <utils/JsonUtils.h>
#include <db/SettingsTable.h>
// json schema process
#include <utils/jsonschema/QJsonFactory.h>
@ -11,16 +12,13 @@
// write config to filesystem
#include <utils/JsonUtils.h>
// hyperion
#include <hyperion/Hyperion.h>
QJsonObject SettingsManager::schemaJson;
SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile)
: _hyperion(hyperion)
SettingsManager::SettingsManager(const quint8& instance, QObject* parent)
: QObject(parent)
, _log(Logger::getInstance("SettingsManager"))
, _sTable(new SettingsTable(instance, this))
{
connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged);
// get schema
if(schemaJson.isEmpty())
{
@ -34,112 +32,78 @@ SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, con
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;
schemaCheckerT.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<bool, bool> validate = schemaCheckerT.validate(_qconfig);
// errors in schema syntax, abort
if (!validate.second)
// transform json to string lists
QStringList keyList = defaultConfig.keys();
QStringList defValueList;
for(const auto key : keyList)
{
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
if(defaultConfig[key].isObject())
{
schemaJson = QJsonFactory::readSchema(":/hyperion-schema");
defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact));
}
catch(const std::runtime_error& error)
else if(defaultConfig[key].isArray())
{
throw std::runtime_error(error.what());
defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact));
}
}
// 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;
schemaCheckerT.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<bool, bool> validate = schemaCheckerT.validate(_qconfig);
// errors in schema syntax, abort
if (!validate.second)
// fill database with default data if required
for(const auto key : keyList)
{
foreach (auto & schemaError, schemaCheckerT.getMessages())
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
throw std::runtime_error("ERROR: Hyperion schema has syntax errors!");
QString val = defValueList.takeFirst();
// prevent overwrite
if(!_sTable->recordExist(key))
_sTable->createSettingsRecord(key,val);
}
// 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())
// need to validate all data in database constuct the entire data object
// TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry...
QJsonObject dbConfig;
for(const auto key : keyList)
{
QJsonDocument doc = _sTable->getSettingsRecord(key);
if(doc.isArray())
dbConfig[key] = doc.array();
else
dbConfig[key] = doc.object();
}
// validate full dbconfig against schema, on error we need to rewrite entire table
QJsonSchemaChecker schemaChecker;
schemaChecker.setSchema(schemaJson);
QPair<bool,bool> valid = schemaChecker.validate(dbConfig);
// check if our main schema syntax is IO
if (!valid.second)
{
foreach (auto & schemaError, schemaChecker.getMessages())
Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError));
throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!");
}
if (!valid.first)
{
Info(_log,"Table upgrade required...");
dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig);
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");
saveSettings(dbConfig);
}
else
_qconfig = dbConfig;
Debug(_log,"Settings database initialized")
}
SettingsManager::~SettingsManager()
{
}
const QJsonDocument SettingsManager::getSetting(const settings::type& type)
{
QString key = settings::typeToString(type);
if(_qconfig[key].isObject())
return QJsonDocument(_qconfig[key].toObject());
else
return QJsonDocument(_qconfig[key].toArray());
return _sTable->getSettingsRecord(settings::typeToString(type));
}
bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
@ -161,32 +125,34 @@ bool SettingsManager::saveSettings(QJsonObject config, const bool& correct)
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
}
// save data to file
if(_hyperion != nullptr)
{
if(!JsonUtils::write(_hyperion->getConfigFilePath(), config, _log))
return false;
}
// compare old data with new data to emit/save changes accordingly
for(const auto key : config.keys())
{
QString newData, oldData;
_qconfig[key].isObject()
? oldData = QString(QJsonDocument(_qconfig[key].toObject()).toJson(QJsonDocument::Compact))
: oldData = QString(QJsonDocument(_qconfig[key].toArray()).toJson(QJsonDocument::Compact));
config[key].isObject()
? newData = QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact))
: newData = QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
if(oldData != newData)
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(newData.toLocal8Bit()));
}
// store the current state
// store the new config
_qconfig = config;
// extract keys and data
QStringList keyList = config.keys();
QStringList newValueList;
for(const auto key : keyList)
{
if(config[key].isObject())
{
newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact));
}
else if(config[key].isArray())
{
newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact));
}
}
// compare database data with new data to emit/save changes accordingly
for(const auto key : keyList)
{
QString data = newValueList.takeFirst();
if(_sTable->getSettingsRecordString(key) != data)
{
_sTable->createSettingsRecord(key, data);
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
}
}
return true;
}

View File

@ -63,10 +63,6 @@
{
"$ref": "schema-boblightServer.json"
},
"udpListener" :
{
"$ref": "schema-udpListener.json"
},
"webConfig" :
{
"$ref": "schema-webConfig.json"
@ -79,6 +75,10 @@
{
"$ref": "schema-instCapture.json"
},
"network":
{
"$ref": "schema-network.json"
},
"ledConfig":
{
"$ref": "schema-ledConfig.json"

View File

@ -17,11 +17,11 @@
<file alias="schema-flatbufServer.json">schema/schema-flatbufServer.json</file>
<file alias="schema-protoServer.json">schema/schema-protoServer.json</file>
<file alias="schema-boblightServer.json">schema/schema-boblightServer.json</file>
<file alias="schema-udpListener.json">schema/schema-udpListener.json</file>
<file alias="schema-webConfig.json">schema/schema-webConfig.json</file>
<file alias="schema-effects.json">schema/schema-effects.json</file>
<file alias="schema-ledConfig.json">schema/schema-ledConfig.json</file>
<file alias="schema-leds.json">schema/schema-leds.json</file>
<file alias="schema-instCapture.json">schema/schema-instCapture.json</file>
<file alias="schema-network.json">schema/schema-network.json</file>
</qresource>
</RCC>

View File

@ -0,0 +1,59 @@
{
"type" : "object",
"title" : "edt_conf_net_heading_title",
"required" : true,
"properties" :
{
"apiAuth" :
{
"type" : "boolean",
"title" : "edt_conf_net_apiAuth_title",
"required" : true,
"default" : true,
"propertyOrder" : 1
},
"internetAccessAPI" :
{
"type" : "boolean",
"title" : "edt_conf_net_internetAccessAPI_title",
"required" : true,
"default" : false,
"options": {
"dependencies": {
"apiAuth": true
}
},
"propertyOrder" : 2
},
"ipWhitelist" :
{
"type" : "array",
"title" : "edt_conf_net_ipWhitelist_title",
"required" : true,
"items" : {
"type": "string",
"title" : "edt_conf_net_ip_itemtitle"
},
"options": {
"dependencies": {
"internetAccessAPI": false
}
},
"propertyOrder" : 3
},
"localApiAuth" :
{
"type" : "boolean",
"title" : "edt_conf_net_localApiAuth_title",
"required" : true,
"default" : false,
"options": {
"dependencies": {
"apiAuth": true
}
},
"propertyOrder" : 4
}
},
"additionalProperties" : false
}

View File

@ -1,56 +0,0 @@
{
"type" : "object",
"title" : "edt_conf_udpl_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"title" : "edt_conf_general_enable_title",
"default" : false,
"propertyOrder" : 1
},
"address" :
{
"type" : "string",
"title" : "edt_conf_udpl_address_title",
"default" : "239.255.28.01",
"propertyOrder" : 2
},
"port" :
{
"type" : "integer",
"title" : "edt_conf_general_port_title",
"minimum" : 0,
"maximum" : 65535,
"default" : 2801,
"propertyOrder" : 3
},
"priority" :
{
"type" : "integer",
"title" : "edt_conf_general_priority_title",
"minimum" : 100,
"maximum" : 254,
"default" : 200,
"propertyOrder" : 4
},
"timeout" :
{
"type" : "integer",
"title" : "edt_conf_udpl_timeout_title",
"minimum" : 1000,
"default" : 10000,
"append" : "edt_append_ms",
"propertyOrder" : 5
},
"shared" :
{
"type" : "boolean",
"title" : "edt_conf_udpl_shared_title",
"default" : false,
"propertyOrder" : 6
}
},
"additionalProperties" : false
}

View File

@ -6,20 +6,16 @@
#include <QTcpSocket>
#include <QHostAddress>
// websocket includes
#include "webserver/WebSocketClient.h"
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
JsonClientConnection::JsonClientConnection(QTcpSocket *socket, const bool& localConnection)
: QObject()
, _socket(socket)
, _websocketClient(nullptr)
, _receiveBuffer()
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
{
connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected);
connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
// create a new instance of JsonAPI
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this);
_jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, localConnection, this);
// get the callback messages from JsonAPI and send it to the client
connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject)));
}
@ -27,37 +23,21 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
void JsonClientConnection::readRequest()
{
_receiveBuffer += _socket->readAll();
// might be an old hyperion classic handshake request or raw socket data
if(_receiveBuffer.contains("Upgrade: websocket"))
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
if(_websocketClient == Q_NULLPTR)
{
// disconnect this slot from socket for further requests
disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest);
int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19;
QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data());
_websocketClient = new WebSocketClient(header, _socket, this);
}
}
else
{
// raw socket data, handling as usual
int bytes = _receiveBuffer.indexOf('\n') + 1;
while(bytes > 0)
{
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// create message string
QString message(QByteArray(_receiveBuffer.data(), bytes));
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// remove message data from buffer
_receiveBuffer = _receiveBuffer.mid(bytes);
// handle message
_jsonAPI->handleMessage(message);
// handle message
_jsonAPI->handleMessage(message);
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
// try too look up '\n' again
bytes = _receiveBuffer.indexOf('\n') + 1;
}
}

View File

@ -10,7 +10,6 @@
class JsonAPI;
class QTcpSocket;
class WebSocketClient;
///
/// The Connection object created by \a JsonServer when a new connection is established
@ -24,7 +23,7 @@ public:
/// Constructor
/// @param socket The Socket object for this connection
///
JsonClientConnection(QTcpSocket * socket);
JsonClientConnection(QTcpSocket * socket, const bool& localConnection);
signals:
void connectionClosed();
@ -42,7 +41,6 @@ private slots:
private:
QTcpSocket* _socket;
WebSocketClient* _websocketClient;
/// new instance of JsonAPI
JsonAPI * _jsonAPI;

View File

@ -7,6 +7,7 @@
// bonjour include
#include <bonjour/bonjourserviceregister.h>
#include <utils/NetOrigin.h>
// qt includes
#include <QTcpServer>
@ -19,6 +20,7 @@ JsonServer::JsonServer(const QJsonDocument& config)
, _server(new QTcpServer(this))
, _openConnections()
, _log(Logger::getInstance("JSONSERVER"))
, _netOrigin(NetOrigin::getInstance())
{
Debug(_log, "Created instance");
@ -95,12 +97,17 @@ void JsonServer::newConnection()
{
if (QTcpSocket * socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
JsonClientConnection * connection = new JsonClientConnection(socket);
_openConnections.insert(connection);
if(_netOrigin->accessAllowed(socket->peerAddress(), socket->localAddress()))
{
Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str());
JsonClientConnection * connection = new JsonClientConnection(socket, _netOrigin->isLocalAddress(socket->peerAddress(), socket->localAddress()));
_openConnections.insert(connection);
// register slot for cleaning up after the connection closed
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
// register slot for cleaning up after the connection closed
connect(connection, &JsonClientConnection::connectionClosed, this, &JsonServer::closedConnection);
}
else
socket->close();
}
}
}

Some files were not shown because too many files have changed in this diff Show More