From ed7f538e3807112f636b5ba3c2cad5fedafb036c Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Sun, 2 Dec 2018 14:25:36 +0100 Subject: [PATCH 01/46] Delete hyperion-icon_32px.png --- libsrc/hyperion/hyperion-icon_32px.png | Bin 4717 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 libsrc/hyperion/hyperion-icon_32px.png diff --git a/libsrc/hyperion/hyperion-icon_32px.png b/libsrc/hyperion/hyperion-icon_32px.png deleted file mode 100644 index 12196277a588b306ec07c1b8533df7ed631826c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4717 zcmV-z5|ZtSP)uJ@VVD_UC<6{NG_fI~0ue<-1QkJoA_k0xBC#Thg@9ne9*`iQ#9$Or zQF$}6R&?d%y_c8YA7_1QpS|}zXYYO1x&V;8{kgn!SPFnNo`4_X6{c}T{8k*B#$jdxfFg<9uYy1K45IaYvHg`_dOZM)Sy63ve6hvv z1)yUy0P^?0*fb9UASvow`@mQCp^4`uNg&9uGcn1|&Nk+9SjOUl{-OWr@Hh0;_l(8q z{wNRKos+;6rV8ldy0Owz(}jF`W(JeRp&R{qi2rfmU!TJ;gp(Kmm5I1s5m_f-n#TRsj}B0%?E`vOzxB2#P=n*a3EfYETOrKoe*ICqM@{4K9Go;5xVgZi5G4 z1dM~{UdP6d+Yd3o?MrAqM0Kc|iV92owdyL5UC#5<>aVCa44|hpM4E zs0sQWIt5*Tu0n&*J!lk~f_{hI!w5`*sjxDv4V%CW*ah~3!{C*0BD@;TgA3v9a1~q+ zAA{TB3-ERLHar49hi4Ih5D^-ph8Q6X#0?2VqLBoIkE}zAkxHZUgRb+f=nat zP#6>iMMoK->`~sRLq)(kHo*Vn{;LcG6+edD1=7D>9j^O?D{Qg|tCDK{ym)H7&wDr6*;uGTJg8GHjVbnL{!cWyUB7MT6o-VNo_w8Yq`2<5Ub)hw4L3rj}5@qxMs0 zWMyP6Wy582WNT#4$d1qunl{acmP#w5ouJ*Jy_Zv#bCKi7ZIf$}8d zZdVy&)LYdbX%I9R8VMQ|8r>Q*nyQ)sn)#Z|n)kKvS`4iu ztvy=3T65Yu+7a4Yv^%sXb>ww?bn(=Yu(!=O6^iuTp>)p_Y^{w=i z^lS773}6Fm1Fpe-gF!>Ip{*g$u-szvGhed;vo5pW&GpS$<~8QGEXWp~7V9lKEnZq0SaK{6Sl+dwSOr*Z zvFf(^Xl-N7w{EeXveC4Ov)N}e%%C!Y7^RFWwrE>d+x51mZQt2h+X?JW*!^a2WS?Sx z)P8cQ&Qi|OhNWW;>JChYI)@QQx?`Nj^#uJBl~d&PK+RZLOLos~K(b5>qmrMN0})tOkySZ3_W zICNY@+|jrX%s^&6b2i>5eqa0y%Z;^%^_=a@u3%4b9605ii3Ep)@`TAmhs0fpQ%O!q zl}XcFH*PieWwLj2ZSq`7V9Mc?h17`D)-+sNT-qs~3@?S(ldh7UlRlVXkWrK|vf6I- z?$tAVKYn8-l({mqQ$Q8{O!WzMg`0(=S&msXS#Pt$vrpzo=kRj+a`kh!z=6$;c zwT88(J6|n-WB%w`m$h~4pmp)YIh_ z3ETV2tjiAU!0h1dxU-n=E9e!)6|Z;4?!H=SSy{V>ut&IOq{_dl zbFb#!9eY1iCsp6Bajj|Hr?hX|zPbJE{X++w546-O*Ot`2Kgd0Jx6Z4syT zu9enWavU5N9)I?I-1m1*_?_rJ$vD~agVqoG+9++s?NEDe`%Fht$4F;X=in*dQ{7$m zU2Q)a|9JSc+Uc4zvS-T963!N$T{xF_ZuWe}`RNOZ7sk3{yB}PPym+f8xTpV;-=!;; zJuhGEb?H5K#o@~7t9DmUU1MD9xNd#Dz0azz?I)|B+WM{g+Xrk0I&awC=o(x)cy`EX z=)z6+o0o6-+`4{y+3mqQ%kSJBju{@g%f35#FZJHb`&swrA8dGtepviS>QUumrN{L@ z>;2q1Vm)$Z)P1z?N$8UYW2~{~zhwUMVZ87u`Dx{Z>O|9|`Q+&->FRy-Sjp7DHs zy69KwU-!MxeeuI@&cF4|M9z%AfP?@5 z`Tzg`fam}Kbua(`>RI+y?e7jT@qQ9J+u00v@9M??Vs0RI60puMM)00009a7bBm z000XU000XU0RWnu7ytkO2XskIMF-&s0S+iS2+tHT000MgNklQ%!tb(qpGVtEJ!K3GBY~ko;dg16A_}1fC^|ZY_M!FtguvAmN=GJ=2*^f zoMAb~a)IFz!wg~L1PX;xVGv;$U>IQ(VAQ~{?cViU7z8K-6am@@FX8wh@D)7_Zvt0N42fQ-osrfXqIwba>eZ747ma!#19~K8fHFcq zK+!^P1;yvk`wjSiIC2Dd8TjeNZ20!?{&**jFTK`!tU*qU`XeDb5cMO`H%iJM> zj{fww+w#sYKDKYa^#;b6M6PrUr+XFMzC~42hk!!*lWrA2RMm=f1FV)ye*eLTJRH9> zg6tFu>+m>)J6MW;eOL115AM;_4Ppdi3`R`47ZgE|9k>oz0->O22P#IQcK0#H-EXfb zSiWlD?}HrN3FOw1;W3<&k*~sCthK}#yWpvb$PS1|YA>+SF`}xd2&kelcGxrmf}*r{ z9WeADCy;rEC&O@z!v?vFs@@20jNqK-%P+sAZCieF|2|?29jjX=*nRit(Icv=;@x-O z#aatMj9r#cXkHOuWT8Fk0AyqY4sl3`FQcm642{8vAY$nE`#gB?01@G{&pzYu@F)Se zc@;&$!-o%9EEat7$tT>sd-q0vjFG|Z0g4)-xCJhAjgVW%u-#a5>);3=*jojIr10+G|I*4mt0w%;|bG1e>`PWDfe{D=&u!qfTZbJK;$Z z;`TDeV2uG0L>1`L5oKSTV9egLc5@aL4u^+a`lZ}nXJv%kEjj?bXHzeu(O^$xTk&;Z zb^~w4h_KCrh#(rT%NA}5))-6#6=gUWa_MGTLoY(+Cg2@p&*xV9We+jz7(mQ!knJTV zRYZEf^MZX>PC$$iRJTC7ar;G|v74DS_uIuPZpFD5@P7$2CN@+fO#=HPPLnN3^)$%=Ud|xZuriqQ| z?24*B5rui)F)*s3H-R#K>n0!&L%boTwr#n0?;arpthIy~5#4SD(|Vvv+qS&++G|W_ zGqNoEUhs~stIKodo8!>Hw1$oA4iCj8^xLO)l6)W%qW~R{#bUvypMHulhRvoX#*n;C zx-v+aST2`*^wCE^XW1cy-ORW9h3DV2%ptaL*(3lh6lc({|6QEXwkgIgo|{QFknc z&ja6mYkB&_Gk*5VW(^bI84z4|Y%0Hn^7HX4Wqyd9hIfz}ttu{j?_W(1(^!6t`U4xa;0fastA z$}=c`3&kHGe+k?vm}AI})~AP~^=W?q{Q`=dka? Date: Sun, 2 Dec 2018 14:26:41 +0100 Subject: [PATCH 02/46] Remove invalid iCCP chunk from icon --- libsrc/hyperion/hyperion-icon_32px.png | Bin 0 -> 1997 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libsrc/hyperion/hyperion-icon_32px.png diff --git a/libsrc/hyperion/hyperion-icon_32px.png b/libsrc/hyperion/hyperion-icon_32px.png new file mode 100644 index 0000000000000000000000000000000000000000..550ed55814de592f67233a70eca4fd8e84be49e6 GIT binary patch literal 1997 zcmV;;2Qv7HP)*RdS4;ea*+0(~4^I&>&#)1_dJ$Kwzz zU?2bjI0e&y3(&5>Z@`w&UxHh}(F}SMD8}%;FO)xiA-uP8stBL+0wuqo!`lcw6rT>q z0zyDI0Omk}#6qaNk;WUMdJ*;N)l&o?je5WXZJ?uR>_+GX=r+(=LHAQ|zfts`4iG#q zqWtvP?BI>x|M^B7kG)oUtU*qU`jJo^iTZ))t6jQ3XyFTw^A>9jB7&+CVxS2PRlT8H zm#|o4rfW6hWt6jXwNEQXivxLk@+0&5_@spPOYq4T4luO10gV3gk8ASg&p)(py#6Z2 z7ytxG4ClFuZvRD9^MZgv{>f7X5LLAz>44R8$sgW*pS$BX2T)vx?_0-LIJ(@x()st} zEd;U?BvVvI?6?jo`SB9i;J*hq}1Dk=i1XpA|+A|NPAbJGC_9^?eN-qE7KaE!+W zxrwS?5pIm&oag@i`!r3%Pj25vj3KeQWrE$l2M-=lmL+e!^%mAz0Afs5LZSYy00Rrn z2)uV3P*5|(BO$(sswNGM!H6JY==FNsxpM~*;oiM_42Po(;A-o1I^4Z`m&IbiM<0E} z&6_u`tj8GXU+bf&5juxp3$uondJd|MHHSwdfNc8=B6sgS-uv`y4c1zWu^5x*BG0{N zFU_^rM~6es+A9O?1Lzj6h9W?3eBotyG)QhHvU5Dij<{{c7_2cMf~W$?98vb26O7q= z)}*tjFgzG??v`?mqASpH6DT~ipG=($2K_xJ+d5wYvpaa}j0jshhzO$bGHl_h!y1E$ zprRb~4;cHIR?x1YaC7hmil=iYy>1&Z>=;1I?vQO0lOrPkzsm(%huq5<#t5ogAnDv* zr^mUQnKiU(D9i-h1l)LIrPXTfPLOTi3fcn5&&fQ3&3+)yd1ZPwJK<#0b~|0>W@A?1 z*5KS37z1X!F}&j4-ewRnmw-~pvtF+$%kql7lbQ9(D0}81ColkSJyW-03AV`q!~l~Z zm_qinX4fHQxo)S+JMX-+NBh;Ls`PrjJ#&FJTRzjCq9`bdG6x&MxbwU;lbp?VhwUV1 zO%kweYW=@jPR}U^re=(xM42a2#XU$jg}5vNT8EuurEID@r&5L0YQ<-N{fyD^=vy}5 zCa~MLZ?oBKc6C8Sh@_F6uGhdUsiy945H`>hMbu{1#7y>j+d;#Tvr|qJ!xE3k}%jux-}mYSl^fMpW$;?e^!}Vin2GxhL>> zRt&m_pfwb!MP7 zg|?1Y7wMvrH<$=&%(nn&hy;_oTJ{7!4$tDaO>7BLI_xE2{EQ1mgEDPY(~>Gy!`UZOlLESqIgbtV(a4kjM?Tmv|wtJ z8HySP=g@7wwv(I(A~6b(fGidZKK}S)j4^CB6)}dK+a!fSw#0I|o#pCQ&G_sYC)clC4Mmk*nP#uDsp?fGA}BdTHSTB^ z5u9@rMZuTY@O@sX`rHa_ZnSzFog8yGJfeR%px5iu z?sjnA<68yRSX7m|uBfVtx~eG41#K4mu zLdbdZ*Dpc)m(Y0^`a>ABVc?-9?bF)2aU;dFGH$vNvkut#T4dS4N?|4F+M;Elq3jT5 zeVX%rtj9-MJv)x|*&s3_08JY3Vf13NDn<6J2)($U4Sp3TR_WG7wMCIj%5Z+fr|`aOv4#9kEnS}#YLW%)SOe3 fKBZ$CQx^Ll=KNfan+oz000000NkvXXu0mjfgQ2oT literal 0 HcmV?d00001 From a8efe51b25d5c039a4710d5bc050e577fc7a5638 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Sun, 2 Dec 2018 14:32:34 +0100 Subject: [PATCH 03/46] Forward signal to all clients corrected --- src/hyperiond/hyperiond.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 867155bc..0eb9e90d 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -288,7 +288,7 @@ void HyperionDaemon::startNetworkServices() } _protoServer = new ProtoServer(protoPort); - QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SLOT(setVideoMode(VideoMode))); + QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SIGNAL(videoMode(VideoMode))); Info(_log, "Proto server created and started on port %d", _protoServer->getPort()); // Create Boblight server if configuration is present From 640ff5ac0cfa9b2357e68a5512dbc285a234f9dd Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:21:35 +0100 Subject: [PATCH 04/46] WebUI CSS Background Color set to white --- assets/webconfig/css/hyperion.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/webconfig/css/hyperion.css b/assets/webconfig/css/hyperion.css index b648a05c..6919f11c 100644 --- a/assets/webconfig/css/hyperion.css +++ b/assets/webconfig/css/hyperion.css @@ -176,6 +176,9 @@ table label{margin:0} .checklist{list-style-type:none;padding-left:0px} .checklist li::before{content: "\f00c";font: normal normal normal 14px/1 FontAwesome;margin-right:5px;color:green;font-size:19px} +/* bgwhite */ +.bg-w{background-color: white;} + /*Modal icons*/ [class*="modal-icon"]{ padding:30px; @@ -993,4 +996,4 @@ input[type="radio"] .styled:checked + label::before { input[type="checkbox"] .styled:checked + label::after, input[type="radio"] .styled:checked + label::after { color: #fff; -} \ No newline at end of file +} From 5731119a4f71df243c4947c1305f0b873a794970 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:22:43 +0100 Subject: [PATCH 05/46] Update translation file --- assets/webconfig/i18n/de.json | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index cafcd74f..45b65f4b 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -341,6 +341,7 @@ "wiz_cc_morethanone" : "Du hast mehr als 1 Profil, bitte wähle das zu kalibrierende Profil", "wiz_cc_btn_stop" : "Stoppe Video", "wiz_cc_summary" : "Im folgenden eine Zusammenfassung deiner Einstellungen. Während du ein Video abspielst, kannst du hier weiter ausprobieren. Wenn du fertig bist, klicke auf speichern.", + "edt_dev_auth_key_title" : "Aurora API Schlüssel", "edt_dev_enum_subtract_minimum" : "Subtrahiere minimum", "edt_dev_enum_sub_min_warm_adjust" : "Minimale Anpassung: warm", "edt_dev_enum_white_off" : "Weiß ist aus", @@ -502,7 +503,7 @@ "edt_conf_v4l2_cropBottom_title" : "Entferne unten", "edt_conf_v4l2_cropBottom_expl" : "Anzahl der Pixel auf der unteren Seite die vom Bild entfernt werden.", "edt_conf_v4l2_signalDetection_title" : "Signal Erkennung", - "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten.", + "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten. Das Bild muss dazu 4 Sekunden lang unter die Schwellwerte fallen.", "edt_conf_v4l2_redSignalThreshold_title" : "Rote Signalschwelle", "edt_conf_v4l2_redSignalThreshold_expl" : "Je höher die rote Schwelle je eher wird abgeschalten bei entsprechendem rot-Anteil.", "edt_conf_v4l2_greenSignalThreshold_title" : "Grüne Signalschwelle", @@ -593,9 +594,14 @@ "edt_eff_smooth_updateFrequency" : "Glättung: Aktualisierungsfrequenz", "edt_eff_candle_header" : "Kerze", "edt_eff_candle_header_desc" : "Flackerndes Kerzenlicht", + "edt_eff_waves_header" : "Wellen", + "edt_eff_waves_header_desc" : "Gestalte Wellen aus Farbe! Mische dazu deine lieblings Farben und wähle einen Mittelpunkt.", + "edt_eff_gif_header" : "GIF's", + "edt_eff_gif_header_desc" : "Dieser Effekt spielt .gif Dateien ab. Bietet die Möglichkeit kleine GIF-Videos abzuspielen.", "edt_eff_police_header" : "Polizei", "edt_eff_police_header_desc" : "Lights like a police car in action", "edt_eff_fade_header" : "Farbübergang", + "edt_eff_fade_header_desc" : "Farbübergange für alle LED's", "edt_eff_rainbowmood_header" : "Regenbogen", "edt_eff_rainbowmood_header_desc" : "Alle LEDs Regenbogen Farbübergang", "edt_eff_knightrider_header" : "Knight Rider", @@ -603,18 +609,24 @@ "edt_eff_lightclock_header" : "Lichtuhr", "edt_eff_lightclock_header_desc" : "Eine echte Uhr als Licht! Passe die Farben von Stunden, Minuten, Sekunden deinen Vorstellungen an. Optional können 3/6/9/12 Uhr Markierungen aktiviert werden. Sollte die Uhr eine falsche Zeit anzeigen, überprüfe die Uhrzeit deines Systems.", "edt_eff_pacman_header" : "Pac-Man", + "edt_eff_pacman_header_desc" : "Klein gefräßig und gelb, wer wird überleben?", "edt_eff_moodblobs_header" : "Stimmungskugeln", + "edt_eff_moodblobs_header_desc" : "Entspannt den Abend beginnen mit langsam bewegenden Farbkugeln die ebenso sanft ihre Farbe verändern.", "edt_eff_swirl_header" : "Farbwirbel", "edt_eff_swirl_header_desc" : "Ein Wirbel mit frei wählbaren Farben. Die Farben werden gleichmäßig auf 360° aufgeteilt, dazwischen werden Farbübergänge berechnet. Zusätzlich kann ein zweiter Wirbel über den Ersten gelegt werden (Transparenz beachten!). Tipp: Eine Widerholung der selben Farbe erhöht deren \"größe\" und verringert den Bereich des Farbübergangs zu benachbarten Farben.", "edt_eff_random_header" : "Zufällig", - "edt_eff_runningdots_header" : "Rennende Punkte", + "edt_eff_random_header_desc" : "Pixel-Farb-Mix", "edt_eff_systemshutdown_header" : "Herunterfahren", + "edt_eff_systemshutdown_header_desc" : "Eine kurze Animation gefolgt von einem möglicherweise echten Herunterfahren des Systems", "edt_eff_snake_header" : "Schlange", + "edt_eff_snake_header_desc" : "Wo ist das Futter?", "edt_eff_sparks_header" : "Funken", "edt_eff_sparks_header_desc" : "Ein Sternenfunkeln, wahlweise in festgelegter Farbe oder zufällig. Passe Helligkeit, Sättigung und Geschwindigkeit an.", "edt_eff_traces_header" : "Farbspuren", "edt_eff_x-mas_header" : "Weihnachten", - "edt_eff_trails_header" : "Spuren", + "edt_eff_x-mas_header_desc" : "Ein Hauch von Weihnachten", + "edt_eff_trails_header" : "Sternschnuppen", + "edt_eff_trails_header_desc" : "In verschiedenen Farben, wünsch dir was!", "edt_eff_flag_header" : "Flaggen", "edt_eff_flag_header_desc" : "Verpasse deinen LEDs die Farben deines Landes. Du kannst mehr als eine Flagge auswählen, je nach Intervall werden diese dann abwechselnd angezeigt.", "edt_eff_enum_all" : "Alle", @@ -679,6 +691,7 @@ "edt_eff_customColor" : "Benutzerdefinierte Farbe", "edt_eff_randomCenter" : "Zufälliger Mittelpunkt", "edt_eff_enableSecondSwirl":"Zweiter Wirbel", + "edt_eff_reverseRandomTime":"Richtungswechsel alle", "edt_append_ns" : "ns", "edt_append_ms" : "ms", "edt_append_s" : "s", From b4095d10e1c377c50c606105bc08988649f11701 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:23:09 +0100 Subject: [PATCH 06/46] Update translation file --- assets/webconfig/i18n/en.json | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index f7fd7102..5431fd04 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -341,6 +341,7 @@ "wiz_cc_morethanone" : "You have more than one profile, please choose the profile you want to calibrate.", "wiz_cc_btn_stop" : "Stop video", "wiz_cc_summary" : "A conclusion of your settings. During video playback, you could change or test values again. If you are done, click on save.", + "edt_dev_auth_key_title" : "Aurora API Key", "edt_dev_enum_subtract_minimum" : "Substract minimum", "edt_dev_enum_sub_min_warm_adjust" : "Min warm adjust", "edt_dev_enum_white_off" : "White off", @@ -493,7 +494,7 @@ "edt_conf_v4l2_frameDecimation_title" : "Frame decimation", "edt_conf_v4l2_frameDecimation_expl" : "The factor of frame decimation", "edt_conf_v4l2_sizeDecimation_title" : "Size decimation", - "edt_conf_v4l2_sizeDecimation_expl" : "The factor of size decimation", + "edt_conf_v4l2_sizeDecimation_expl" : "The factor of size decimation. 1 means no decimation (keep original size)", "edt_conf_v4l2_cropLeft_title" : "Crop left", "edt_conf_v4l2_cropLeft_expl" : "Count of pixels on the left side that are removed from the picture.", "edt_conf_v4l2_cropRight_title" : "Crop right", @@ -503,7 +504,7 @@ "edt_conf_v4l2_cropBottom_title" : "Crop bottom", "edt_conf_v4l2_cropBottom_expl" : "Count of pixels on the bottom side that are removed from the picture.", "edt_conf_v4l2_signalDetection_title" : "Signal detection", - "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found.", + "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found. This will happen when the picture fall below the threshold value for a period of 4 seconds.", "edt_conf_v4l2_redSignalThreshold_title" : "Red signal threshold", "edt_conf_v4l2_redSignalThreshold_expl" : "Darkens low red values (recognized as black)", "edt_conf_v4l2_greenSignalThreshold_title" : "Green signal threshold", @@ -592,6 +593,10 @@ "edt_eff_smooth_custom" : "Enable smoothing", "edt_eff_smooth_time_ms" : "Smoothing time", "edt_eff_smooth_updateFrequency" : "Smoothing update frequency", + "edt_eff_waves_header" : "Waves", + "edt_eff_waves_header_desc" : "Waves of color! Choose your colors, rotation time, direction reverse and more.", + "edt_eff_gif_header" : "GIF's", + "edt_eff_gif_header_desc" : "This effect plays .gif files, provide a simple video like loop as effect.", "edt_eff_candle_header" : "Candle", "edt_eff_candle_header_desc" : "Shimmering candles", "edt_eff_police_header" : "Police", @@ -605,20 +610,27 @@ "edt_eff_lightclock_header" : "Light Clock", "edt_eff_lightclock_header_desc" : "A real clock as light! Adjsut the colors of hours, minute, seconds. A optional 3/6/9/12 o'clock marker is also available. In case the clock is wrong, you need to check your system clock.", "edt_eff_pacman_header" : "Pac-Man", + "edt_eff_pacman_header_desc" : "Small hungry and yellow. Who will survive?", "edt_eff_moodblobs_header" : "Mood Blobs", + "edt_eff_moodblobs_header_desc" : "Relax at the evening with slow moving and color changing blobs.", "edt_eff_swirl_header" : "Color Swirl", "edt_eff_swirl_header_desc" : "A swirl with custom colors. Colors are even spread to 360°, in between colors shifts will be calcualted. Additional you can add a second swirl on top, be aware that you need partly transparency! Hint: A reapeat of the same color results in a \"hugher\" color area and a reduced color shift area.", "edt_eff_random_header" : "Random", - "edt_eff_runningdots_header" : "Running Dots", + "edt_eff_random_header_desc" : "Pixel Dot, dot, dot...", "edt_eff_systemshutdown_header" : "System Shutdown", + "edt_eff_systemshutdown_header_desc" : "A short animation with probably a real system shutdown", "edt_eff_snake_header" : "Snake", + "edt_eff_snake_header_desc" : "Where is something to eat?", "edt_eff_sparks_header" : "Sparks", "edt_eff_sparks_header_desc" : "Star-Sparking, choose between a static color or random. You could also adjust brightness, staturation and speed.", "edt_eff_traces_header" : "Color Traces", + "edt_eff_traces_header_desc" : "Requires redesign", "edt_eff_x-mas_header" : "X-Mas", - "edt_eff_trails_header" : "Trails", + "edt_eff_x-mas_header_desc" : "Touch of christmas", + "edt_eff_trails_header" : "Falling stars", + "edt_eff_trails_header_desc" : "Colored stars that fall from top to bottom", "edt_eff_flag_header" : "Flags", - "edt_eff_flag_header_desc" : "Let your leds shine bright in the colors of your country. You could select more then one flag, they will change based on interval time.", + "edt_eff_flag_header_desc" : "Let your leds shine bright in the colors of your country. You could select more than one flag, they will change based on interval time.", "edt_eff_enum_all" : "All", "edt_eff_enum_all-together" : "All together", "edt_eff_enum_list" : "LED List", @@ -630,6 +642,8 @@ "edt_eff_colorcount" : "Color length", "edt_eff_rotationtime" : "Rotation time", "edt_eff_sleeptime" : "Sleep time", + "edt_eff_image" : "Image file", + "edt_eff_fps" : "Frames per seconds", "edt_eff_reversedirection" : "Reverse direction", "edt_eff_fadeintime" : "Fade in time", "edt_eff_fadeouttime" : "Fade out time", @@ -681,6 +695,7 @@ "edt_eff_customColor" : "Custom Color", "edt_eff_randomCenter" : "Random Center", "edt_eff_enableSecondSwirl":"Second Swirl", + "edt_eff_reverseRandomTime":"Reverse every", "edt_append_ns" : "ns", "edt_append_ms" : "ms", "edt_append_s" : "s", From 0e68459850566143845ec6ff003f27768853e55c Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:29:18 +0100 Subject: [PATCH 07/46] custom effects can now be deleted again added comments on function --- assets/webconfig/js/content_effectsconfigurator.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index 9d7eae61..e339331e 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -70,6 +70,7 @@ $(document).ready( function() { }); }); + // disable or enable control elements $("#name-input").on('change keyup', function(event) { effectName = $(this).val(); if ($(this).val() == '') { @@ -81,6 +82,7 @@ $(document).ready( function() { } }); + // Save Effect $('#btn_write').off().on('click',function() { requestWriteEffect(effectName,effectPy,JSON.stringify(effects_editor.getValue())); $(hyperion).one("cmd-create-effect", function(event) { @@ -93,21 +95,25 @@ $(document).ready( function() { }); + // Start test $('#btn_start_test').off().on('click',function() { triggerTestEffect(); }); + // Stop test $('#btn_stop_test').off().on('click',function() { requestPriorityClear(); testrun = false; }); + // Continuous test $('#btn_cont_test').off().on('click',function() { toggleClass('#btn_cont_test', "btn-success", "btn-danger"); }); + // Delete Effect $('#btn_delete').off().on('click',function() { - var name = $("#effectsdellist").val(); + var name = $("#effectsdellist").val().split("_")[1]; requestDeleteEffect(name); $(hyperion).one("cmd-delete-effect", function(event) { if (event.response.success) @@ -115,11 +121,13 @@ $(document).ready( function() { }); }); + // disable or enable Delete Effect Button $('#effectsdellist').off().on('change', function(){ $(this).val() == null ? $('#btn_edit, #btn_delete').prop('disabled',true) : ""; $(this).val().startsWith("int_") ? $('#btn_delete').prop('disabled',true) : $('#btn_delete').prop('disabled',false); }); + // Load Effect $('#btn_edit').off().on('click', function(){ var name = $("#effectsdellist").val().replace("ext_",""); From 10a1047a462305f18cfe85d5d61657089ecf09cc Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:32:27 +0100 Subject: [PATCH 08/46] added aurora device to network selection --- assets/webconfig/js/content_leds.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index 2cf7e07b..81d02e64 100644 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -473,7 +473,7 @@ $(document).ready(function() { devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi']; devRPiPWM = ['ws281x']; devRPiGPIO = ['piblaster']; - devNET = ['atmoorb', 'fadecandy', 'philipshue', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw']; + devNET = ['atmoorb', 'fadecandy', 'philipshue', 'aurora', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw']; devUSB = ['adalight', 'dmx', 'atmo', 'hyperionusbasp', 'lightpack', 'multilightpack', 'paintpack', 'rawhid', 'sedu', 'tpm2', 'karate']; var optArr = [[]]; From 4c02b15f65bee461b7b74e5ffdc41c1d745181f9 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:33:28 +0100 Subject: [PATCH 09/46] speed improvements for live visualization --- assets/webconfig/js/ledsim.js | 201 +++++++++++++++++++++++++--------- 1 file changed, 149 insertions(+), 52 deletions(-) diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js index 92bd1383..698fc92c 100644 --- a/assets/webconfig/js/ledsim.js +++ b/assets/webconfig/js/ledsim.js @@ -5,23 +5,91 @@ $(document).ready(function() { var dialog; var leds; var lC = false; - + var imageCanvasNodeCtx; + var ledsCanvasNodeCtx; + var canvas_height; + var canvas_width; + var twoDPaths = []; + var toggleLeds = false; + + /// add prototype for simple canvas clear() method + CanvasRenderingContext2D.prototype.clear = function(){ + this.clearRect(0, 0, this.canvas.width, this.canvas.height) + }; + + function create2dPaths(){ + twoDPaths = []; + for(var idx=0; idx'+led.index+''; - } - $('#leds_canvas').html(leds_html); - - if($('#leds_toggle_num').hasClass('btn-success')) - $('.led_num').toggle(true); - - if($('#leds_toggle').hasClass('btn-danger')) - $('.led').toggle(false); - - $('#image_preview').attr("width" , canvas_width-1); - $('#image_preview').attr("height", canvas_height-1); - } - // ------------------------------------------------------------------ - $('#leds_toggle_num').off().on("click", function() { - $('.led_num').toggle(); - toggleClass('#leds_toggle_num', "btn-danger", "btn-success"); - }); + canvas_height = $('#ledsim_dialog').outerHeight()-$('#ledsim_text').outerHeight()-$('[data-role=footer]').outerHeight()-$('[data-role=header]').outerHeight()-40; + canvas_width = $('#ledsim_dialog').outerWidth()-30; + $('#leds_canvas').html(""); + leds_html = ''; + leds_html += ''; + + $('#leds_canvas').html(leds_html); + + imageCanvasNodeCtx = document.getElementById("image_preview_canv").getContext("2d"); + ledsCanvasNodeCtx = document.getElementById("leds_preview_canv").getContext("2d"); + create2dPaths(); + printLedsToCanvas(); + resetImage() + } + + // -----------------------FIX THIS------------------------- // ------------------------------------------------------------------ + // $('#leds_toggle_num').off().on("click", function() { + // $('.led_num').toggle(); + // toggleClass('#leds_toggle_num', "btn-danger", "btn-success"); + //}); + // ------------------------------------------------------------------ + $('#leds_toggle').off().on("click", function() { - $('.led').toggle(); + toggleLeds = !toggleLeds + ledsCanvasNodeCtx.clear(); toggleClass('#leds_toggle', "btn-success", "btn-danger"); }); @@ -106,14 +193,14 @@ $(document).ready(function() { if ( imageStreamActive ) { requestLedImageStop(); - $('#image_preview').removeAttr("src"); + resetImage(); } else { requestLedImageStart(); } }); - + // ------------------------------------------------------------------ $(hyperion).on("cmd-ledcolors-ledstream-update",function(event){ if (!modalOpened) @@ -122,15 +209,10 @@ $(document).ready(function() { } else { - ledColors = (event.response.result.leds); - for(var idx=0; idx Date: Thu, 20 Dec 2018 14:34:09 +0100 Subject: [PATCH 10/46] Delete hyperion-header.html --- doc/hyperion-header.html | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 doc/hyperion-header.html diff --git a/doc/hyperion-header.html b/doc/hyperion-header.html deleted file mode 100644 index 652afa69..00000000 --- a/doc/hyperion-header.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - $title - - - - From ca6adbc4dc722c6b172b05d973ab5fc004f37b2c Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:34:52 +0100 Subject: [PATCH 11/46] Delete hyperion-stylesheet.css --- doc/hyperion-stylesheet.css | 472 ------------------------------------ 1 file changed, 472 deletions(-) delete mode 100644 doc/hyperion-stylesheet.css diff --git a/doc/hyperion-stylesheet.css b/doc/hyperion-stylesheet.css deleted file mode 100644 index 2e81b75d..00000000 --- a/doc/hyperion-stylesheet.css +++ /dev/null @@ -1,472 +0,0 @@ -BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { - font-family: Geneva, Arial, Helvetica, sans-serif; -} -BODY,TD { - font-size: 90%; -} -H1 { - text-align: center; - font-size: 160%; -} -H2 { - font-size: 120%; -} -H3 { - font-size: 100%; -} -CAPTION { - font-weight: bold -} -DIV.qindex { - width: 100%; - background-color: #e8eef2; - border: 1px solid #84b0c7; - text-align: center; - margin: 2px; - padding: 2px; - line-height: 140%; -} -DIV.navpath { - width: 100%; - background-color: #e8eef2; - border: 1px solid #84b0c7; - text-align: center; - margin: 2px; - padding: 2px; - line-height: 140%; -} -DIV.navtab { - background-color: #e8eef2; - border: 1px solid #84b0c7; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; -} -TD.navtab { - font-size: 70%; -} -A.qindex { - text-decoration: none; - font-weight: bold; - color: #1A419D; -} -A.qindex:visited { - text-decoration: none; - font-weight: bold; - color: #1A419D -} -A.qindex:hover { - text-decoration: none; - background-color: #ddddff; -} -A.qindexHL { - text-decoration: none; - font-weight: bold; - background-color: #6666cc; - color: #ffffff; - border: 1px double #9295C2; -} -A.qindexHL:hover { - text-decoration: none; - background-color: #6666cc; - color: #ffffff; -} -A.qindexHL:visited { - text-decoration: none; - background-color: #6666cc; - color: #ffffff -} -A.el { - text-decoration: none; - font-weight: bold -} -A.elRef { - font-weight: bold -} -A.code:link { - text-decoration: none; - font-weight: normal; - color: #0000FF -} -A.code:visited { - text-decoration: none; - font-weight: normal; - color: #0000FF -} -A.codeRef:link { - font-weight: normal; - color: #0000FF -} -A.codeRef:visited { - font-weight: normal; - color: #0000FF -} -A:hover { - text-decoration: none; - background-color: #f2f2ff -} -DL.el { - margin-left: -1cm -} -.fragment { - font-family: monospace, fixed; - font-size: 95%; -} -PRE.fragment { - border: 1px solid #CCCCCC; - background-color: #f5f5f5; - margin-top: 4px; - margin-bottom: 4px; - margin-left: 2px; - margin-right: 8px; - padding-left: 6px; - padding-right: 6px; - padding-top: 4px; - padding-bottom: 4px; -} -DIV.ah { - background-color: black; - font-weight: bold; - color: #ffffff; - margin-bottom: 3px; - margin-top: 3px -} - -DIV.groupHeader { - margin-left: 16px; - margin-top: 12px; - margin-bottom: 6px; - font-weight: bold; -} -DIV.groupText { - margin-left: 16px; - font-style: italic; - font-size: 90% -} -BODY { - background: white; - color: black; - margin-right: 20px; - margin-left: 20px; -} -TD.indexkey { - background-color: #e8eef2; - font-weight: bold; - padding-right : 10px; - padding-top : 2px; - padding-left : 10px; - padding-bottom : 2px; - margin-left : 0px; - margin-right : 0px; - margin-top : 2px; - margin-bottom : 2px; - border: 1px solid #CCCCCC; -} -TD.indexvalue { - background-color: #e8eef2; - font-style: italic; - padding-right : 10px; - padding-top : 2px; - padding-left : 10px; - padding-bottom : 2px; - margin-left : 0px; - margin-right : 0px; - margin-top : 2px; - margin-bottom : 2px; - border: 1px solid #CCCCCC; -} -TR.memlist { - background-color: #f0f0f0; -} -P.formulaDsp { - text-align: center; -} -IMG.formulaDsp { -} -IMG.formulaInl { - vertical-align: middle; -} -SPAN.keyword { color: #008000 } -SPAN.keywordtype { color: #604020 } -SPAN.keywordflow { color: #e08000 } -SPAN.comment { color: #800000 } -SPAN.preprocessor { color: #806020 } -SPAN.stringliteral { color: #002080 } -SPAN.charliteral { color: #008080 } -SPAN.vhdldigit { color: #ff00ff } -SPAN.vhdlchar { color: #000000 } -SPAN.vhdlkeyword { color: #700070 } -SPAN.vhdllogic { color: #ff0000 } - -.mdescLeft { - padding: 0px 8px 4px 8px; - font-size: 80%; - font-style: italic; - background-color: #FAFAFA; - border-top: 1px none #E0E0E0; - border-right: 1px none #E0E0E0; - border-bottom: 1px none #E0E0E0; - border-left: 1px none #E0E0E0; - margin: 0px; -} -.mdescRight { - padding: 0px 8px 4px 8px; - font-size: 80%; - font-style: italic; - background-color: #FAFAFA; - border-top: 1px none #E0E0E0; - border-right: 1px none #E0E0E0; - border-bottom: 1px none #E0E0E0; - border-left: 1px none #E0E0E0; - margin: 0px; -} -.memItemLeft { - padding: 1px 0px 0px 8px; - margin: 4px; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-top-color: #E0E0E0; - border-right-color: #E0E0E0; - border-bottom-color: #E0E0E0; - border-left-color: #E0E0E0; - border-top-style: solid; - border-right-style: none; - border-bottom-style: none; - border-left-style: none; - background-color: #FAFAFA; - font-size: 80%; -} -.memItemRight { - padding: 1px 8px 0px 8px; - margin: 4px; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-top-color: #E0E0E0; - border-right-color: #E0E0E0; - border-bottom-color: #E0E0E0; - border-left-color: #E0E0E0; - border-top-style: solid; - border-right-style: none; - border-bottom-style: none; - border-left-style: none; - background-color: #FAFAFA; - font-size: 80%; -} -.memTemplItemLeft { - padding: 1px 0px 0px 8px; - margin: 4px; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-top-color: #E0E0E0; - border-right-color: #E0E0E0; - border-bottom-color: #E0E0E0; - border-left-color: #E0E0E0; - border-top-style: none; - border-right-style: none; - border-bottom-style: none; - border-left-style: none; - background-color: #FAFAFA; - font-size: 80%; -} -.memTemplItemRight { - padding: 1px 8px 0px 8px; - margin: 4px; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-top-color: #E0E0E0; - border-right-color: #E0E0E0; - border-bottom-color: #E0E0E0; - border-left-color: #E0E0E0; - border-top-style: none; - border-right-style: none; - border-bottom-style: none; - border-left-style: none; - background-color: #FAFAFA; - font-size: 80%; -} -.memTemplParams { - padding: 1px 0px 0px 8px; - margin: 4px; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-top-color: #E0E0E0; - border-right-color: #E0E0E0; - border-bottom-color: #E0E0E0; - border-left-color: #E0E0E0; - border-top-style: solid; - border-right-style: none; - border-bottom-style: none; - border-left-style: none; - color: #606060; - background-color: #FAFAFA; - font-size: 80%; -} -.search { - color: #003399; - font-weight: bold; -} -FORM.search { - margin-bottom: 0px; - margin-top: 0px; -} -INPUT.search { - font-size: 75%; - color: #000080; - font-weight: normal; - background-color: #e8eef2; -} -TD.tiny { - font-size: 75%; -} -a { - color: #1A41A8; -} -a:visited { - color: #2A3798; -} -.dirtab { - padding: 4px; - border-collapse: collapse; - border: 1px solid #84b0c7; -} -TH.dirtab { - background: #e8eef2; - font-weight: bold; -} -HR { - height: 1px; - border: none; - border-top: 1px solid black; -} - -/* Style for detailed member documentation */ -.memtemplate { - font-size: 80%; - color: #606060; - font-weight: normal; - margin-left: 3px; -} -.memnav { - background-color: #e8eef2; - border: 1px solid #84b0c7; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; -} -.memitem { - padding: 4px; - background-color: #eef3f5; - border-width: 1px; - border-style: solid; - border-color: #dedeee; - -moz-border-radius: 8px 8px 8px 8px; -} -.memname { - white-space: nowrap; - font-weight: bold; -} -.memdoc { - padding-left: 10px; -} -.memproto { - background-color: #d5e1e8; - width: 100%; - border-width: 1px; - border-style: solid; - border-color: #84b0c7; - font-weight: bold; - -moz-border-radius: 8px 8px 8px 8px; -} -.paramkey { - text-align: right; -} -.paramtype { - white-space: nowrap; -} -.paramname { - color: #602020; - font-style: italic; - white-space: nowrap; -} -/* End Styling for detailed member documentation */ - -/* for the tree view */ -.ftvtree { - font-family: sans-serif; - margin:0.5em; -} -/* these are for tree view when used as main index */ -.directory { - font-size: 9pt; - font-weight: bold; -} -.directory h3 { - margin: 0px; - margin-top: 1em; - font-size: 11pt; -} - -/* The following two styles can be used to replace the root node title */ -/* with an image of your choice. Simply uncomment the next two styles, */ -/* specify the name of your image and be sure to set 'height' to the */ -/* proper pixel height of your image. */ - -/* .directory h3.swap { */ -/* height: 61px; */ -/* background-repeat: no-repeat; */ -/* background-image: url("yourimage.gif"); */ -/* } */ -/* .directory h3.swap span { */ -/* display: none; */ -/* } */ - -.directory > h3 { - margin-top: 0; -} -.directory p { - margin: 0px; - white-space: nowrap; -} -.directory div { - display: none; - margin: 0px; -} -.directory img { - vertical-align: -30%; -} -/* these are for tree view when not used as main index */ -.directory-alt { - font-size: 100%; - font-weight: bold; -} -.directory-alt h3 { - margin: 0px; - margin-top: 1em; - font-size: 11pt; -} -.directory-alt > h3 { - margin-top: 0; -} -.directory-alt p { - margin: 0px; - white-space: nowrap; -} -.directory-alt div { - display: none; - margin: 0px; -} -.directory-alt img { - vertical-align: -30%; -} From 8326c122fea916bc0500573a2bef5fcd79dcea50 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:35:02 +0100 Subject: [PATCH 12/46] Delete hyperion-build-doc.in.sh --- doc/hyperion-build-doc.in.sh | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 doc/hyperion-build-doc.in.sh diff --git a/doc/hyperion-build-doc.in.sh b/doc/hyperion-build-doc.in.sh deleted file mode 100644 index 3198611d..00000000 --- a/doc/hyperion-build-doc.in.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -# Fail on error. -set -e - -# Log file containing documentation errors and warnings (if any). -log_file=${CMAKE_CURRENT_BINARY_DIR}/hyperion-doxygen.log - -# Remove the log file before building the documentation. -rm -f $log_file - -# Generate the documentation. -${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/hyperion.doxygen - -# At this point, the log file should have been generated. -# If not, an error is displayed on stderr and 1 is returned to indicate an error. -if [ -f $log_file ] ; then - # So the log file exists. If its size is > 0, show its contents on stderr and exit 1. - if [ -s $log_file ] ; then - cat $log_file 1>&2 - exit 1; - else - # The log file exists, but its size is zero, meaning there were no documentation warnings or errors. - # Exit with 0 to indicate success. - exit 0; - fi -else - echo "The doxygen log file ($log_file) does not exist. Ensure that WARN_LOGFILE is set correctly in hyperion-cmake.doxyfile." 1>&2 - exit 1; -fi From bb79589e3ef49642fba71e1180713d746ae2bb81 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:36:13 +0100 Subject: [PATCH 13/46] doxygen fix for documentation --- doc/CMakeLists.txt | 50 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index dc5e88fd..5f90eced 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,32 +1,34 @@ +option(BUILD_HYPERION_DOC "Build hyperion documentation" OFF) -# Find doxygen +# Find doxygen and check if Doxygen is installed find_package(Doxygen QUIET) -# This processes our hyperion-cmake.doxyfile and subsitutes variables to generate a final hyperion.doxyfile -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hyperion.in.doxygen ${CMAKE_CURRENT_BINARY_DIR}/hyperion.doxygen) +if (BUILD_HYPERION_DOC) + if (DOXYGEN_FOUND) -# This processes the shell script that is used to build the documentation and check the result -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hyperion-build-doc.in.sh ${CMAKE_CURRENT_BINARY_DIR}/hyperion-build-doc.sh) + # set input and output files + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/hyperion.in.doxygen) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/hyperion.doxygen) -# Define all static (i.e. not generated) documentation files -set(StaticDocumentationFiles hyperion-header.html hyperion-footer.html hyperion-stylesheet.css) + # request to configure the file + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + message(STATUS "Doxygen build started") -# 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() + # Define all static (i.e. not generated) documentation files + set(StaticDocumentationFileshyperion-footer.html) -if(DOXYGEN_FOUND) - option(BuildDocumentationSearchEngine "Enable doxygen's search engine (requires that documentation to be installed on a php enabled web server)" OFF) - if(BuildDocumentationSearchEngine) - set(DOXYGEN_SEARCHENGINE YES) - else(BuildDocumentationSearchEngine) - set(DOXYGEN_SEARCHENGINE NO) - endif(BuildDocumentationSearchEngine) + # 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() - #Create a custom target to build documentation. It runs doxygen aginast the generated hyperion.doxyfile and checks its return value - add_custom_target(doc sh ${CMAKE_CURRENT_BINARY_DIR}/hyperion-build-doc.sh) -else(DOXYGEN_FOUND) - message(WARNING "Doxygen not found, unable to generate documenation!") -endif(DOXYGEN_FOUND) + 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() From 58f184b1a108535731e81a2e84761ffe554177b3 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:36:36 +0100 Subject: [PATCH 14/46] Update hyperion.in.doxygen --- doc/hyperion.in.doxygen | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/hyperion.in.doxygen b/doc/hyperion.in.doxygen index 5c91fc5f..69c37750 100644 --- a/doc/hyperion.in.doxygen +++ b/doc/hyperion.in.doxygen @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = "${CMAKE_CURRENT_BINARY_DIR}" +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -152,7 +152,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = ${CMAKE_SOURCE_DIR} +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -409,7 +409,7 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. @@ -742,7 +742,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = ${CMAKE_CURRENT_BINARY_DIR}/hyperion-doxygen.log +WARN_LOGFILE = @CMAKE_CURRENT_BINARY_DIR@/hyperion-doxygen.log #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -754,10 +754,10 @@ WARN_LOGFILE = ${CMAKE_CURRENT_BINARY_DIR}/hyperion-doxygen.log # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = "${CMAKE_SOURCE_DIR}/include" \ - "${CMAKE_SOURCE_DIR}/libsrc" \ - "${CMAKE_SOURCE_DIR}/src" \ - "${CMAKE_SOURCE_DIR}/test" +INPUT = @CMAKE_SOURCE_DIR@/include \ + @CMAKE_SOURCE_DIR@/libsrc \ + @CMAKE_SOURCE_DIR@/src \ + @CMAKE_SOURCE_DIR@/test # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1050,7 +1050,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = "html/hyperion-header.html" +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1482,7 +1482,7 @@ MATHJAX_CODEFILE = # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -SEARCHENGINE = ${DOXYGEN_SEARCHENGINE} +SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. There @@ -2080,7 +2080,7 @@ DOT_NUM_THREADS = 0 # The default value is: Helvetica. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = FreeSans +DOT_FONTNAME = # The DOT_FONTSIZE tag can be used to set the size (in points) of the font of # dot graphs. @@ -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 = "${DOXYGEN_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 From cf016efbce4bf637917b0ce91333beaa78d645e9 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:41:21 +0100 Subject: [PATCH 15/46] improvements --- effects/gif.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/effects/gif.py b/effects/gif.py index 4463f782..a210f4ff 100644 --- a/effects/gif.py +++ b/effects/gif.py @@ -6,8 +6,10 @@ framesPerSecond = float(hyperion.args.get('fps', 25)) reverse = bool(hyperion.args.get('reverse', False)) sleepTime = 1./framesPerSecond +imageList = [] + if imageFile: - imageList = list(reversed(hyperion.getImage(imageFile))) if reverse else hyperion.getImage(imageFile) + imageList = [reversed(hyperion.getImage(imageFile))] if reverse else hyperion.getImage(imageFile) # Start the write data loop while not hyperion.abort() and imageList: From d393dc46fd39a3ca99b0f70a8a2c56c9bb5c9d38 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:43:41 +0100 Subject: [PATCH 16/46] Delete running_dots.json --- effects/running_dots.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 effects/running_dots.json diff --git a/effects/running_dots.json b/effects/running_dots.json deleted file mode 100644 index 8accd451..00000000 --- a/effects/running_dots.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name" : "Running dots", - "script" : "running_dots.py", - "args" : - { - "speed" : 1.5, - "whiteLevel" : 100, - "colorLevel" : 230 - } -} From 8d32a035e7fe8b27ca059c98f05c281d21fe670f Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:44:21 +0100 Subject: [PATCH 17/46] Delete running_dots.schema.json --- effects/schema/running_dots.schema.json | 32 ------------------------- 1 file changed, 32 deletions(-) delete mode 100644 effects/schema/running_dots.schema.json diff --git a/effects/schema/running_dots.schema.json b/effects/schema/running_dots.schema.json deleted file mode 100644 index 5661bde7..00000000 --- a/effects/schema/running_dots.schema.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type":"object", - "script" : "running_dots.py", - "title":"edt_eff_runningdots_header", - "required":true, - "properties":{ - "speed": { - "type": "number", - "title":"edt_eff_speed", - "default": 1.5, - "minimum" : 0.1, - "propertyOrder" : 1 - }, - "colorLevel": { - "type": "integer", - "title":"edt_eff_colorevel", - "default": 220, - "minimium" : 0, - "maximum" : 255, - "propertyOrder" : 2 - }, - "whiteLevel": { - "type": "integer", - "title":"edt_eff_whitelevel", - "default": 0, - "minimium" : 0, - "maximum" : 254, - "propertyOrder" : 3 - } - }, - "additionalProperties": false -} From 92db744388b83adf101f09d23272a066c0a56d69 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:47:16 +0100 Subject: [PATCH 18/46] Wave effect schema --- effects/schema/waves.schema.json | 121 +++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 effects/schema/waves.schema.json diff --git a/effects/schema/waves.schema.json b/effects/schema/waves.schema.json new file mode 100644 index 00000000..10da7c8a --- /dev/null +++ b/effects/schema/waves.schema.json @@ -0,0 +1,121 @@ +{ + "type":"object", + "script" : "waves.py", + "title":"edt_eff_waves_header", + "required":true, + "properties":{ + "reverse": { + "type": "boolean", + "title":"edt_eff_reversedirection", + "default": false, + "propertyOrder" : 1 + }, + "reverse_time": { + "type": "integer", + "title":"edt_eff_reverseRandomTime", + "default": 0, + "minimum": 0, + "append" : "edt_append_s", + "propertyOrder" : 2 + }, + "rotation_time": { + "type": "integer", + "title":"edt_eff_rotationtime", + "default": 50, + "minimum": 1, + "append" : "edt_append_s", + "propertyOrder" : 2 + }, + "random-center": { + "type": "boolean", + "title":"edt_eff_randomCenter", + "default": false, + "propertyOrder" : 3 + }, + "center_x": { + "type": "number", + "title":"edt_eff_centerx", + "default": -0.15, + "minimum" : -3.0, + "maximum" : 4.0, + "step" : 0.1, + "options": { + "dependencies": { + "random-center": false + } + }, + "propertyOrder" : 4 + }, + "center_y": { + "type": "number", + "title":"edt_eff_centery", + "default": -0.15, + "minimum" : -3.0, + "maximum" : 4.0, + "step" : 0.1, + "options": { + "dependencies": { + "random-center": false + } + }, + "propertyOrder" : 5 + }, + "smoothing-custom-settings" : + { + "type" : "boolean", + "title" : "edt_eff_smooth_custom", + "default" : true, + "propertyOrder" : 6 + }, + "smoothing-time_ms" : + { + "type" : "integer", + "title" : "edt_eff_smooth_time_ms", + "minimum" : 25, + "maximum": 600, + "default" : 200, + "append" : "edt_append_ms", + "options": { + "dependencies": { + "smoothing-custom-settings": true + } + }, + "propertyOrder" : 7 + }, + "smoothing-updateFrequency" : + { + "type" : "number", + "title" : "edt_eff_smooth_updateFrequency", + "minimum" : 1.0, + "maximum" : 100.0, + "default" : 25.0, + "append" : "edt_append_hz", + "options": { + "dependencies": { + "smoothing-custom-settings": true + } + }, + "propertyOrder" : 8 + }, + "colors": { + "type": "array", + "title":"edt_eff_customColor", + "items" : { + "type": "array", + "title" : "edt_eff_color", + "format":"colorpicker", + "default" : [255,0,0], + "items":{ + "type":"integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3 + }, + "minItems": 6, + "propertyOrder" : 9 + } + }, + "additionalProperties": false +} From fbcc46ed28ce0d4b9a87ec080c4f71a952fc5830 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 14:49:34 +0100 Subject: [PATCH 19/46] Wave effect --- effects/Seawaves.json | 47 ++++++++++++++++++++++++++ effects/Waves.json | 8 +++++ effects/waves.py | 77 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 effects/Seawaves.json create mode 100644 effects/Waves.json create mode 100644 effects/waves.py diff --git a/effects/Seawaves.json b/effects/Seawaves.json new file mode 100644 index 00000000..cf3c30c9 --- /dev/null +++ b/effects/Seawaves.json @@ -0,0 +1,47 @@ +{ + "args": { + "center_x": 1.25, + "center_y": -0.25, + "colors": [ + [ + 8, + 0, + 255 + ], + [ + 0, + 161, + 255 + ], + [ + 0, + 222, + 255 + ], + [ + 0, + 153, + 255 + ], + [ + 38, + 0, + 255 + ], + [ + 0, + 199, + 255 + ] + ], + "random-center": false, + "reverse": false, + "reverse_time": 0, + "rotation_time": 60, + "smoothing-custom-settings": true, + "smoothing-time_ms": 200, + "smoothing-updateFrequency": 25 + }, + "name": "Sea waves", + "script": "waves.py" +} diff --git a/effects/Waves.json b/effects/Waves.json new file mode 100644 index 00000000..b4ae8b14 --- /dev/null +++ b/effects/Waves.json @@ -0,0 +1,8 @@ +{ + "name" : "Waves with Color", + "script" : "waves.py", + "args" : + { + "reverse" : false + } +} diff --git a/effects/waves.py b/effects/waves.py new file mode 100644 index 00000000..b253ec79 --- /dev/null +++ b/effects/waves.py @@ -0,0 +1,77 @@ +import hyperion, time, math, random + +randomCenter = bool(hyperion.args.get('random-center', False)) +centerX = float(hyperion.args.get('center_x', -0.15)) +centerY = float(hyperion.args.get('center_y', -0.25)) +rotationTime = float(hyperion.args.get('rotation_time', 90)) +colors = hyperion.args.get('colors', ((255,0,0),(255,255,0),(0,255,0),(0,255,255),(0,0,255),(255,0,255))) +reverse = bool(hyperion.args.get('reverse', False)) +reverseTime = int(hyperion.args.get('reverse_time', 0)) +#rotate = bool(hyperion.args.get('rotate', True)) +positions = [] + +# calc center if random +if randomCenter: + centerX = random.uniform(0.0, 1.0) + centerY = random.uniform(0.0, 1.0) + +rCenterX = int(round(float(hyperion.imageWidth())*centerX)) +rCenterY = int(round(float(hyperion.imageHeight())*centerY)) + +#calc interval +sleepTime = max(1/(255/rotationTime), 0.016) + +#calc diagonal +if centerX < 0.5: + cX = 1.0-centerX +else: + cX = 0.0+centerX + +if centerY < 0.5: + cY = 1.0-centerY +else: + cY = 0.0+centerY + +diag = int(round(math.sqrt(((cX*hyperion.imageWidth())**2)+((cY*hyperion.imageHeight())**2)))) +# some diagonal overhead +diag = int(diag*1.3) + +# calc positions +pos = 0 +step = int(255/len(colors)) +for entry in colors: + positions.append(pos) + pos += step + +# target time +targetTime = time.time()+float(reverseTime) + +#hyperion.imageCOffset(int(hyperion.imageWidth()/2), int(hyperion.imageHeight()/2)) + +while not hyperion.abort(): + # verify reverseTime, randomize reverseTime based on reverseTime up to reversedTime*2 + if reverseTime >= 1: + now = time.time() + if now > targetTime: + reverse = not reverse + targetTime = time.time()+random.uniform(float(reverseTime), float(reverseTime*2.0)) + # apply rotate + #if rotate: + # hyperion.imageCRotate(1) + # prepare bytearray with colors and positions + gradientBa = bytearray() + it = 0 + for color in colors: + gradientBa += bytearray((positions[it],color[0],color[1],color[2])) + it += 1 + + hyperion.imageRadialGradient(rCenterX,rCenterY, diag, gradientBa,0) + + # increment positions + for i, pos in enumerate(positions): + if reverse: + positions[i] = pos - 1 if pos >= 1 else 255 + else: + positions[i] = pos + 1 if pos <= 254 else 0 + hyperion.imageShow() + time.sleep(sleepTime) From ccbfe4c5b3af686501688e84b2fd56d533b1ed4c Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:01:46 +0100 Subject: [PATCH 20/46] added new signal rawLedColors --- include/hyperion/Hyperion.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index e17d853c..28c34c4d 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -354,6 +354,11 @@ signals: /// Signal emitted when a 3D movie is detected void videoMode(VideoMode mode); + /// + /// @brief Emits whenever new untransformed ledColos data is available, reflects the current visible device + /// + void rawLedColors(const std::vector& ledValues); + private slots: /// /// Updates the priority muxer with the current time and (re)writes the led color with applied @@ -416,7 +421,7 @@ private: QTimer _timer; QTimer _timerBonjourResolver; - /// buffer for leds + /// buffer for leds (with adjustment) std::vector _ledBuffer; /// Logger instance From 4c81614c69e605cef24cc509b48f45deab97e2ae Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:05:38 +0100 Subject: [PATCH 21/46] add led color streaming mutex and timeout for led color refresh --- include/utils/JsonProcessor.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/include/utils/JsonProcessor.h b/include/utils/JsonProcessor.h index 5ccd8e81..e256ff79 100644 --- a/include/utils/JsonProcessor.h +++ b/include/utils/JsonProcessor.h @@ -7,7 +7,6 @@ #include // qt includess -#include #include #include #include @@ -64,8 +63,11 @@ public: void forceServerInfo(); public slots: - /// _timer_ledcolors requests ledcolor updates (if enabled) - void streamLedcolorsUpdate(); + /// + /// @brief is called whenever the current Hyperion instance pushes new led raw values (if enabled) + /// @param ledColors The current ledColors + /// + void streamLedcolorsUpdate(const std::vector& ledColors); /// push images whenever hyperion emits (if enabled) void setImage(int priority, const Image & image, int duration_ms); @@ -107,23 +109,26 @@ private: /// returns if hyperion is on or off inline bool hyperionIsActive() { return JsonProcessor::_componentsPrevState.empty(); }; - /// timer for ledcolors streaming - QTimer _timer_ledcolors; - // streaming buffers QJsonObject _streaming_leds_reply; QJsonObject _streaming_image_reply; QJsonObject _streaming_logging_reply; + bool _ledcolorsLedsActive = false; /// flag to determine state of log streaming bool _streaming_logging_activated; /// mutex to determine state of image streaming QMutex _image_stream_mutex; + /// mutex to determine state of led color streaming + QMutex _led_stream_mutex; /// timeout for live video refresh volatile qint64 _image_stream_timeout; + /// timeout for led color refresh + volatile qint64 _led_stream_timeout; + /// /// Handle an incoming JSON Color message /// From 669501a0847964b8754c9de0856f859fbcdf59a4 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:10:39 +0100 Subject: [PATCH 22/46] LED stream set to max 20hz --- libsrc/utils/JsonProcessor.cpp | 58 +++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/libsrc/utils/JsonProcessor.cpp b/libsrc/utils/JsonProcessor.cpp index b1d6dc64..5e114f6c 100644 --- a/libsrc/utils/JsonProcessor.cpp +++ b/libsrc/utils/JsonProcessor.cpp @@ -14,9 +14,9 @@ #include #include #include -#include -#include -#include + #include + #include + #include #include // hyperion includes @@ -43,6 +43,7 @@ JsonProcessor::JsonProcessor(QString peerAddress, Logger* log, QObject* parent, , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _streaming_logging_activated(false) , _image_stream_timeout(0) + , _led_stream_timeout(0) { // notify hyperion about a jsonMessageForward connect(this, &JsonProcessor::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); @@ -55,10 +56,8 @@ JsonProcessor::JsonProcessor(QString peerAddress, Logger* log, QObject* parent, connect(_hyperion, &Hyperion::sendServerInfo, this, &JsonProcessor::forceServerInfo); } - // led color stream update timer - _timer_ledcolors.setSingleShot(false); - connect(&_timer_ledcolors, SIGNAL(timeout()), this, SLOT(streamLedcolorsUpdate())); _image_stream_mutex.unlock(); + _led_stream_mutex.unlock(); } void JsonProcessor::handleMessage(const QString& messageString) @@ -980,11 +979,13 @@ void JsonProcessor::handleLedColorsCommand(const QJsonObject& message, const QSt _streaming_leds_reply["success"] = true; _streaming_leds_reply["command"] = command+"-ledstream-update"; _streaming_leds_reply["tan"] = tan; - _timer_ledcolors.start(125); + connect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate, Qt::UniqueConnection); + _ledcolorsLedsActive = true; } else if (subcommand == "ledstream-stop") { - _timer_ledcolors.stop(); + disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate); + _ledcolorsLedsActive = false; } else if (subcommand == "imagestream-start") { @@ -1095,27 +1096,32 @@ void JsonProcessor::sendErrorReply(const QString &error, const QString &command, } -void JsonProcessor::streamLedcolorsUpdate() +void JsonProcessor::streamLedcolorsUpdate(const std::vector& ledColors) { - QJsonObject result; - QJsonArray leds; - - const PriorityMuxer::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()); - for(auto color = priorityInfo.ledColors.begin(); color != priorityInfo.ledColors.end(); ++color) + if ( (_led_stream_timeout+50) < QDateTime::currentMSecsSinceEpoch() && _led_stream_mutex.tryLock(0) ) { - QJsonObject item; - item["index"] = int(color - priorityInfo.ledColors.begin()); - item["red"] = color->red; - item["green"] = color->green; - item["blue"] = color->blue; - leds.append(item); + _led_stream_timeout = QDateTime::currentMSecsSinceEpoch(); + QJsonObject result; + QJsonArray leds; + + for(auto color = ledColors.begin(); color != ledColors.end(); ++color) + { + QJsonObject item; + item["index"] = int(color - ledColors.begin()); + item["red"] = color->red; + item["green"] = color->green; + item["blue"] = color->blue; + leds.append(item); + } + + result["leds"] = leds; + _streaming_leds_reply["result"] = result; + + // send the result + emit callbackMessage(_streaming_leds_reply); + + _led_stream_mutex.unlock(); } - - result["leds"] = leds; - _streaming_leds_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_leds_reply); } void JsonProcessor::setImage(int priority, const Image & image, int duration_ms) From 87c9c23807d4af0a5bdcc2c8b25c1f54aa265c82 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:12:45 +0100 Subject: [PATCH 23/46] emit rawLedColors before transform --- libsrc/hyperion/Hyperion.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 32dc9f0d..c626b7db 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -840,6 +840,9 @@ void Hyperion::update() _ledBuffer.reserve(_hwLedCount); _ledBuffer = priorityInfo.ledColors; + // emit rawLedColors before transform + emit rawLedColors(_ledBuffer); + if (priorityInfo.componentId != _prevCompId) { bool backlightEnabled = (priorityInfo.componentId != hyperion::COMP_COLOR && priorityInfo.componentId != hyperion::COMP_EFFECT); From 46dbede8f4e4904d1846093322395bfa133f9613 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:13:45 +0100 Subject: [PATCH 24/46] add aurora schema to resource file --- libsrc/leddevice/LedDeviceSchemas.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc index 9536835f..db9100aa 100644 --- a/libsrc/leddevice/LedDeviceSchemas.qrc +++ b/libsrc/leddevice/LedDeviceSchemas.qrc @@ -32,5 +32,6 @@ schemas/schema-apa104.json schemas/schema-ws281x.json schemas/schema-karate.json + schemas/schema-aurora.json From a78d8d3a03eba18ccf6ca7bcbdbbe5a260bc47f4 Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:15:07 +0100 Subject: [PATCH 25/46] Removed ColorOrder from schema --- libsrc/leddevice/schemas/schema-aurora.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libsrc/leddevice/schemas/schema-aurora.json b/libsrc/leddevice/schemas/schema-aurora.json index b79c560b..a116149f 100644 --- a/libsrc/leddevice/schemas/schema-aurora.json +++ b/libsrc/leddevice/schemas/schema-aurora.json @@ -11,12 +11,6 @@ "type": "string", "title":"edt_dev_auth_key_title", "propertyOrder" : 2 - }, - "colorOrder": { - "type": "string", - "title":"edt_dev_color_order_title", - "default": "rgb", - "propertyOrder" : 3 } }, "additionalProperties": true From e3be03ea73235df7fbab783ccafb8a42a85159bc Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Thu, 20 Dec 2018 15:16:23 +0100 Subject: [PATCH 26/46] Close client connection on stop --- libsrc/webconfig/QtHttpServer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libsrc/webconfig/QtHttpServer.cpp b/libsrc/webconfig/QtHttpServer.cpp index 89207434..d58b6aed 100644 --- a/libsrc/webconfig/QtHttpServer.cpp +++ b/libsrc/webconfig/QtHttpServer.cpp @@ -64,6 +64,12 @@ void QtHttpServer::start (quint16 port) { void QtHttpServer::stop (void) { if (m_sockServer->isListening ()) { m_sockServer->close (); + // disconnect clients + const QList socks = m_socksClientsHash.keys(); + for(auto sock : socks) + { + sock->close(); + } emit stopped (); } } From d762aa2f3e370b857f501158261ce2a1ba6a5b3f Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Thu, 27 Dec 2018 23:11:32 +0100 Subject: [PATCH 27/46] Details coming soon. --- assets/webconfig/i18n/de.json | 21 +- assets/webconfig/i18n/en.json | 11 +- assets/webconfig/index.html | 3 + assets/webconfig/js/content_dashboard.js | 47 +- assets/webconfig/js/content_effects.js | 5 +- .../js/content_effectsconfigurator.js | 5 +- assets/webconfig/js/content_grabber.js | 49 +- assets/webconfig/js/content_index.js | 95 +- assets/webconfig/js/content_network.js | 28 +- assets/webconfig/js/content_remote.js | 64 +- assets/webconfig/js/hyperion.js | 22 +- .../webconfig/js/lib/bootstrap-notify.min.js | 2 + assets/webconfig/js/ui_utils.js | 178 ++- config/hyperion.config.json.commented | 31 +- config/hyperion.config.json.default | 21 +- include/blackborder/BlackBorderProcessor.h | 71 +- include/boblightserver/BoblightServer.h | 33 +- include/bonjour/bonjourbrowserwrapper.h | 68 + include/bonjour/bonjourserviceregister.h | 3 +- include/effectengine/Effect.h | 89 ++ include/effectengine/EffectEngine.h | 19 +- .../effectengine/EffectModule.h | 79 +- include/grabber/AmlogicWrapper.h | 3 +- include/grabber/DispmanxFrameGrabber.h | 13 +- include/grabber/DispmanxWrapper.h | 6 +- include/grabber/FramebufferFrameGrabber.h | 11 +- include/grabber/FramebufferWrapper.h | 2 +- include/grabber/OsxFrameGrabber.h | 13 +- include/grabber/OsxWrapper.h | 3 +- include/grabber/V4L2Grabber.h | 51 +- include/grabber/V4L2Wrapper.h | 14 +- include/grabber/X11Grabber.h | 36 +- include/grabber/X11Wrapper.h | 3 +- include/hyperion/BGEffectHandler.h | 71 + include/hyperion/CaptureCont.h | 62 + include/hyperion/ComponentRegister.h | 48 +- include/hyperion/Grabber.h | 67 +- include/hyperion/GrabberWrapper.h | 68 +- include/hyperion/Hyperion.h | 350 +++-- include/hyperion/ImageProcessor.h | 81 +- include/hyperion/ImageProcessorFactory.h | 53 - include/hyperion/ImageToLedsMap.h | 26 +- include/hyperion/MessageForwarder.h | 38 +- .../hyperion/MultiColorAdjustment.h | 2 +- include/hyperion/PriorityMuxer.h | 174 ++- include/hyperion/SettingsManager.h | 72 + include/jsonserver/JsonServer.h | 38 +- include/leddevice/LedDevice.h | 23 +- include/leddevice/LedDeviceFactory.h | 3 +- include/protoserver/ProtoServer.h | 44 +- include/python/PythonInit.h | 13 + include/python/PythonUtils.h | 15 + include/udplistener/UDPListener.h | 37 +- include/utils/Components.h | 9 + include/utils/FileUtils.h | 8 +- include/utils/Image.h | 32 + include/utils/JsonProcessor.h | 280 ---- include/utils/JsonUtils.h | 32 +- include/utils/hyperion.h | 320 +++++ include/utils/settings.h | 99 ++ include/webconfig/WebConfig.h | 34 - include/webserver/WebServer.h | 55 + libsrc/CMakeLists.txt | 4 +- .../JSONRPC_schema/schema-adjustment.json | 11 - .../JSONRPC_schema/schema-clear.json | 2 +- .../JSONRPC_schema/schema-color.json | 0 .../JSONRPC_schema/schema-componentstate.json | 0 .../JSONRPC_schema/schema-config.json | 0 .../JSONRPC_schema/schema-create-effect.json | 0 .../JSONRPC_schema/schema-delete-effect.json | 0 .../JSONRPC_schema/schema-effect.json | 0 .../JSONRPC_schema/schema-image.json | 0 .../JSONRPC_schema/schema-ledcolors.json | 0 .../JSONRPC_schema/schema-logging.json | 0 .../JSONRPC_schema/schema-processing.json | 0 .../JSONRPC_schema/schema-serverinfo.json | 3 + .../JSONRPC_schema/schema-sourceselect.json | 0 .../JSONRPC_schema/schema-sysinfo.json | 0 .../JSONRPC_schema/schema-videomode.json | 0 .../{utils => api}/JSONRPC_schema/schema.json | 0 libsrc/{utils => api}/JSONRPC_schemas.qrc | 1 - libsrc/blackborder/BlackBorderDetector.cpp | 2 +- libsrc/blackborder/BlackBorderProcessor.cpp | 97 +- libsrc/blackborder/CMakeLists.txt | 5 +- .../BoblightClientConnection.cpp | 18 +- .../boblightserver/BoblightClientConnection.h | 7 +- libsrc/boblightserver/BoblightServer.cpp | 78 +- libsrc/bonjour/bonjourbrowserwrapper.cpp | 81 ++ libsrc/bonjour/bonjourserviceregister.cpp | 38 +- libsrc/effectengine/CMakeLists.txt | 1 + libsrc/effectengine/Effect.cpp | 1100 +-------------- libsrc/effectengine/EffectEngine.cpp | 61 +- libsrc/effectengine/EffectModule.cpp | 1077 +++++++++++++++ libsrc/grabber/amlogic/AmlogicWrapper.cpp | 4 +- .../grabber/dispmanx/DispmanxFrameGrabber.cpp | 78 +- libsrc/grabber/dispmanx/DispmanxWrapper.cpp | 4 +- .../framebuffer/FramebufferFrameGrabber.cpp | 64 +- .../framebuffer/FramebufferWrapper.cpp | 4 +- libsrc/grabber/osx/OsxFrameGrabber.cpp | 67 +- libsrc/grabber/osx/OsxWrapper.cpp | 4 +- libsrc/grabber/v4l2/V4L2Grabber.cpp | 133 +- libsrc/grabber/v4l2/V4L2Wrapper.cpp | 49 +- libsrc/grabber/x11/X11Grabber.cpp | 100 +- libsrc/grabber/x11/X11Wrapper.cpp | 6 +- libsrc/hyperion/CaptureCont.cpp | 106 ++ libsrc/hyperion/ComponentRegister.cpp | 67 +- libsrc/hyperion/Grabber.cpp | 19 +- libsrc/hyperion/GrabberWrapper.cpp | 162 +-- libsrc/hyperion/Hyperion.cpp | 940 +++++-------- libsrc/hyperion/ImageProcessor.cpp | 99 +- libsrc/hyperion/ImageProcessorFactory.cpp | 25 - libsrc/hyperion/LinearColorSmoothing.cpp | 100 +- libsrc/hyperion/LinearColorSmoothing.h | 72 +- libsrc/hyperion/MessageForwarder.cpp | 98 +- libsrc/hyperion/MultiColorAdjustment.cpp | 33 +- libsrc/hyperion/PriorityMuxer.cpp | 274 +++- libsrc/hyperion/SettingsManager.cpp | 175 +++ libsrc/hyperion/hyperion.schema.json | 4 + libsrc/hyperion/resource.qrc | 1 + libsrc/hyperion/schema/schema-color.json | 16 - .../hyperion/schema/schema-framegrabber.json | 46 +- .../hyperion/schema/schema-grabberV4L2.json | 58 +- .../hyperion/schema/schema-instCapture.json | 45 + libsrc/jsonserver/CMakeLists.txt | 1 + libsrc/jsonserver/JsonClientConnection.cpp | 15 +- libsrc/jsonserver/JsonClientConnection.h | 7 +- libsrc/jsonserver/JsonServer.cpp | 95 +- libsrc/leddevice/LedDevice.cpp | 10 +- libsrc/leddevice/LedDeviceFactory.cpp | 16 +- libsrc/leddevice/dev_net/ProviderUdp.cpp | 8 +- libsrc/leddevice/dev_net/ProviderUdp.h | 7 +- libsrc/protoserver/CMakeLists.txt | 28 +- libsrc/protoserver/ProtoClientConnection.cpp | 47 +- libsrc/protoserver/ProtoClientConnection.h | 7 - libsrc/protoserver/ProtoServer.cpp | 113 +- libsrc/python/CMakeLists.txt | 21 + libsrc/python/PythonInit.cpp | 31 + libsrc/udplistener/UDPListener.cpp | 70 +- libsrc/utils/CMakeLists.txt | 3 - libsrc/utils/FileUtils.cpp | 19 +- .../utils/JSONRPC_schema/schema-clearall.json | 15 - libsrc/utils/JsonProcessor.cpp | 1196 ----------------- libsrc/utils/JsonUtils.cpp | 37 +- libsrc/utils/Process.cpp | 10 +- libsrc/utils/RgbChannelAdjustment.cpp | 2 +- libsrc/utils/RgbTransform.cpp | 4 +- libsrc/utils/Stats.cpp | 14 +- libsrc/webconfig/WebConfig.cpp | 67 - .../{webconfig => webserver}/CMakeLists.txt | 9 +- .../{webconfig => webserver}/CgiHandler.cpp | 9 +- libsrc/{webconfig => webserver}/CgiHandler.h | 11 +- .../QtHttpClientWrapper.cpp | 3 +- .../QtHttpClientWrapper.h | 0 .../{webconfig => webserver}/QtHttpHeader.cpp | 0 .../{webconfig => webserver}/QtHttpHeader.h | 0 .../{webconfig => webserver}/QtHttpReply.cpp | 0 libsrc/{webconfig => webserver}/QtHttpReply.h | 0 .../QtHttpRequest.cpp | 0 .../{webconfig => webserver}/QtHttpRequest.h | 0 .../{webconfig => webserver}/QtHttpServer.cpp | 53 +- .../{webconfig => webserver}/QtHttpServer.h | 0 .../StaticFileServing.cpp | 52 +- .../StaticFileServing.h | 11 +- .../{webconfig => webserver}/WebConfig.qrc.in | 0 .../{webconfig => webserver}/WebJsonRpc.cpp | 10 +- libsrc/{webconfig => webserver}/WebJsonRpc.h | 4 +- libsrc/webserver/WebServer.cpp | 99 ++ .../WebSocketClient.cpp | 11 +- .../WebSocketClient.h | 4 +- .../{webconfig => webserver}/WebSocketUtils.h | 0 src/CMakeLists.txt | 1 - src/hyperion-aml/CMakeLists.txt | 2 +- src/hyperion-dispmanx/CMakeLists.txt | 2 +- src/hyperion-framebuffer/CMakeLists.txt | 2 +- src/hyperion-osx/CMakeLists.txt | 2 +- src/hyperion-remote/JsonConnection.cpp | 3 +- src/hyperion-v4l2/CMakeLists.txt | 2 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 7 - src/hyperion-x11/CMakeLists.txt | 2 +- src/hyperion-x11/X11Wrapper.cpp | 6 +- src/hyperion-x11/X11Wrapper.h | 2 +- src/hyperion-x11/hyperion-x11.cpp | 5 +- src/hyperiond/CMakeLists.txt | 7 +- src/hyperiond/hyperiond.cpp | 632 ++++----- src/hyperiond/hyperiond.h | 107 +- src/hyperiond/main.cpp | 7 +- 186 files changed, 6156 insertions(+), 5444 deletions(-) create mode 100644 assets/webconfig/js/lib/bootstrap-notify.min.js create mode 100644 include/bonjour/bonjourbrowserwrapper.h create mode 100644 include/effectengine/Effect.h rename libsrc/effectengine/Effect.h => include/effectengine/EffectModule.h (55%) create mode 100644 include/hyperion/BGEffectHandler.h create mode 100644 include/hyperion/CaptureCont.h delete mode 100644 include/hyperion/ImageProcessorFactory.h rename {libsrc => include}/hyperion/MultiColorAdjustment.h (98%) create mode 100644 include/hyperion/SettingsManager.h create mode 100644 include/python/PythonInit.h create mode 100644 include/python/PythonUtils.h delete mode 100644 include/utils/JsonProcessor.h create mode 100644 include/utils/hyperion.h create mode 100644 include/utils/settings.h delete mode 100644 include/webconfig/WebConfig.h create mode 100644 include/webserver/WebServer.h rename libsrc/{utils => api}/JSONRPC_schema/schema-adjustment.json (92%) rename libsrc/{utils => api}/JSONRPC_schema/schema-clear.json (94%) rename libsrc/{utils => api}/JSONRPC_schema/schema-color.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-componentstate.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-config.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-create-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-delete-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-effect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-image.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-ledcolors.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-logging.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-processing.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-serverinfo.json (83%) rename libsrc/{utils => api}/JSONRPC_schema/schema-sourceselect.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-sysinfo.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema-videomode.json (100%) rename libsrc/{utils => api}/JSONRPC_schema/schema.json (100%) rename libsrc/{utils => api}/JSONRPC_schemas.qrc (94%) create mode 100644 libsrc/bonjour/bonjourbrowserwrapper.cpp create mode 100644 libsrc/effectengine/EffectModule.cpp create mode 100644 libsrc/hyperion/CaptureCont.cpp delete mode 100644 libsrc/hyperion/ImageProcessorFactory.cpp create mode 100644 libsrc/hyperion/SettingsManager.cpp create mode 100644 libsrc/hyperion/schema/schema-instCapture.json create mode 100644 libsrc/python/CMakeLists.txt create mode 100644 libsrc/python/PythonInit.cpp delete mode 100644 libsrc/utils/JSONRPC_schema/schema-clearall.json delete mode 100644 libsrc/utils/JsonProcessor.cpp delete mode 100644 libsrc/webconfig/WebConfig.cpp rename libsrc/{webconfig => webserver}/CMakeLists.txt (78%) rename libsrc/{webconfig => webserver}/CgiHandler.cpp (94%) rename libsrc/{webconfig => webserver}/CgiHandler.h (83%) rename libsrc/{webconfig => webserver}/QtHttpClientWrapper.cpp (99%) rename libsrc/{webconfig => webserver}/QtHttpClientWrapper.h (100%) rename libsrc/{webconfig => webserver}/QtHttpHeader.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpHeader.h (100%) rename libsrc/{webconfig => webserver}/QtHttpReply.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpReply.h (100%) rename libsrc/{webconfig => webserver}/QtHttpRequest.cpp (100%) rename libsrc/{webconfig => webserver}/QtHttpRequest.h (100%) rename libsrc/{webconfig => webserver}/QtHttpServer.cpp (66%) rename libsrc/{webconfig => webserver}/QtHttpServer.h (100%) rename libsrc/{webconfig => webserver}/StaticFileServing.cpp (63%) rename libsrc/{webconfig => webserver}/StaticFileServing.h (69%) rename libsrc/{webconfig => webserver}/WebConfig.qrc.in (100%) rename libsrc/{webconfig => webserver}/WebJsonRpc.cpp (74%) rename libsrc/{webconfig => webserver}/WebJsonRpc.h (89%) create mode 100644 libsrc/webserver/WebServer.cpp rename libsrc/{webconfig => webserver}/WebSocketClient.cpp (96%) rename libsrc/{webconfig => webserver}/WebSocketClient.h (97%) rename libsrc/{webconfig => webserver}/WebSocketUtils.h (100%) diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index 45b65f4b..469a5e21 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -416,6 +416,7 @@ "edt_conf_enum_PAL" : "PAL", "edt_conf_enum_NTSC" : "NTSC", "edt_conf_enum_SECAM" : "SECAM", + "edt_conf_enum_NO_CHANGE" : "Auto", "edt_conf_enum_logsilent" : "Stille", "edt_conf_enum_logwarn" : "Warnung", "edt_conf_enum_logverbose" : "Ausführlich", @@ -486,14 +487,8 @@ "edt_conf_v4l2_input_expl" : "Der Eingang des Pfades.", "edt_conf_v4l2_standard_title" : "Videoformat", "edt_conf_v4l2_standard_expl" : "Wähle das passende Videoformat deiner Region.", - "edt_conf_v4l2_width_title" : "Breite", - "edt_conf_v4l2_width_expl" : "Die Breite des Bildes. (-1 = Automatische Breitenbestimmung)", - "edt_conf_v4l2_height_title" : "Höhe", - "edt_conf_v4l2_height_expl" : "Die Höhes des Bildes. (-1 = Automatische Höhenbestimmung)", - "edt_conf_v4l2_frameDecimation_title" : "Bildverkleinerung", - "edt_conf_v4l2_frameDecimation_expl" : "Der Faktor der Bildverkleinerung", - "edt_conf_v4l2_sizeDecimation_title" : "Größenänderung", - "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Größenänderung", + "edt_conf_v4l2_sizeDecimation_title" : "Bildverkleinerung Faktor", + "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Bildverkleinerung ausgehend der von der ursprünglichen Größe, 1 bedeutet keine Änderung (originales Bild).", "edt_conf_v4l2_cropLeft_title" : "Entferne links", "edt_conf_v4l2_cropLeft_expl" : "Anzahl der Pixel auf der linken Seite die vom Bild entfernt werden.", "edt_conf_v4l2_cropRight_title" : "Entferne rechts", @@ -503,7 +498,7 @@ "edt_conf_v4l2_cropBottom_title" : "Entferne unten", "edt_conf_v4l2_cropBottom_expl" : "Anzahl der Pixel auf der unteren Seite die vom Bild entfernt werden.", "edt_conf_v4l2_signalDetection_title" : "Signal Erkennung", - "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten. Das Bild muss dazu 4 Sekunden lang unter die Schwellwerte fallen.", + "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten.", "edt_conf_v4l2_redSignalThreshold_title" : "Rote Signalschwelle", "edt_conf_v4l2_redSignalThreshold_expl" : "Je höher die rote Schwelle je eher wird abgeschalten bei entsprechendem rot-Anteil.", "edt_conf_v4l2_greenSignalThreshold_title" : "Grüne Signalschwelle", @@ -523,16 +518,12 @@ "edt_conf_fg_type_expl" : "Art der Plattform Aufnahme, standard ist 'auto'", "edt_conf_fg_frequency_Hz_title" : "Aufnahmefrequenz", "edt_conf_fg_frequency_Hz_expl" : "Wie schnell neue Bilder aufgenommen werden.", - "edt_conf_fg_horizontalPixelDecimation_title" : "Horizontale Pixelreduzierung", - "edt_conf_fg_horizontalPixelDecimation_expl" : "Horizontale Pixelreduzierung (Faktor)", - "edt_conf_fg_useXGetImage_title" : "Nutze XGetImage", - "edt_conf_fg_useXGetImage_expl" : "XGetImage für aktuelle X11 desktops", "edt_conf_fg_width_title" : "Breite", "edt_conf_fg_width_expl" : "Verkleinere Bild auf dieser Breite, da das Rohmaterial viel Leistung benötigen würde.", "edt_conf_fg_height_title" : "Höhe", "edt_conf_fg_height_expl" : "Verkleinere Bild auf dieser Höhe, da das Rohmaterial viel Leistung benötigen würde.", - "edt_conf_fg_verticalPixelDecimation_title" : "Vertikale Pixelreduzierung", - "edt_conf_fg_verticalPixelDecimation_expl" : "Vertikale Pixelreduzierung (Faktor)", + "edt_conf_fg_pixelDecimation_title" : "Bildverkleinerung Faktor", + "edt_conf_fg_pixelDecimation_expl" : "Bildverkleinerung (Faktor) ausgehend von der original Größe. 1 für unveränderte/originale Größe.", "edt_conf_fg_device_title" : "Device", "edt_conf_fg_display_title" : "Display", "edt_conf_fg_display_expl" : "Gebe an von welchem Desktop aufgenommen werden soll. (Multi Monitor Setup)", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 5431fd04..5c2ae968 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -417,6 +417,7 @@ "edt_conf_enum_PAL" : "PAL", "edt_conf_enum_NTSC" : "NTSC", "edt_conf_enum_SECAM" : "SECAM", + "edt_conf_enum_NO_CHANGE" : "Auto", "edt_conf_enum_logsilent" : "Silent", "edt_conf_enum_logwarn" : "Warning", "edt_conf_enum_logverbose" : "Verbose", @@ -504,7 +505,7 @@ "edt_conf_v4l2_cropBottom_title" : "Crop bottom", "edt_conf_v4l2_cropBottom_expl" : "Count of pixels on the bottom side that are removed from the picture.", "edt_conf_v4l2_signalDetection_title" : "Signal detection", - "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found. This will happen when the picture fall below the threshold value for a period of 4 seconds.", + "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found.", "edt_conf_v4l2_redSignalThreshold_title" : "Red signal threshold", "edt_conf_v4l2_redSignalThreshold_expl" : "Darkens low red values (recognized as black)", "edt_conf_v4l2_greenSignalThreshold_title" : "Green signal threshold", @@ -524,16 +525,12 @@ "edt_conf_fg_type_expl" : "Type of platform capture, default is 'auto'", "edt_conf_fg_frequency_Hz_title" : "Capture frequency", "edt_conf_fg_frequency_Hz_expl" : "How fast new pictures are captured", - "edt_conf_fg_horizontalPixelDecimation_title" : "Horizontal pixel decimation", - "edt_conf_fg_horizontalPixelDecimation_expl" : "Horizontal pixel decimation (factor)", "edt_conf_fg_width_title" : "Width", "edt_conf_fg_width_expl" : "Shrink picture to this width, as raw picture needs a lot of cpu time.", "edt_conf_fg_height_title" : "Height", "edt_conf_fg_height_expl" : "Shrink picture to this height, as raw picture needs a lot of cpu time.", - "edt_conf_fg_useXGetImage_title" : "Use XGetImage", - "edt_conf_fg_useXGetImage_expl" : "XGetImage for newer X11 desktops", - "edt_conf_fg_verticalPixelDecimation_title" : "Vertical pixel decimation", - "edt_conf_fg_verticalPixelDecimation_expl" : "Vertical pixel decimation (factor)", + "edt_conf_fg_pixelDecimation_title" : "Picture decimation", + "edt_conf_fg_pixelDecimation_expl" : "Reduce picture size (factor) based on original size. A factor of 1 means no change", "edt_conf_fg_device_title" : "Device", "edt_conf_fg_display_title" : "Display", "edt_conf_fg_display_expl" : "Select which desktop should be captured (multi monitor setup)", diff --git a/assets/webconfig/index.html b/assets/webconfig/index.html index f222ff77..c2f61a19 100644 --- a/assets/webconfig/index.html +++ b/assets/webconfig/index.html @@ -31,6 +31,9 @@ + + + diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index ca0cae1e..de61dffd 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -1,6 +1,6 @@ $(document).ready( function() { performTranslation(); - + function newsCont(t,e,l) { var h = '
'; @@ -10,7 +10,7 @@ $(document).ready( function() { h += '

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

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

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

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

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

'+header+'

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

'+text+'


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

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

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

'+text+'
'); else if(type == "wizard") @@ -247,8 +268,8 @@ function valValue(id,value,min,max) { if(typeof max === 'undefined' || max == "") max = 999999; - - if(Number(value) > Number(max)) + + if(Number(value) > Number(max)) { $('#'+id).val(max); showInfoDialog("warning","",$.i18n('edt_msg_error_maximum_incl',max)); @@ -260,7 +281,7 @@ function valValue(id,value,min,max) showInfoDialog("warning","",$.i18n('edt_msg_error_minimum_incl',min)); return min; } - return value; + return value; } function readImg(input,cb) @@ -294,12 +315,12 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) { $('#'+container).off(); $('#'+container).html(""); - + //JSONEditor.plugins.selectize.enable = true; - + if (typeof arrayre === 'undefined') arrayre = true; - + var editor = new JSONEditor(document.getElementById(container), { theme: 'bootstrap3', @@ -338,18 +359,18 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) } function buildWL(link,linkt,cl) -{ +{ var baseLink = "https://docs.hyperion-project.org/"; var lang; - + if(typeof linkt == "undefined") linkt = "Placeholder"; - + if(storedLang == "de" || navigator.locale == "de") lang = "de"; else lang = "en"; - + if(cl === true) { linkt = $.i18n(linkt); @@ -366,7 +387,7 @@ function rgbToHex(rgb) return "#" + ("0" + parseInt(rgb[0],10).toString(16)).slice(-2) + ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) + - ("0" + parseInt(rgb[2],10).toString(16)).slice(-2); + ("0" + parseInt(rgb[2],10).toString(16)).slice(-2); } else debugMessage('rgbToHex: Given rgb is no array or has wrong length'); @@ -381,6 +402,57 @@ function hexToRgb(hex) { } : null; } +/* + Show a notification + @param type Valid types are "info","success","warning","danger" + @param message The message to show + @param title A title (optional) + */ +function showNotification(type, message, title="") +{ + if(title == "") + { + switch(type) + { + case "info": + title = $.i18n('infoDialog_general_info_title'); + break; + case "success": + title = $.i18n('infoDialog_general_success_title'); + break; + case "warning": + title = $.i18n('infoDialog_general_warning_title'); + break; + case "danger": + title = $.i18n('infoDialog_general_error_title'); + break; + } + } + + $.notify({ + // options + title: title, + message: message + },{ + // settings + type: type, + animate: { + enter: 'animated fadeInRight', + exit: 'animated fadeOutRight' + }, + mouse_over : 'pause', + template: '' + }); +} function createCP(id, color, cb) { @@ -388,7 +460,7 @@ function createCP(id, color, cb) color = rgbToHex(color); else if(color == "undefined") color = "#AA3399"; - + if(color.startsWith("#")) { $('#'+id).colorpicker({ @@ -425,7 +497,7 @@ function createTable(hid, bid, cont, bless, tclass) var table = document.createElement('table'); var thead = document.createElement('thead'); var tbody = document.createElement('tbody'); - + table.className = "table"; if(bless === true) table.className += " borderless"; @@ -438,30 +510,30 @@ function createTable(hid, bid, cont, bless, tclass) if(hid != "") table.appendChild(thead); table.appendChild(tbody); - + $('#'+cont).append(table); } // Creates a table row // @param array list :innerHTML content for / // @param bool head :if null or false it's body -// @param bool align :if null or false no alignment +// @param bool align :if null or false no alignment // // @return : with or as child(s) function createTableRow(list, head, align) { var row = document.createElement('tr'); - + for(var i = 0; i < list.length; i++) { if(head === true) var el = document.createElement('th'); else var el = document.createElement('td'); - + if(align) el.style.verticalAlign = "middle"; - + el.innerHTML = list[i]; row.appendChild(el); } @@ -483,7 +555,7 @@ function createOptPanel(phicon, phead, bodyid, footerid) pfooter.className = "btn btn-primary"; pfooter.setAttribute("id", footerid); pfooter.innerHTML = ''+$.i18n('general_button_savesettings'); - + return createPanel(phead, "", pfooter, "panel-default", bodyid); } @@ -506,30 +578,30 @@ function createHelpTable(list, phead){ var thead = document.createElement('thead'); var tbody = document.createElement('tbody'); list = sortProperties(list); - + phead = ''+phead+' '+$.i18n("conf_helptable_expl"); - + table.className = 'table table-hover borderless'; - + thead.appendChild(createTableRow([$.i18n('conf_helptable_option'), $.i18n('conf_helptable_expl')], true, false)); - + for (key in list) { if(list[key].access != 'system') { var text = list[key].title.replace('title', 'expl'); tbody.appendChild(createTableRow([$.i18n(list[key].title), $.i18n(text)], false, false)); - + if(list[key].items && list[key].items.properties) { var ilist = sortProperties(list[key].items.properties); for (ikey in ilist) { - + var itext = ilist[ikey].title.replace('title', 'expl'); tbody.appendChild(createTableRow([$.i18n(ilist[ikey].title), $.i18n(itext)], false, false)); } - } + } } } table.appendChild(thead); @@ -544,42 +616,42 @@ function createPanel(head, body, footer, type, bodyid){ var phead = document.createElement('div'); var pbody = document.createElement('div'); var pfooter = document.createElement('div'); - + cont.className = "col-lg-6"; - + if(typeof type == 'undefined') type = 'panel-default'; - + p.className = 'panel '+type; phead.className = 'panel-heading'; pbody.className = 'panel-body'; pfooter.className = 'panel-footer'; - + phead.innerHTML = head; - + if(typeof bodyid != 'undefined') { pfooter.style.textAlign = 'right'; pbody.setAttribute("id", bodyid) } - + if(typeof body != 'undefined' && body != "") pbody.appendChild(body); - + if(typeof footer != 'undefined') pfooter.appendChild(footer); - + p.appendChild(phead); p.appendChild(pbody); - + if(typeof footer != 'undefined') { pfooter.style.textAlign = "right"; p.appendChild(pfooter); } - + cont.appendChild(p); - + return cont; } @@ -589,12 +661,12 @@ function createSelGroup(group) el.setAttribute('label', group); return el; } - + function createSelOpt(opt, title) { var el = document.createElement('option'); el.setAttribute('value', opt); - if (typeof title == 'undefined') + if (typeof title == 'undefined') el.innerHTML = opt; else el.innerHTML = title; diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 10f1a8ce..30375404 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -43,7 +43,7 @@ /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') /// * 'leds' : The indices (or index ranges) of the leds to which this channel adjustment applies /// (eg '0-5, 9, 11, 12-17'). The indices are zero based. - /// * 'black'/'white'/'red'/'green'/'blue'/'cyan'/'magenta'/'yellow' : Array of RGB to adjust the output color + /// * 'white'/'red'/'green'/'blue'/'cyan'/'magenta'/'yellow' : Array of RGB to adjust the output color /// * 'gammaRed'/'gammaGreen'/'gammaBlue' : Gamma value for each channel /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') /// * 'id' : The unique identifier of the channel adjustments (eg 'device_1') @@ -60,7 +60,6 @@ { "id" : "default", "leds" : "*", - "black" : [0,0,0], "white" : [255,255,255], "red" : [255,0,0], "green" : [0,255,0], @@ -99,15 +98,10 @@ }, /// Configuration for the embedded V4L2 grabber - /// * enable : Enable or disable the v4lgrabber (true/false) /// * device : V4L2 Device to use [default="/dev/video0"] /// * input : V4L2 input to use [default=0] - /// * standard : Video standard (PAL/NTSC/SECAM) [default="PAL"] - /// * width : V4L2 width to set [default=-1] - /// * height : V4L2 height to set [default=-1] - /// * frameDecimation : Frame decimation factor [default=2] + /// * standard : Video standard (PAL/NTSC/SECAM/NO_CHANGE) [default="NO_CHANGE"] /// * sizeDecimation : Size decimation factor [default=8] - /// * priority : Hyperion priority channel [default=900] /// * cropLeft : Cropping from the left [default=0] /// * cropRight : Cropping from the right [default=0] /// * cropTop : Cropping from the top [default=0] @@ -123,13 +117,9 @@ "grabberV4L2" : [ { - "enable" : false, "device" : "auto", "input" : 0, - "standard" : "PAL", - "width" : 0, - "height" : 0, - "frameDecimation" : 2, + "standard" : "NO_CHANGE", "sizeDecimation" : 8, "priority" : 240, "cropLeft" : 0, @@ -148,20 +138,16 @@ ], /// The configuration for the frame-grabber, contains the following items: - /// * enable : true if the framegrabber (platform grabber) should be activated /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|framebuffer) [auto] /// * width : The width of the grabbed frames [pixels] /// * height : The height of the grabbed frames [pixels] /// * frequency_Hz : The frequency of the frame grab [Hz] - /// * priority : The priority of the frame-gabber (Default=250) HINT: lower value result in HIGHER priority! /// * ATTENTION : Power-of-Two resolution is not supported and leads to unexpected behaviour! "framegrabber" : { // for all type of grabbers - "enable" : true, "type" : "framebuffer", "frequency_Hz" : 10, - "priority" : 250, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -172,9 +158,7 @@ "height" : 96, // valid for x11 - "useXGetImage" : false, - "horizontalPixelDecimation" : 8, - "verticalPixelDecimation" : 8, + "pixelDecimation" : 8, // valid for framebuffer "device" : "/dev/fb0" @@ -313,6 +297,13 @@ ] }, + "instCapture" : { + "systemEnable" : true, + "systemPriority" : 250, + "v4lEnable" : false, + "v4lPriority" : 240 + }, + /// Recreate and save led layouts made with web config. These values are just helpers for ui, not for Hyperion. "ledConfig" : { diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index e5385f55..33f27ece 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -26,7 +26,6 @@ { "id" : "default", "leds" : "*", - "black" : [0,0,0], "white" : [255,255,255], "red" : [255,0,0], "green" : [0,255,0], @@ -58,15 +57,10 @@ "grabberV4L2" : [ { - "enable" : false, "device" : "auto", "input" : 0, - "standard" : "PAL", - "width" : 0, - "height" : 0, - "frameDecimation" : 2, + "standard" : "NO_CHANGE", "sizeDecimation" : 8, - "priority" : 240, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -84,15 +78,11 @@ "framegrabber" : { - "enable" : true, "type" : "auto", "width" : 80, "height" : 45, "frequency_Hz" : 10, - "priority" : 250, - "useXGetImage" : false, - "horizontalPixelDecimation" : 8, - "verticalPixelDecimation" : 8, + "pixelDecimation" : 8, "cropLeft" : 0, "cropRight" : 0, "cropTop" : 0, @@ -175,6 +165,13 @@ "disable": [""] }, + "instCapture" : { + "systemEnable" : true, + "systemPriority" : 250, + "v4lEnable" : false, + "v4lPriority" : 240 + }, + "ledConfig" : { "top" : 8, diff --git a/include/blackborder/BlackBorderProcessor.h b/include/blackborder/BlackBorderProcessor.h index d5f1b9de..0b625f08 100644 --- a/include/blackborder/BlackBorderProcessor.h +++ b/include/blackborder/BlackBorderProcessor.h @@ -4,30 +4,28 @@ // QT includes #include +// util +#include +#include +#include + // Local Hyperion includes #include "BlackBorderDetector.h" +class Hyperion; + namespace hyperion { /// /// The BlackBorder processor is a wrapper around the black-border detector for keeping track of /// detected borders and count of the type and size of detected borders. /// - class BlackBorderProcessor + class BlackBorderProcessor : public QObject { + Q_OBJECT public: - /// - /// Constructor for the BlackBorderProcessor - /// @param unknownFrameCnt The number of frames(images) that need to contain an unknown - /// border before the current border is set to unknown - /// @param borderFrameCnt The number of frames(images) that need to contain a vertical or - /// horizontal border becomes the current border - /// @param blurRemoveCnt The size to add to a horizontal or vertical border (because the - /// outer pixels is blurred (black and color combined due to image scaling)) - /// @param[in] blackborderThreshold The threshold which the blackborder detector should use - /// - BlackBorderProcessor(const QJsonObject &blackborderConfig); - + BlackBorderProcessor(Hyperion* hyperion, QObject* parent); + ~BlackBorderProcessor(); /// /// Return the current (detected) border /// @return The current border @@ -46,6 +44,13 @@ namespace hyperion /// void setEnabled(bool enable); + /// + /// Sets the _hardDisabled state, if True prevents the enable from COMP_BLACKBORDER state emit (mimiks wrong state to external!) + /// It's not possible to enable bb from this method, if the user requsted a disable! + /// @param disable The new state + /// + void setHardDisable(const bool& disable); + /// /// Processes the image. This performs detecion of black-border on the given image and /// updates the current border accordingly. If the current border is updated the method call @@ -70,11 +75,11 @@ namespace hyperion } if (_detectionMode == "default") { - imageBorder = _detector.process(image); + imageBorder = _detector->process(image); } else if (_detectionMode == "classic") { - imageBorder = _detector.process_classic(image); + imageBorder = _detector->process_classic(image); } else if (_detectionMode == "osd") { - imageBorder = _detector.process_osd(image); + imageBorder = _detector->process_osd(image); } // add blur to the border if (imageBorder.horizontalSize > 0) @@ -89,8 +94,23 @@ namespace hyperion const bool borderUpdated = updateBorder(imageBorder); return borderUpdated; } + private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief Handle component state changes, it's not possible for BB to be enabled, when a hardDisable is active + /// + void componentStateChanged(const hyperion::Components component, bool enable); private: + /// Hyperion instance + Hyperion* _hyperion; + /// /// Updates the current border based on the newly detected border. Returns true if the /// current border has changed. @@ -102,24 +122,24 @@ namespace hyperion /// flag for blackborder detector usage bool _enabled; - + /// The number of unknown-borders detected before it becomes the current border - const unsigned _unknownSwitchCnt; + unsigned _unknownSwitchCnt; /// The number of horizontal/vertical borders detected before it becomes the current border - const unsigned _borderSwitchCnt; + unsigned _borderSwitchCnt; // The number of frames that are "ignored" before a new border gets set as _previousDetectedBorder - const unsigned _maxInconsistentCnt; + unsigned _maxInconsistentCnt; /// The number of pixels to increase a detected border for removing blury pixels unsigned _blurRemoveCnt; /// The border detection mode - const QString _detectionMode; + QString _detectionMode; /// The blackborder detector - BlackBorderDetector _detector; + BlackBorderDetector* _detector; /// The current detected border BlackBorder _currentBorder; @@ -131,5 +151,12 @@ namespace hyperion unsigned _consistentCnt; /// The number of frame the previous detected border NOT matched the incomming border unsigned _inconsistentCnt; + /// old threshold + double _oldThreshold; + /// True when disabled in specific situations, this prevents to enable BB when the visible priority requested a disable + bool _hardDisabled; + /// Reflect the last component state request from user (comp change) + bool _userEnabled; + }; } // end namespace hyperion diff --git a/include/boblightserver/BoblightServer.h b/include/boblightserver/BoblightServer.h index 42ed587c..3b711816 100644 --- a/include/boblightserver/BoblightServer.h +++ b/include/boblightserver/BoblightServer.h @@ -4,15 +4,19 @@ #include // Qt includes -#include #include +#include // Hyperion includes -#include #include #include +// settings +#include + class BoblightClientConnection; +class Hyperion; +class QTcpServer; /// /// This class creates a TCP server which accepts connections from boblight clients. @@ -27,25 +31,24 @@ public: /// @param hyperion Hyperion instance /// @param port port number on which to start listening for connections /// - BoblightServer(const int priority, uint16_t port = 19333); + BoblightServer(const QJsonDocument& config); ~BoblightServer(); /// /// @return the port number on which this TCP listens for incoming connections /// uint16_t getPort() const; - + /// @return true if server is active (bind to a port) /// - bool active() { return _isActive; }; - bool componentState() { return active(); }; + bool active(); public slots: /// /// bind server to network /// void start(); - + /// /// close server /// @@ -53,8 +56,12 @@ public slots: void componentStateChanged(const hyperion::Components component, bool enable); -signals: - void statusChanged(bool isActive); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// @@ -73,19 +80,17 @@ private: Hyperion * _hyperion; /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// List with open connections QSet _openConnections; /// hyperion priority - const int _priority; + int _priority; /// Logger instance Logger * _log; - /// state of connection - bool _isActive; - + // current port uint16_t _port; }; diff --git a/include/bonjour/bonjourbrowserwrapper.h b/include/bonjour/bonjourbrowserwrapper.h new file mode 100644 index 00000000..527576d4 --- /dev/null +++ b/include/bonjour/bonjourbrowserwrapper.h @@ -0,0 +1,68 @@ +#pragma once +// qt incl +#include +#include +#include + +#include + +class BonjourServiceBrowser; +class BonjourServiceResolver; +class QTimer; + +class BonjourBrowserWrapper : public QObject +{ + Q_OBJECT +private: + friend class HyperionDaemon; + /// + /// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon + /// Searching for hyperion http service by default + /// + BonjourBrowserWrapper(QObject * parent = 0); + +public: + + /// + /// @brief Browse for a service + /// + bool browseForServiceType(const QString &serviceType); + /// + /// @brief Get all available sessions + /// + QMap getAllServices() { return _hyperionSessions; }; + + static BonjourBrowserWrapper* instance; + static BonjourBrowserWrapper* getInstance(){ return instance; }; + +signals: + /// + /// @brief Emits whenever a change happend + /// + void browserChange(const QMap& bRegisters); + +private: + /// map of service names and browsers + QMap< QString, BonjourServiceBrowser* > _browsedServices; + /// Resolver + BonjourServiceResolver* _bonjourResolver; + + // contains all current active service sessions + QMap _hyperionSessions; + + QString _bonjourCurrentServiceToResolve; + /// timer to resolve changes + QTimer* _timerBonjourResolver; + +private slots: + /// + /// @brief is called whenever a BonjourServiceBrowser emits change + void currentBonjourRecordsChanged(const QList &list); + /// @brief new record resolved + void bonjourRecordResolved(const QHostInfo &hostInfo, int port); + + /// + /// @brief timer slot which updates regularly entries + /// + void bonjourResolve(); +}; diff --git a/include/bonjour/bonjourserviceregister.h b/include/bonjour/bonjourserviceregister.h index 137f97cf..6e3e14ec 100755 --- a/include/bonjour/bonjourserviceregister.h +++ b/include/bonjour/bonjourserviceregister.h @@ -43,7 +43,8 @@ public: BonjourServiceRegister(QObject *parent = 0); ~BonjourServiceRegister(); - void registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt); + void registerService(const QString& service, const int& port); + void registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt = std::vector>()); inline BonjourRecord registeredRecord() const {return finalRecord; } signals: diff --git a/include/effectengine/Effect.h b/include/effectengine/Effect.h new file mode 100644 index 00000000..e028a36a --- /dev/null +++ b/include/effectengine/Effect.h @@ -0,0 +1,89 @@ +#pragma once + +// Python includes +// collide of qt slots macro +#undef slots +#include "Python.h" +#define slots + +// Qt includes +#include +#include +#include +#include +#include +#include + +// Hyperion includes +#include +#include + +class Hyperion; +class Logger; + +class Effect : public QThread +{ + Q_OBJECT + +public: + friend class EffectModule; + + Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args = QJsonObject()); + virtual ~Effect(); + + virtual void run(); + + int getPriority() const { return _priority; }; + + /// + /// @brief Set manual interuption to true, + /// Note: DO NOT USE QThread::interuption! + /// + void setInteruptionFlag() { _interupt = true; }; + + /// + /// @brief Check if the interuption flag has been set + /// @return The flag state + /// + bool hasInteruptionFlag() { return _interupt; }; + + QString getScript() const { return _script; } + QString getName() const { return _name; } + + int getTimeout() const {return _timeout; } + + QJsonObject getArgs() const { return _args; } + +signals: + void setInput(const int priority, const std::vector& ledColors, const int timeout_ms, const bool& clearEffect); + void setInputImage(const int priority, const Image& image, const int timeout_ms, const bool& clearEffect); + +private: + + void addImage(); + + Hyperion* _hyperion; + + const int _priority; + + const int _timeout; + + const QString _script; + const QString _name; + + const QJsonObject _args; + + int64_t _endTime; + + /// Buffer for colorData + QVector _colors; + + Logger* _log; + // Reflects whenever this effects should interupt (timeout or external request) + bool _interupt = false; + + QSize _imageSize; + QImage _image; + QPainter* _painter; + QVector _imageStack; +}; diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index 323b8270..138e6ebf 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -19,7 +19,6 @@ // pre-declarioation class Effect; -typedef struct _ts PyThreadState; class EffectEngine : public QObject { @@ -43,6 +42,20 @@ public: return _effectSchemas; }; + /// + /// @brief Get all init data of the running effects and stop them + /// + void cacheRunningEffects(); + + /// + /// @brief Start all cached effects, origin and smooth cfg is default + /// + void startCachedEffects(); + +signals: + /// Emit when the effect list has been updated + void effectListUpdated(); + public slots: /// Run the specified effect on the given priority channel and optionally specify a timeout int runEffect(const QString &effectName, int priority, int timeout = -1, const QString &origin="System"); @@ -78,9 +91,9 @@ private: std::list _availableActiveEffects; + std::list _cachedActiveEffects; + std::list _effectSchemas; Logger * _log; - - PyThreadState* _mainThreadState; }; diff --git a/libsrc/effectengine/Effect.h b/include/effectengine/EffectModule.h similarity index 55% rename from libsrc/effectengine/Effect.h rename to include/effectengine/EffectModule.h index 7d51d5a5..c274d05b 100644 --- a/libsrc/effectengine/Effect.h +++ b/include/effectengine/EffectModule.h @@ -1,49 +1,27 @@ #pragma once -// Python includes -// collide of qt slots macro #undef slots #include #define slots -// Qt includes -#include -#include -#include -#include -#include +#include -// Hyperion includes -#include -#include +class Effect; -class Effect : public QThread +class EffectModule { - Q_OBJECT - public: - Effect(PyThreadState* mainThreadState, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args = QJsonObject(), const QString & origin="System", unsigned smoothCfg=0); - virtual ~Effect(); + // Python 3 module def + static struct PyModuleDef moduleDef; - virtual void run(); + // Init module + static PyObject* PyInit_hyperion(); - int getPriority() const { return _priority; }; + // Register module once + static void registerHyperionExtensionModule(); - QString getScript() const { return _script; } - QString getName() const { return _name; } - - int getTimeout() const {return _timeout; } - - QJsonObject getArgs() const { return _args; } - - /// This function registers the extension module in Python - static void registerHyperionExtensionModule(); - -signals: - void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects, hyperion::Components componentconst, QString origin, unsigned smoothCfg); - -private: - PyObject * json2python(const QJsonValue & jsonData) const; + // json 2 python + static PyObject * json2python(const QJsonValue & jsonData); // Wrapper methods for Python interpreter extra buildin methods static PyMethodDef effectMethods[]; @@ -71,38 +49,5 @@ private: static PyObject* wrapImageCOffset (PyObject *self, PyObject *args); static PyObject* wrapImageCShear (PyObject *self, PyObject *args); static PyObject* wrapImageResetT (PyObject *self, PyObject *args); - static Effect * getEffect(); - - static struct PyModuleDef moduleDef; - static PyObject* PyInit_hyperion(); - - void addImage(); - - PyThreadState* _mainThreadState; - - const int _priority; - - const int _timeout; - - const QString _script; - const QString _name; - unsigned _smoothCfg; - - const QJsonObject _args; - - int64_t _endTime; - - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - - /// Buffer for colorData - QVector _colors; - - Logger* _log; - - QString _origin; - QSize _imageSize; - QImage _image; - QPainter* _painter; - QVector _imageStack; + static Effect * getEffect(); }; diff --git a/include/grabber/AmlogicWrapper.h b/include/grabber/AmlogicWrapper.h index bfcc9552..0f241162 100644 --- a/include/grabber/AmlogicWrapper.h +++ b/include/grabber/AmlogicWrapper.h @@ -18,9 +18,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this dispmanx frame grabber. Releases any claimed resources. diff --git a/include/grabber/DispmanxFrameGrabber.h b/include/grabber/DispmanxFrameGrabber.h index 3d22d3dc..cd885015 100644 --- a/include/grabber/DispmanxFrameGrabber.h +++ b/include/grabber/DispmanxFrameGrabber.h @@ -40,14 +40,23 @@ public: /// int grabFrame(Image & image); + /// + ///@brief Set new width and height for dispmanx, overwrite Grabber.h impl + virtual void setWidthHeight(int width, int height); + private: - /// + /// /// Updates the frame-grab flags as used by the VC library for frame grabbing /// /// @param vc_flags The snapshot grabbing mask /// void setFlags(const int vc_flags); - + + /// + /// @brief free _vc_resource and captureBuffer + /// + void freeResources(); + /// Handle to the display that is being captured DISPMANX_DISPLAY_HANDLE_T _vc_display; diff --git a/include/grabber/DispmanxWrapper.h b/include/grabber/DispmanxWrapper.h index 618a8b27..e231c809 100644 --- a/include/grabber/DispmanxWrapper.h +++ b/include/grabber/DispmanxWrapper.h @@ -7,8 +7,7 @@ /// /// The DispmanxWrapper uses an instance of the DispmanxFrameGrabber to obtain ImageRgb's from the -/// displayed content. This ImageRgb is processed to a ColorRgb for each led and commmited to the -/// attached Hyperion. +/// displayed content. This ImageRgb is forwarded to all Hyperion instances via HyperionDaemon /// class DispmanxWrapper: public GrabberWrapper { @@ -20,9 +19,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this dispmanx frame grabber. Releases any claimed resources. diff --git a/include/grabber/FramebufferFrameGrabber.h b/include/grabber/FramebufferFrameGrabber.h index 6077bb1b..6296755f 100644 --- a/include/grabber/FramebufferFrameGrabber.h +++ b/include/grabber/FramebufferFrameGrabber.h @@ -5,7 +5,7 @@ #include /// -/// The FramebufferFrameGrabber is used for creating snapshots of the display (screenshots) +/// The FramebufferFrameGrabber is used for creating snapshots of the display (screenshots) /// class FramebufferFrameGrabber : public Grabber { @@ -30,13 +30,18 @@ public: /// int grabFrame(Image & image); + /// + /// @brief Overwrite Grabber.h implememtation + /// + virtual void setDevicePath(const QString& path); + private: /// Framebuffer file descriptor int _fbfd; /// Pointer to framebuffer unsigned char * _fbp; - + /// Framebuffer device e.g. /dev/fb0 - const QString _fbDevice; + QString _fbDevice; }; diff --git a/include/grabber/FramebufferWrapper.h b/include/grabber/FramebufferWrapper.h index 164c56b3..a7c9243c 100644 --- a/include/grabber/FramebufferWrapper.h +++ b/include/grabber/FramebufferWrapper.h @@ -20,7 +20,7 @@ public: /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] /// - FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this framebuffer frame grabber. Releases any claimed resources. diff --git a/include/grabber/OsxFrameGrabber.h b/include/grabber/OsxFrameGrabber.h index 8cba6394..59e39930 100644 --- a/include/grabber/OsxFrameGrabber.h +++ b/include/grabber/OsxFrameGrabber.h @@ -12,7 +12,7 @@ #include /// -/// The OsxFrameGrabber is used for creating snapshots of the display (screenshots) +/// The OsxFrameGrabber is used for creating snapshots of the display (screenshots) /// class OsxFrameGrabber : public Grabber { @@ -37,10 +37,15 @@ public: /// int grabFrame(Image & image); -private: + /// + /// @brief Overwrite Grabber.h implementation + /// + virtual void setDisplayIndex(int index); + +private: /// display - const unsigned _screenIndex; - + unsigned _screenIndex; + /// Reference to the captured diaplay CGDirectDisplayID _display; }; diff --git a/include/grabber/OsxWrapper.h b/include/grabber/OsxWrapper.h index 65b765d7..65ce70ab 100644 --- a/include/grabber/OsxWrapper.h +++ b/include/grabber/OsxWrapper.h @@ -19,9 +19,8 @@ public: /// @param[in] grabWidth The width of the grabbed image [pixels] /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] - /// @param[in] hyperion The instance of Hyperion used to write the led values /// - OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority); + OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); /// /// Destructor of this osx frame grabber. Releases any claimed resources. diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index b21aa005..7277b69a 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -24,12 +24,9 @@ class V4L2Grabber : public Grabber public: V4L2Grabber(const QString & device, int input, - VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int horizontalPixelDecimation, - int verticalPixelDecimation + VideoStandard videoStandard, + PixelFormat pixelFormat, + int pixelDecimation ); virtual ~V4L2Grabber(); @@ -37,21 +34,46 @@ public: bool getSignalDetectionEnabled(); int grabFrame(Image &); - -public slots: - void setSignalThreshold( + + /// + /// @brief overwrite Grabber.h implementation, as v4l doesn't use width/height + /// + virtual void setWidthHeight(){}; + + /// + /// @brief set new PixelDecimation value to ImageResampler + /// @param pixelDecimation The new pixelDecimation value + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalThreshold( double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, - int noSignalCounterThreshold); + int noSignalCounterThreshold = 50); - void setSignalDetectionOffset( + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalDetectionOffset( double verticalMin, double horizontalMin, double verticalMax, double horizontalMax); + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setSignalDetectionEnable(bool enable); - void setSignalDetectionEnable(bool enable); + /// + /// @brief overwrite Grabber.h implementation + /// + virtual void setInputVideoStandard(int input, VideoStandard videoStandard); + +public slots: bool start(); @@ -66,7 +88,7 @@ private slots: private: void getV4Ldevices(); - + bool init(); void uninit(); @@ -120,9 +142,9 @@ private: std::vector _buffers; PixelFormat _pixelFormat; + int _pixelDecimation; int _lineLength; int _frameByteSize; - int _frameDecimation; // signal detection int _noSignalCounterThreshold; @@ -134,7 +156,6 @@ private: double _y_frac_min; double _x_frac_max; double _y_frac_max; - int _currentFrame; QSocketNotifier * _streamNotifier; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index ffc78a7c..dfa14014 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -12,14 +12,7 @@ public: int input, VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int pixelDecimation, - double redSignalThreshold, - double greenSignalThreshold, - double blueSignalThreshold, - const int priority); + int pixelDecimation ); virtual ~V4L2Wrapper() {}; bool getSignalDetectionEnable(); @@ -28,19 +21,16 @@ public slots: bool start(); void stop(); + void setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold); void setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom); void setSignalDetectionOffset(double verticalMin, double horizontalMin, double verticalMax, double horizontalMax); void setSignalDetectionEnable(bool enable); -// signals: -// void emitColors(int priority, const std::vector &ledColors, const int timeout_ms); - private slots: void newFrame(const Image & image); void readError(const char* err); virtual void action(); - void checkSources(); private: /// The V4L2 grabber diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index 7ef286fb..af833f33 100755 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -17,12 +17,12 @@ class X11Grabber : public Grabber { public: - X11Grabber(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); virtual ~X11Grabber(); bool Setup(); - + /// /// Captures a single snapshot of the display and writes the data to the given image. The /// provided image should have the same dimensions as the configured values (_width and @@ -32,15 +32,34 @@ public: /// height) /// virtual int grabFrame(Image & image, bool forceUpdate=false); - + /// /// update dimension according current screen int updateScreenDimensions(bool force=false); virtual void setVideoMode(VideoMode mode); + /// + /// @brief Apply new width/height values, overwrite Grabber.h implementation as X11 doesn't use width/height, just pixelDecimation to calc dimensions + /// + virtual void setWidthHeight(int width, int height); + + /// + /// @brief Apply new pixelDecimation + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// + virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + private: - bool _useXGetImage, _XShmAvailable, _XShmPixmapAvailable, _XRenderAvailable; + bool _XShmAvailable, _XShmPixmapAvailable, _XRenderAvailable; XImage* _xImage; XShmSegmentInfo _shminfo; @@ -49,17 +68,16 @@ private: Display* _x11Display; Window _window; XWindowAttributes _windowAttr; - + Pixmap _pixmap; XRenderPictFormat* _srcFormat; XRenderPictFormat* _dstFormat; XRenderPictureAttributes _pictAttr; Picture _srcPicture; Picture _dstPicture; - + XTransform _transform; - int _horizontalDecimation; - int _verticalDecimation; + int _pixelDecimation; unsigned _screenWidth; unsigned _screenHeight; @@ -67,7 +85,7 @@ private: unsigned _src_y; Image _image; - + void freeResources(); void setupResources(); }; diff --git a/include/grabber/X11Wrapper.h b/include/grabber/X11Wrapper.h index e29b3ca4..51f2c77c 100644 --- a/include/grabber/X11Wrapper.h +++ b/include/grabber/X11Wrapper.h @@ -24,7 +24,7 @@ public: /// @param[in] grabHeight The height of the grabbed images [pixels] /// @param[in] updateRate_Hz The image grab rate [Hz] /// - X11Wrapper(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation, const unsigned updateRate_Hz, const int priority); + X11Wrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz); /// /// Destructor of this framebuffer frame grabber. Releases any claimed resources. @@ -43,4 +43,3 @@ private: bool _init; }; - diff --git a/include/hyperion/BGEffectHandler.h b/include/hyperion/BGEffectHandler.h new file mode 100644 index 00000000..3ca229dc --- /dev/null +++ b/include/hyperion/BGEffectHandler.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +/// +/// @brief Handle the background Effect settings, reacts on runtime to settings changes +/// +class BGEffectHandler : public QObject +{ + Q_OBJECT + +public: + BGEffectHandler(Hyperion* hyperion) + : QObject(hyperion) + , _hyperion(hyperion) + { + // listen for config changes + connect(_hyperion, &Hyperion::settingsChanged, this, &BGEffectHandler::handleSettingsUpdate); + + // init + handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT)); + }; + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) + { + if(type == settings::BGEFFECT) + { + const QJsonObject& BGEffectConfig = config.object(); + + #define BGCONFIG_ARRAY bgColorConfig.toArray() + // clear bg prioritiy + _hyperion->clear(254); + // initial background effect/color + if (BGEffectConfig["enable"].toBool(true)) + { + const QString bgTypeConfig = BGEffectConfig["type"].toString("effect"); + const QString bgEffectConfig = BGEffectConfig["effect"].toString("Warm mood blobs"); + const QJsonValue bgColorConfig = BGEffectConfig["color"]; + if (bgTypeConfig.contains("color")) + { + ColorRgb bg_color = { + (uint8_t)BGCONFIG_ARRAY.at(0).toInt(0), + (uint8_t)BGCONFIG_ARRAY.at(1).toInt(0), + (uint8_t)BGCONFIG_ARRAY.at(2).toInt(0) + }; + _hyperion->setColor(254, bg_color); + Info(Logger::getInstance("HYPERION"),"Inital background color set (%d %d %d)",bg_color.red,bg_color.green,bg_color.blue); + } + else + { + int result = _hyperion->setEffect(bgEffectConfig, 254); + Info(Logger::getInstance("HYPERION"),"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); + } + } + + #undef BGCONFIG_ARRAY + } + }; + +private: + /// Hyperion instance pointer + Hyperion* _hyperion; +}; diff --git a/include/hyperion/CaptureCont.h b/include/hyperion/CaptureCont.h new file mode 100644 index 00000000..4322776c --- /dev/null +++ b/include/hyperion/CaptureCont.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +class Hyperion; + +/// +/// @brief Capture Control class which is a interface to the HyperionDaemon native capture classes. +/// It controls the instance based enable/disable of capture feeds and PriorityMuxer registrations +/// +class CaptureCont : public QObject +{ + Q_OBJECT +public: + CaptureCont(Hyperion* hyperion); + ~CaptureCont(); + + void setSystemCaptureEnable(const bool& enable); + void setV4LCaptureEnable(const bool& enable); + +private slots: + /// + /// @brief Handle component state change of V4L and SystemCapture + /// @param component The component from enum + /// @param enable The new state + /// + void componentStateChanged(const hyperion::Components component, bool enable); + + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief forward system image + /// @param image The image + /// + void handleSystemImage(const Image& image); + + /// + /// @brief forward v4l image + /// @param image The image + /// + void handleV4lImage(const Image & image); + +private: + /// Hyperion instance + Hyperion* _hyperion; + + /// Reflect state of System capture and prio + bool _systemCaptEnabled; + quint8 _systemCaptPrio; + + /// Reflect state of v4l capture and prio + bool _v4lCaptEnabled; + quint8 _v4lCaptPrio; +}; diff --git a/include/hyperion/ComponentRegister.h b/include/hyperion/ComponentRegister.h index 5ae442b0..91bd7228 100644 --- a/include/hyperion/ComponentRegister.h +++ b/include/hyperion/ComponentRegister.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include // STL includes @@ -8,21 +8,61 @@ #include +class Hyperion; + +/// +/// @brief The component register reflects and manages the current state of all components and Hyperion as a whole +/// It emits also real component state changes (triggert from the specific component), which can be used for listening APIs (Network Clients/Plugins) +/// class ComponentRegister : public QObject { Q_OBJECT public: - ComponentRegister(); + ComponentRegister(Hyperion* hyperion); ~ComponentRegister(); + /// + /// @brief Enable or disable Hyperion (all components) + /// @param state The new state of Hyperion + /// + /// @return Returns true on success, false when Hyperion is already at the requested state + /// + bool setHyperionEnable(const bool& state); + + /// + /// @brief Check if a component is currently enabled + /// @param comp The component from enum + /// @return True if component is running else false + /// + bool isComponentEnabled(const hyperion::Components& comp) const; + + /// contains all components and their state std::map getRegister() { return _componentStates; }; +signals: + /// + /// @brief Emits whenever a component changed (really) the state + /// @param comp The component + /// @param state The new state of the component + /// + void updatedComponentState(const hyperion::Components comp, const bool state); + public slots: + /// + /// @brief is called whenever a component change a state, DO NOT CALL FROM API (use hyperion->setComponentState() instead) + /// @param comp The component + /// @param state The new state of the component + /// void componentStateChanged(const hyperion::Components comp, const bool activated); private: - std::map _componentStates; + /// Hyperion instance + Hyperion * _hyperion; + /// Logger instance Logger * _log; + /// current state of all components + std::map _componentStates; + /// on hyperion off we save the previous states of all components + std::map _prevComponentStates; }; - diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index fa81a57b..88207e03 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -6,10 +6,14 @@ #include #include #include +#include #include #include - +/// +/// @brief The Grabber class is responsible to apply image resizes (with or without ImageResampler) +/// Overwrite the videoMode with setVideoMode() +/// Overwrite setCropping() class Grabber : public QObject { Q_OBJECT @@ -24,14 +28,71 @@ public: /// virtual void setVideoMode(VideoMode mode); + /// + /// @brief Apply new crop values, on errors reject the values + /// virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); - /// gets resulting height of image + /// + /// @brief Apply new width/height values, on errors (collide with cropping) reject the values + /// + virtual void setWidthHeight(int width, int height); + + /// + /// @brief Apply new pixelDecimation (used from x11) + /// + virtual void setPixelDecimation(int pixelDecimation) {}; + + /// + /// @brief Apply new signalThreshold (used from v4l) + /// + virtual void setSignalThreshold( + double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + int noSignalCounterThreshold = 50) {}; + /// + /// @brief Apply new SignalDetectionOffset (used from v4l) + /// + virtual void setSignalDetectionOffset( + double verticalMin, + double horizontalMin, + double verticalMax, + double horizontalMax) {}; + + /// + /// @brief Apply SignalDetectionEnable (used from v4l) + /// + virtual void setSignalDetectionEnable(bool enable) {}; + + /// + /// @brief Apply input and videoStanded (used from v4l) + /// + virtual void setInputVideoStandard(int input, VideoStandard videoStandard) {}; + + /// + /// @brief Apply display index (used from x11) + /// + virtual void setDisplayIndex(int index) {}; + + /// + /// @brief Apply path for device (used from framebuffer) + /// + virtual void setDevicePath(const QString& path) {}; + + /// + /// @brief get current resulting height of image (after crop) + /// virtual const int getImageWidth() { return _width; }; - /// gets resulting width of image + /// + /// @brief get current resulting width of image (after crop) + /// virtual const int getImageHeight() { return _height; }; + /// + /// @brief Prevent the real capture implementation from capturing if disabled + /// void setEnabled(bool enable); protected: diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index 7e893a82..ca70a436 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -1,27 +1,29 @@ #pragma once #include -#include #include #include #include #include #include -#include #include #include #include +#include -class ImageProcessor; class Grabber; class DispmanxFrameGrabber; +class QTimer; +/// +/// This class will be inherted by FramebufferWrapper and others which contains the real capture interface +/// class GrabberWrapper : public QObject { Q_OBJECT public: - GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz, const int priority, hyperion::Components grabberComponentId=hyperion::COMP_GRABBER); + GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz); virtual ~GrabberWrapper(); @@ -35,8 +37,6 @@ public: /// virtual void stop(); - void setImageProcessorEnabled(bool enable); - static QStringList availableGrabbers(); public: @@ -45,18 +45,15 @@ public: { unsigned w = grabber.getImageWidth(); unsigned h = grabber.getImageHeight(); - if (_imageProcessorEnabled && ( _image.width() != w || _image.height() != h)) + if ( _image.width() != w || _image.height() != h) { - _processor->setSize(w, h); _image.resize(w, h); } int ret = grabber.grabFrame(_image); if (ret >= 0) { - emit emitImage(_priority, _image, _timeout_ms); - _processor->process(_image, _ledColors); - setColors(_ledColors, _timeout_ms); + emit systemImage(_image); return true; } return false; @@ -64,46 +61,51 @@ public: public slots: - void componentStateChanged(const hyperion::Components component, bool enable); - /// /// virtual method, should perform single frame grab and computes the led-colors /// virtual void action() = 0; - void actionWrapper(); - /// /// Set the video mode (2D/3D) /// @param[in] mode The new video mode /// - virtual void setVideoMode(const VideoMode videoMode); + virtual void setVideoMode(const VideoMode& videoMode); + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + /// + /// @brief Handle settings update from HyperionDaemon Settingsmanager emit + /// @param type settingyType from enum + /// @param config configuration object + /// + virtual void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + signals: - void emitImage(int priority, const Image & image, const int timeout_ms); + /// + /// @brief Emit the final processed image + /// + void systemImage(const Image& image); protected: - void setColors(const std::vector &ledColors, const int timeout_ms); - QString _grabberName; /// Pointer to Hyperion for writing led values Hyperion * _hyperion; - /// The priority of the led colors - const int _priority; - /// The timer for generating events with the specified update rate - QTimer _timer; + QTimer* _timer; - /// The update rate [Hz] - const int _updateInterval_ms; - - /// The timeout of the led colors [ms] - const int _timeout_ms; + /// The calced update rate [ms] + int _updateInterval_ms; /// The Logger instance Logger * _log; @@ -111,18 +113,8 @@ protected: // forwarding enabled bool _forward; - /// The processor for transforming images to led colors - ImageProcessor * _processor; - - hyperion::Components _grabberComponentId; - Grabber *_ggrabber; /// The image used for grabbing frames Image _image; - - /// The list with computed led colors - std::vector _ledColors; - - bool _imageProcessorEnabled; }; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 28c34c4d..c39dce72 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -8,12 +8,10 @@ #include #include #include -#include #include #include #include #include -#include #include // hyperion-utils includes @@ -27,7 +25,6 @@ #include #include #include -#include #include // Effect engine includes @@ -35,17 +32,23 @@ #include #include -// bonjour includes -#include -#include +// settings utils +#include // Forward class declaration +class QTimer; + +class HyperionDaemon; +class ImageProcessor; +class MessageForwarder; class LedDevice; class LinearColorSmoothing; -class RgbTransform; class EffectEngine; -class RgbChannelAdjustment; class MultiColorAdjustment; +class ColorAdjustment; +class SettingsManager; +class BGEffectHandler; +class CaptureCont; /// /// The main class of Hyperion. This gives other 'users' access to the attached LedDevice through @@ -57,8 +60,6 @@ class Hyperion : public QObject public: /// Type definition of the info structure used by the priority muxer typedef PriorityMuxer::InputInfo InputInfo; - typedef QMap PriorityRegister; - typedef QMap BonjourRegister; /// /// RGB-Color channel enumeration /// @@ -79,23 +80,54 @@ public: /// /// @brief creates a new Hyperion instance, usually called from the Hyperion Daemon - /// @param[in] qjsonConfig The configuration file + /// @param[in] daemon The Hyperion daemon parent + /// @param[in] instance The instance id /// @param[in] rootPath Root path of all hyperion userdata /// @return Hyperion instance pointer /// - static Hyperion* initInstance(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath); + static Hyperion* initInstance(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath); /// /// @brief Get a pointer of this Hyperion instance - /// @return Hyperion instance pointer + /// @return Hyperion instance pointer /// static Hyperion* getInstance(); + /// + /// @brief Get a pointer to the effect engine + /// @return EffectEngine instance pointer + /// + EffectEngine* getEffectEngineInstance() { return _effectEngine; }; + + /// + /// @brief Get a pointer to the priorityMuxer instance + /// @return PriorityMuxer instance pointer + /// + PriorityMuxer* getMuxerInstance() { return &_muxer; }; + + /// + /// @brief Get a setting by settings::type from SettingsManager + /// @param type The settingsType from enum + /// @return Data Document + /// + QJsonDocument getSetting(const settings::type& type); + + /// + /// @brief Save a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + bool saveSettings(QJsonObject config, const bool& correct = false); + /// /// Returns the number of attached leds /// unsigned getLedCount() const; + /// + /// @brief Return the size of led grid + /// QSize getLedGridSize() const { return _ledGridSize; }; /// @@ -124,11 +156,9 @@ public: /// /// @param[in] priority The priority channel /// - /// @return The information of the given + /// @return The information of the given, a not found priority will return lowest priority as fallback /// - /// @throw std::runtime_error when the priority channel does not exist - /// - const InputInfo& getPriorityInfo(const int priority) const; + const InputInfo getPriorityInfo(const int priority) const; /// Reload the list of available effects void reloadEffects(); @@ -145,27 +175,29 @@ public: /// @return The list of available effect schema files const std::list &getEffectSchemas(); - /// gets the current json config object + /// gets the current json config object from SettingsManager /// @return json config - const QJsonObject& getQJsonConfig() { return _qjsonConfig; }; + const QJsonObject& getQJsonConfig(); + + /// get path+filename of configfile + /// @return the current config path+filename + QString getConfigFilePath() { return _configFile; }; /// get filename of configfile /// @return the current config filename - QString getConfigFileName() { return _configFile; }; + QString getConfigFileName() const; - /// register a input source to a priority channel - /// @param name uniq name of input source - /// @param origin External setter - /// @param priority priority channel - void registerPriority(const QString &name, const int priority); + /// + /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() + /// A repeated call to update the base data of a known priority won't overwrite their current timeout + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] smooth_cfg The smooth id to use + /// + void registerInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0); - /// unregister a input source to a priority channel - /// @param name uniq name of input source - void unRegisterPriority(const QString &name); - - /// gets current priority register - /// @return the priority register - const PriorityRegister& getPriorityRegister() { return _priorityRegister; } /// enable/disable automatic/priorized source selection /// @param enabled the state @@ -178,12 +210,18 @@ public: /// gets current state of automatic/priorized source selection /// @return the state - bool sourceAutoSelectEnabled() { return _sourceAutoSelectEnabled; }; + bool sourceAutoSelectEnabled(); /// - /// Enable/Disable components during runtime + /// @brief Get the last untransformed/unadjusted led colors + /// @return The _rawLedBuffer leds /// - /// @param component The component [SMOOTHING, BLACKBORDER, FORWARDER, UDPLISTENER, BOBLIGHT_SERVER, GRABBER] + const std::vector& getRawLedBuffer() { return _rawLedBuffer; }; + + /// + /// @brief Enable/Disable components during runtime, called from external API (requests) + /// + /// @param component The component from enum /// @param state The state of the component [true | false] /// void setComponentState(const hyperion::Components component, const bool state); @@ -195,54 +233,63 @@ public: bool configWriteable() { return _configWrite; }; /// gets the methode how image is maped to leds - int getLedMappingType() { return _ledMAppingType; }; - - /// get the configuration - QJsonObject getConfig() { return _qjsonConfig; }; + const int & getLedMappingType(); /// get the root path for all hyperion user data files - QString getRootPath() { return _rootPath; }; + const QString &getRootPath() { return _rootPath; }; - /// unique id per instance - QString id; + /// get unique id per instance + const QString &getId(){ return _id; }; + /// set unique id + void setId(QString id){ _id = id; }; int getLatchTime() const; /// forward smoothing config unsigned addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); - VideoMode getCurrentVideoMode() { return _videoMode; }; + const VideoMode & getCurrentVideoMode(); + + /// + /// @brief Get the current active led device + /// @return The device nam + /// e + const QString & getActiveDevice(); public slots: + /// + /// @brief Update the current color of a priority (prev registered with registerInput()) + /// DO NOT use this together with setInputImage() at the same time! + /// @param priority The priority to update + /// @param ledColors The colors + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @param clearEffect Should be true when NOT called from an effect + /// @return True on success, false when priority is not found + /// + const bool setInput(const int priority, const std::vector& ledColors, const int timeout_ms = -1, const bool& clearEffect = true); + + /// + /// @brief Update the current image of a priority (prev registered with registerInput()) + /// DO NOT use this together with setInput() at the same time! + /// @param priority The priority to update + /// @param image The new image + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @param clearEffect Should be true when NOT called from an effect + /// @return True on success, false when priority is not found + /// + const bool setInputImage(const int priority, const Image& image, int64_t timeout_ms = -1, const bool& clearEffect = true); + /// /// Writes a single color to all the leds for the given time and priority + /// Registers comp color or provided type against muxer + /// Should be never used to update leds continuous /// /// @param[in] priority The priority of the written color /// @param[in] ledColor The color to write to the leds + /// @param[in] origin The setter /// @param[in] timeout_ms The time the leds are set to the given color [ms] /// - void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms, bool clearEffects = true); - - /// - /// Writes the given colors to all leds for the given time and priority - /// - /// @param[in] priority The priority of the written colors - /// @param[in] ledColors The colors to write to the leds - /// @param[in] timeout_ms The time the leds are set to the given colors [ms] - /// @param[in] component The current component - /// @param[in] origin Who set it - /// @param[in] smoothCfg smoothing config id - /// - void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects = true, hyperion::Components component=hyperion::COMP_INVALID, const QString origin="System", unsigned smoothCfg=SMOOTHING_MODE_DEFAULT); - - /// - /// Writes the given colors to all leds for the given time and priority - /// - /// @param[in] priority The priority of the written colors - /// @param[in] ledColors The colors to write to the leds - /// @param[in] timeout_ms The time the leds are set to the given colors [ms] - /// - void setImage(int priority, const Image & image, int duration_ms); + void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms = -1, const QString& origin = "System" ,bool clearEffects = true); /// /// Returns the list with unique adjustment identifiers @@ -270,8 +317,9 @@ public slots: /// lower priority channel (or off if no more channels are set) /// /// @param[in] priority The priority channel + /// @return True on success else false (not found) /// - void clear(int priority); + const bool clear(int priority); /// /// Clears all priority channels. This will switch the leds off until a new priority is written. @@ -292,44 +340,18 @@ public slots: int setEffect(const QString & effectName, const QJsonObject & args, int priority, int timeout = -1, const QString & pythonScript = "", const QString & origin="System"); - /// sets the methode how image is maped to leds - void setLedMappingType(int mappingType); - - /// - Hyperion::BonjourRegister getHyperionSessions(); - - /// Slot which is called, when state of hyperion has been changed - void hyperionStateChanged(); + /// sets the methode how image is maped to leds at ImageProcessor + void setLedMappingType(const int& mappingType); /// /// Set the video mode (2D/3D) /// @param[in] mode The new video mode /// - void setVideoMode(VideoMode mode); + void setVideoMode(const VideoMode& mode); public: static Hyperion *_hyperion; - static ColorOrder createColorOrder(const QJsonObject & deviceConfig); - /** - * Construct the 'led-string' with the integration area definition per led and the color - * ordering of the RGB channels - * @param ledsConfig The configuration of the led areas - * @param deviceOrder The default RGB channel ordering - * @return The constructed ledstring - */ - static LedString createLedString(const QJsonValue & ledsConfig, const ColorOrder deviceOrder); - static LedString createLedStringClone(const QJsonValue & ledsConfig, const ColorOrder deviceOrder); - - static MultiColorAdjustment * createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorAdjustmentConfig); - static ColorAdjustment * createColorAdjustment(const QJsonObject & adjustmentConfig); - static RgbTransform * createRgbTransform(const QJsonObject& colorConfig); - static RgbChannelAdjustment * createRgbChannelAdjustment(const QJsonObject & colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB); - - static LinearColorSmoothing * createColorSmoothing(const QJsonObject & smoothingConfig, LedDevice* leddevice); - static MessageForwarder * createMessageForwarder(const QJsonObject & forwarderConfig); - static QSize getLedLayoutGridSize(const QJsonValue& ledsConfig); - signals: /// Signal which is emitted when a priority channel is actively cleared /// This signal will not be emitted when a priority channel time out @@ -339,25 +361,67 @@ signals: /// This signal will not be emitted when a priority channel time out void allChannelsCleared(); + /// + /// @brief Emits whenever a user request a component state change, it's up the component to listen + /// and update the component state at the componentRegister + /// @param component The component from enum + /// @param enabled The new state of the component + /// void componentStateChanged(const hyperion::Components component, bool enabled); - void imageToLedsMappingChanged(int mappingType); - void emitImage(int priority, const Image & image, const int timeout_ms); + /// + /// @brief Emits whenever the imageToLedsMapping has changed + /// @param mappingType The new mapping type + /// + void imageToLedsMappingChanged(const int& mappingType); + + /// + /// @brief Emits whenever the visible priority delivers a image which is applied in update() + /// priorities with ledColors won't emit this signal + /// @param image The current image + /// + void currentImage(const Image & image); + void closing(); /// Signal which is emitted, when a new json message should be forwarded void forwardJsonMessage(QJsonObject); - /// Signal which is emitted, after the hyperionStateChanged has been processed with a emit count blocker (250ms interval) - void sendServerInfo(); - - /// Signal emitted when a 3D movie is detected - void videoMode(VideoMode mode); + /// + /// @brief Is emitted from clients who request a videoMode change + /// + void videoMode(const VideoMode& mode); /// - /// @brief Emits whenever new untransformed ledColos data is available, reflects the current visible device + /// @brief A new videoMode was requested (called from Daemon!) /// - void rawLedColors(const std::vector& ledValues); + void newVideoMode(const VideoMode& mode); + + /// + /// @brief Emits whenever a config part changed. SIGNAL PIPE helper for SettingsManager -> HyperionDaemon + /// @param type The settings type from enum + /// @param data The data as QJsonDocument + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + + /// + /// @brief Emits whenever the adjustments have been updated + /// + void adjustmentChanged(); + + /// + /// @brief Signal pipe from EffectEngine to external, emits when effect list has been updated + /// + void effectListUpdated(); + + /// + /// @brief systemImage from the parent HyperionDaemon SystemCapture + /// + void systemImage(const Image& image); + /// + /// @brief v4lImage from the parent HyperionDaemon V4lCapture + /// + void v4lImage(const Image & image); private slots: /// @@ -366,13 +430,23 @@ private slots: /// void update(); - void currentBonjourRecordsChanged(const QList &list); - void bonjourRecordResolved(const QHostInfo &hostInfo, int port); - void bonjourResolve(); - /// check for configWriteable and modified changes, called by _fsWatcher or fallback _cTimer void checkConfigState(QString cfile = NULL); + /// + /// @brief Apply ComponentRegister emits for COMP_ALL. Enables/Disables core timers + /// @param comp The component + /// @param state The new state of the component + /// + void updatedComponentState(const hyperion::Components comp, const bool state); + + /// + /// @brief Apply settings updates for LEDS and COLOR + /// @param type The type from enum + /// @param config The configuration + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: /// @@ -380,7 +454,16 @@ private: /// /// @param[in] qjsonConfig The Json configuration /// - Hyperion(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath); + Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath); + + /// The parent Hyperion Daemon + HyperionDaemon* _daemon; + + /// Settings manager of this instance + SettingsManager* _settingsManager; + + /// Register that holds component states + ComponentRegister _componentRegister; /// The specifiation of the led frame construction and picture integration LedString _ledString; @@ -388,6 +471,9 @@ private: /// specifiation of cloned leds LedString _ledStringClone; + /// Image Processor + ImageProcessor* _imageProcessor; + std::vector _ledStringColorOrder; /// The priority muxer @@ -408,21 +494,14 @@ private: // proto and json Message forwarder MessageForwarder * _messageForwarder; - // json configuration - const QJsonObject& _qjsonConfig; - /// the name of config file QString _configFile; /// root path for all hyperion user data files QString _rootPath; - /// The timer for handling priority channel timeouts - QTimer _timer; - QTimer _timerBonjourResolver; - - /// buffer for leds (with adjustment) - std::vector _ledBuffer; + /// unique id per instance + QString _id; /// Logger instance Logger * _log; @@ -430,32 +509,16 @@ private: /// count of hardware leds unsigned _hwLedCount; - ComponentRegister _componentRegister; - - /// register of input sources and it's prio channel - PriorityRegister _priorityRegister; - - /// flag indicates state for autoselection of input source - bool _sourceAutoSelectEnabled; - - /// holds the current priority channel that is manualy selected - int _currentSourcePriority; - QByteArray _configHash; QSize _ledGridSize; - int _ledMAppingType; - + /// Store the previous compID for smarter update() hyperion::Components _prevCompId; - BonjourServiceBrowser _bonjourBrowser; - BonjourServiceResolver _bonjourResolver; - BonjourRegister _hyperionSessions; - QString _bonjourCurrentServiceToResolve; /// Observe filesystem changes (_configFile), if failed use Timer QFileSystemWatcher _fsWatcher; - QTimer _cTimer; + QTimer* _cTimer; /// holds the prev states of configWriteable and modified bool _prevConfigMod = false; @@ -465,9 +528,16 @@ private: bool _configMod = false; bool _configWrite = true; - /// timers to handle severinfo blocking - QTimer _fsi_timer; - QTimer _fsi_blockTimer; + /// Background effect instance, kept active to react on setting changes + BGEffectHandler* _BGEffectHandler; + /// Capture control for Daemon native capture + CaptureCont* _captureCont; - VideoMode _videoMode; + // lock Hyperion::update() for exec + bool _lockUpdate = false; + + /// buffer for leds (with adjustment) + std::vector _ledBuffer; + /// buffer for leds (without adjustment) + std::vector _rawLedBuffer; }; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 935fcaea..d2c16216 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -6,14 +6,18 @@ #include // Hyperion includes -#include #include #include #include +// settings +#include + // Black border includes #include +class Hyperion; + /// /// The ImageProcessor translates an RGB-image to RGB-values for the leds. The processing is /// performed in two steps. First the average color per led-region is computed. Second a @@ -24,14 +28,16 @@ class ImageProcessor : public QObject Q_OBJECT public: + /// + /// Constructs an image-processor for translating an image to led-color values based on the + /// given led-string specification + /// @param[in] ledString LedString data + /// @param[in] hyperion Hyperion instance pointer + /// + ImageProcessor(const LedString& ledString, Hyperion* hyperion); ~ImageProcessor(); - /// - /// Returns the number of attached leds - /// - unsigned getLedCount() const; - /// /// Specifies the width and height of 'incomming' images. This will resize the buffer-image to /// match the given size. @@ -42,20 +48,39 @@ public: /// void setSize(const unsigned width, const unsigned height); + /// + /// @brief Update the led string (eg on settings change) + /// + void setLedString(const LedString& ledString); + /// Returns starte of black border detector bool blackBorderDetectorEnabled(); - /// Returns starte of black border detector - int ledMappingType(); + /// Returns the current _userMappingType, this may not be the current applied type! + const int & getUserLedMappingType() { return _userMappingType; }; + + /// Returns the current _mappingType + const int & ledMappingType() { return _mappingType; }; static int mappingTypeToInt(QString mappingType); static QString mappingTypeToStr(int mappingType); -public slots: - /// Enable or disable the black border detector - void enableBlackBorderDetector(bool enable); + /// + /// @brief Set the Hyperion::update() requestes led mapping type. This type is used in favour of type set with setLedMappingType. + /// If you don't want to force a mapType set this to -1 (user choice will be set) + /// @param mapType The new mapping type + /// + void setHardLedMappingType(int mapType); - /// Enable or disable the black border detector +public slots: + /// Enable or disable the black border detector based on component + void setBlackbarDetectDisable(bool enable); + + /// + /// @brief Set the user requested led mapping. + /// The type set with setHardLedMappingType() will be used in favour to respect comp specific settings + /// @param mapType The new mapping type + /// void setLedMappingType(int mapType); public: @@ -101,7 +126,7 @@ public: } else { - Warning(_log, "ImageProcessor::process called without image size 0"); + Warning(_log, "ImageProcessor::process called with image size 0"); } // return the computed colors @@ -134,7 +159,7 @@ public: } else { - Warning(_log, "ImageProcessor::process called without image size 0"); + Warning(_log, "Called with image size 0"); } } @@ -150,18 +175,6 @@ public: bool getScanParameters(size_t led, double & hscanBegin, double & hscanEnd, double & vscanBegin, double & vscanEnd) const; private: - /// Friend declaration of the factory for creating ImageProcessor's - friend class ImageProcessorFactory; - - /// - /// Constructs an image-processor for translating an image to led-color values based on the - /// given led-string specification - /// - /// @param[in] ledString The led-string specification - /// @param[in] blackborderThreshold The threshold which the blackborder detector should use - /// - ImageProcessor(const LedString &ledString, const QJsonObject &blackborderConfig); - /// /// Performs black-border detection (if enabled) on the given image /// @@ -172,7 +185,7 @@ private: { if (!_borderProcessor->enabled() && ( _imageToLeds->horizontalBorder()!=0 || _imageToLeds->verticalBorder()!=0 )) { - Debug(Logger::getInstance("BLACKBORDER"), "disabled, reset border"); + Debug(_log, "Reset border"); _borderProcessor->process(image); delete _imageToLeds; _imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds()); @@ -180,8 +193,6 @@ private: if(_borderProcessor->enabled() && _borderProcessor->process(image)) { - //Debug(Logger::getInstance("BLACKBORDER"), "BORDER SWITCH REQUIRED!!"); - const hyperion::BlackBorder border = _borderProcessor->getCurrentBorder(); // Clean up the old mapping @@ -203,10 +214,13 @@ private: } } +private slots: + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: Logger * _log; /// The Led-string specification - const LedString _ledString; + LedString _ledString; /// The processor for black border detection hyperion::BlackBorderProcessor * _borderProcessor; @@ -216,4 +230,11 @@ private: /// Type of image 2 led mapping int _mappingType; + /// Type of last requested user type + int _userMappingType; + /// Type of last requested hard type + int _hardMappingType; + + /// Hyperion instance pointer + Hyperion* _hyperion; }; diff --git a/include/hyperion/ImageProcessorFactory.h b/include/hyperion/ImageProcessorFactory.h deleted file mode 100644 index e7381ad0..00000000 --- a/include/hyperion/ImageProcessorFactory.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -// STL includes -#include - -// QT includes -#include - -#include - -// Forward class declaration -class ImageProcessor; - -/// -/// The ImageProcessor is a singleton factor for creating ImageProcessors that translate images to -/// led color values. -/// -class ImageProcessorFactory -{ -public: - /// - /// Returns the 'singleton' instance (creates the singleton if it does not exist) - /// - /// @return The singleton instance of the ImageProcessorFactory - /// - static ImageProcessorFactory& getInstance(); - -public: - /// - /// Initialises this factory with the given led-configuration - /// - /// @param[in] ledString The led configuration - /// @param[in] blackborderConfig Contains the blackborder configuration - /// - void init(const LedString& ledString, const QJsonObject &blackborderConfig, int mappingType); - - /// - /// Creates a new ImageProcessor. The onwership of the processor is transferred to the caller. - /// - /// @return The newly created ImageProcessor - /// - ImageProcessor* newImageProcessor() const; - -private: - /// The Led-string specification - LedString _ledString; - - /// Reference to the blackborder json configuration values - QJsonObject _blackborderConfig; - - // image 2 led mapping type - int _mappingType; -}; diff --git a/include/hyperion/ImageToLedsMap.h b/include/hyperion/ImageToLedsMap.h index 116d4760..7896592d 100644 --- a/include/hyperion/ImageToLedsMap.h +++ b/include/hyperion/ImageToLedsMap.h @@ -7,6 +7,7 @@ // hyperion-utils includes #include +#include // hyperion includes #include @@ -58,7 +59,7 @@ namespace hyperion const unsigned horizontalBorder() const { return _horizontalBorder; }; const unsigned verticalBorder() const { return _verticalBorder; }; - + /// /// Determines the mean-color for each led using the mapping the image given /// at construction. @@ -86,7 +87,12 @@ namespace hyperion void getMeanLedColor(const Image & image, std::vector & ledColors) const { // Sanity check for the number of leds - assert(_colorsMap.size() == ledColors.size()); + //assert(_colorsMap.size() == ledColors.size()); + if(_colorsMap.size() != ledColors.size()) + { + Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } // Iterate each led and compute the mean auto led = ledColors.begin(); @@ -96,7 +102,7 @@ namespace hyperion *led = color; } } - + /// /// Determines the mean-color for each led using the mapping the image given /// at construction. @@ -124,7 +130,13 @@ namespace hyperion void getUniLedColor(const Image & image, std::vector & ledColors) const { // Sanity check for the number of leds - assert(_colorsMap.size() == ledColors.size()); + // assert(_colorsMap.size() == ledColors.size()); + if(_colorsMap.size() != ledColors.size()) + { + Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size()); + return; + } + // calculate uni color const ColorRgb color = calcMeanColor(image); @@ -136,11 +148,11 @@ namespace hyperion const unsigned _width; /// The height of the indexed image const unsigned _height; - + const unsigned _horizontalBorder; - + const unsigned _verticalBorder; - + /// The absolute indices into the image for each led std::vector> _colorsMap; diff --git a/include/hyperion/MessageForwarder.h b/include/hyperion/MessageForwarder.h index d0d5de1a..ea247c53 100644 --- a/include/hyperion/MessageForwarder.h +++ b/include/hyperion/MessageForwarder.h @@ -10,31 +10,45 @@ #include #include #include +#include +#include +#include // Utils includes #include -class MessageForwarder +#include +#include + +class Hyperion; + +class MessageForwarder : public QObject { + Q_OBJECT public: - struct JsonSlaveAddress { - QHostAddress addr; - quint16 port; - }; - - MessageForwarder(); + MessageForwarder(Hyperion* hyperion, const QJsonDocument & config); ~MessageForwarder(); - + void addJsonSlave(QString slave); void addProtoSlave(QString slave); bool protoForwardingEnabled(); bool jsonForwardingEnabled(); bool forwardingEnabled() { return jsonForwardingEnabled() || protoForwardingEnabled(); }; - QStringList getProtoSlaves(); - QList getJsonSlaves(); + QStringList getProtoSlaves() const { return _protoSlaves; }; + QStringList getJsonSlaves() const { return _jsonSlaves; }; + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private: - QStringList _protoSlaves; - QList _jsonSlaves; + Hyperion* _hyperion; + Logger* _log; + QStringList _protoSlaves; + QStringList _jsonSlaves; }; diff --git a/libsrc/hyperion/MultiColorAdjustment.h b/include/hyperion/MultiColorAdjustment.h similarity index 98% rename from libsrc/hyperion/MultiColorAdjustment.h rename to include/hyperion/MultiColorAdjustment.h index f6568049..df101122 100644 --- a/libsrc/hyperion/MultiColorAdjustment.h +++ b/include/hyperion/MultiColorAdjustment.h @@ -26,7 +26,7 @@ public: */ void addAdjustment(ColorAdjustment * adjustment); - void setAdjustmentForLed(const QString& id, const unsigned startLed, const unsigned endLed); + void setAdjustmentForLed(const QString& id, const unsigned startLed, unsigned endLed); bool verifyAdjustments() const; diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index a3316940..21af614b 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -7,23 +7,25 @@ // QT includes #include #include -#include #include #include // Utils includes #include +#include #include // global defines #define SMOOTHING_MODE_DEFAULT 0 #define SMOOTHING_MODE_PAUSE 1 +class QTimer; +class Logger; /// -/// The PriorityMuxer handles the priority channels. Led values input is written to the priority map +/// The PriorityMuxer handles the priority channels. Led values input/ images are written to the priority map /// and the muxer keeps track of all active priorities. The current priority can be queried and per -/// priority the led colors. +/// priority the led colors. Handles also manual/auto selection mode, provides a lot of signals to hook into priority related events /// class PriorityMuxer : public QObject { @@ -36,17 +38,20 @@ public: { /// The priority of this channel int priority; - /// The absolute timeout of the channel int64_t timeoutTime_ms; /// The colors for each led of the channel std::vector ledColors; + /// The raw Image (size should be preprocessed!) + Image image; /// The component hyperion::Components componentId; /// Who set it QString origin; - /// id fo smoothing config + /// id of smoothing config unsigned smooth_cfg; + /// specific owner description + QString owner; }; /// The lowest possible priority, which is used when no priority channels are active @@ -65,6 +70,38 @@ public: /// ~PriorityMuxer(); + /// + /// @brief Start/Stop the PriorityMuxer update timer; On disabled no priority and timeout updates will be performend + /// @param enable The new state + /// + void setEnable(const bool& enable); + + /// @brief Enable or disable auto source selection + /// @param enable True if it should be enabled else false + /// @param update True to update _currentPriority - INTERNAL usage. + /// @return True if changed has been applied, false if the state is unchanged + /// + bool setSourceAutoSelectEnabled(const bool& enabel, const bool& update = true); + + /// + /// @brief Get the state of source auto selection + /// @return True if enabled, else false + /// + bool isSourceAutoSelectEnabled() const { return _sourceAutoSelectEnabled; }; + + /// + /// @brief Overwrite current lowest piority with manual selection; On success disables aito selection + /// @param priority The + /// @return True on success, false if priority not found + /// + bool setPriority(const uint8_t priority); + + /// + /// @brief Update all ledColos with min length of >= 1 to fit the new led length + /// @param[in] ledCount The count of leds + /// + void updateLedColorsLength(const int& ledCount); + /// /// Returns the current priority /// @@ -87,69 +124,134 @@ public: QList getPriorities() const; /// - /// Returns the information of a specified priority channel + /// Returns the information of a specified priority channel. + /// If a priority is no longer available the _lowestPriorityInfo (255) is returned /// /// @param priority The priority channel /// /// @return The information for the specified priority channel /// - /// @throws std::runtime_error if the priority channel does not exist - /// - const InputInfo& getInputInfo(const int priority) const; + const InputInfo getInputInfo(const int priority) const; /// - /// Sets/Updates the data for a priority channel + /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() + /// A repeated call to update the base data of a known priority won't overwrite their current timeout + /// @param[in] priority The priority of the channel + /// @param[in] component The component of the channel + /// @param[in] origin Who set the channel (CustomString@IP) + /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] smooth_cfg The smooth id to use /// - /// @param[in] priority The priority of the channel - /// @param[in] ledColors The led colors of the priority channel - /// @param[in] timeoutTime_ms The absolute timeout time of the channel - /// @param[in] component The component of the channel - /// @param[in] origin Who set the channel - /// - void setInput(const int priority, const std::vector& ledColors, const int64_t timeoutTime_ms=-1, hyperion::Components component=hyperion::COMP_INVALID, const QString origin="System", unsigned smooth_cfg=SMOOTHING_MODE_DEFAULT); + void registerInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = SMOOTHING_MODE_DEFAULT); /// - /// Clears the specified priority channel + /// @brief Update the current color of a priority (prev registered with registerInput()) + /// @param priority The priority to update + /// @param ledColors The colors + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @return True on success, false when priority is not found + /// + const bool setInput(const int priority, const std::vector& ledColors, int64_t timeout_ms = -1); + + /// + /// @brief Update the current image of a priority (prev registered with registerInput()) + /// @param priority The priority to update + /// @param image The new image + /// @param timeout_ms The new timeout (defaults to -1 endless) + /// @return True on success, false when priority is not found + /// + const bool setInputImage(const int priority, const Image& image, int64_t timeout_ms = -1); + + /// + /// Clears the specified priority channel and update _currentPriority on success /// /// @param[in] priority The priority of the channel to clear + /// @return True if priority has been cleared else false (not found) /// - void clearInput(const int priority); + const bool clearInput(const uint8_t priority); /// /// Clears all priority channels /// void clearAll(bool forceClearAll=false); +signals: + /// + /// @brief Signal which emits when a effect or color with timeout > -1 is running, once per second + /// + void timeRunner(); + + /// + /// @brief A priority has been added (registerInput()) or deleted, method clear or timeout clear + /// @param priority The priority which has changed + /// @param state If true it was added else it was removed! + /// + void priorityChanged(const quint8& priority, const bool& state); + + /// + /// @brief Emits whenever the visible priority has changed + /// @param priority The new visible prioritiy + /// + void visiblePriorityChanged(const quint8& priority); + + /// + /// @brief Emits whenever a priority changes active state + /// @param priority The priority who changed the active state + /// @param state The new state, state true = active else false + /// + void activeStateChanged(const quint8& priority, const bool& state); + + /// + /// @brief Emits whenever the auto selection state has been changed + /// @param state The new state of auto selection; True enabled else false + /// + void autoSelectChanged(const bool& state); + + /// + /// @brief Emits whenever something changes which influences the priorities listing + /// Emits also in 1s interval when a COLOR or EFFECT is running with a timeout > -1 + /// + void prioritiesChanged(void); + + /// + /// internal used signal to resolve treading issues with timer + /// + void signalTimeTrigger(); + +private slots: + /// + /// Slot which is called to adapt to 1s interval for signal timeRunner() / prioritiesChanged() + /// + void timeTrigger(); + /// /// Updates the current time. Channels with a configured time out will be checked and cleared if /// required. /// - /// @param[in] now The current time - /// - void setCurrentTime(const int64_t& now); - -signals: - /// - /// Signal which is called, when a effect or color with timeout is running, once per second - /// - void timerunner(); - -private slots: - /// - /// Slots which is called to adapt to 1s interval for signal timerunner() - /// - void emitReq(); + void setCurrentTime(void); private: + /// Logger instance + Logger* _log; + /// The current priority (lowest value in _activeInputs) int _currentPriority; + /// The manual select priority set with setPriority + int _manualSelectedPriority; + /// The mapping from priority channel to led-information QMap _activeInputs; /// The information of the lowest priority channel InputInfo _lowestPriorityInfo; - QTimer _timer; - QTimer _blockTimer; + // Reflect the state of auto select + bool _sourceAutoSelectEnabled; + + // Timer to update Muxer times independent + QTimer* _updateTimer; + + QTimer* _timer; + QTimer* _blockTimer; }; diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h new file mode 100644 index 00000000..f3fa1d63 --- /dev/null +++ b/include/hyperion/SettingsManager.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +// qt incl +#include + +class SettingsTable; +class Hyperion; + +/// +/// @brief Manage the settings read write from/to database, on settings changed will emit a signal to update components accordingly +/// +class SettingsManager : public QObject +{ + Q_OBJECT +public: + /// + /// @brief Construct a settings manager and assign a hyperion instance + /// @params hyperion The parent hyperion instance + /// @params instance Instance number of Hyperion + /// + SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile); + + /// + /// @brief Construct a settings manager for HyperionDaemon + /// + SettingsManager(const quint8& instance, const QString& configFile); + ~SettingsManager(); + + /// + /// @brief Save a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + const bool saveSettings(QJsonObject config, const bool& correct = false); + + /// + /// @brief get a single setting json from database + /// @param type The settings::type from enum + /// @return The requested json data as QJsonDocument + /// + const QJsonDocument getSetting(const settings::type& type); + + /// + /// @brief get the full settings object of this instance (with global settings) + /// @return The requested json + /// + const QJsonObject & getSettings() { return _qconfig; }; + +signals: + /// + /// @brief Emits whenever a config part changed. Comparison of database and new data to prevent false positive + /// @param type The settings type from enum + /// @param data The data as QJsonDocument + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + +private: + /// Hyperion instance + Hyperion* _hyperion; + /// Logger instance + Logger* _log; + /// instance of database table interface + SettingsTable* _sTable; + /// the schema + static QJsonObject schemaJson; + /// the current config of this instance + QJsonObject _qconfig; +}; diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index 912e14ac..e0c64206 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -1,14 +1,20 @@ #pragma once // Qt includes -#include #include // Hyperion includes -#include +#include #include +#include +class Hyperion; +class QTcpServer; +class QTcpSocket; class JsonClientConnection; +class BonjourServiceRegister; +class ComponentRegister; +class NetOrigin; /// /// This class creates a TCP server which accepts connections wich can then send @@ -22,10 +28,9 @@ class JsonServer : public QObject public: /// /// JsonServer constructor - /// @param hyperion Hyperion instance - /// @param port port number on which to start listening for connections + /// @param The configuration /// - JsonServer(uint16_t port = 19444); + JsonServer(const QJsonDocument& config); ~JsonServer(); /// @@ -59,9 +64,16 @@ public slots: /// void sendMessage(const QJsonObject & message, QTcpSocket * socket); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + private: /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// Link to Hyperion to get config state emiter Hyperion * _hyperion; @@ -72,6 +84,16 @@ private: /// the logger instance Logger * _log; - /// Flag if forwarder is enabled - bool _forwarder_enabled = true; + /// Component Register pointer + ComponentRegister* _componentRegister; + + NetOrigin* _netOrigin; + + /// port + uint16_t _port = 0; + + BonjourServiceRegister * _serviceRegister = nullptr; + + void start(); + void stop(); }; diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h index 27218e5a..8b9977e2 100644 --- a/include/leddevice/LedDevice.h +++ b/include/leddevice/LedDevice.h @@ -55,13 +55,19 @@ public: /// virtual int open(); + /// + /// @brief Get color order of device + /// @return The color order + /// + const QString & getColorOrder() { return _colorOrder; }; + static int addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr); static const LedDeviceRegistry& getDeviceMap(); - static void setActiveDevice(QString dev); - static QString activeDevice() { return _activeDevice; } + void setActiveDevice(QString dev); + const QString & getActiveDevice() { return _activeDevice; }; static QJsonObject getLedDeviceSchemas(); - static void setLedCount(int ledCount); - static int getLedCount() { return _ledCount; } + void setLedCount(int ledCount); + int getLedCount() { return _ledCount; } void setEnable(bool enable); bool enabled() { return _enabled; }; @@ -95,12 +101,12 @@ protected: bool _deviceReady; - static QString _activeDevice; + QString _activeDevice; static LedDeviceRegistry _ledDeviceMap; - static int _ledCount; - static int _ledRGBCount; - static int _ledRGBWCount; + int _ledCount; + int _ledRGBCount; + int _ledRGBWCount; /// Timer object which makes sure that led data is written at a minimum rate /// e.g. Adalight device will switch off when it does not receive data at least every 15 seconds @@ -116,4 +122,5 @@ private: std::vector _ledValues; bool _componentRegistered; bool _enabled; + QString _colorOrder; }; diff --git a/include/leddevice/LedDeviceFactory.h b/include/leddevice/LedDeviceFactory.h index 29691a98..5a26a8ed 100644 --- a/include/leddevice/LedDeviceFactory.h +++ b/include/leddevice/LedDeviceFactory.h @@ -4,7 +4,6 @@ // Leddevice includes #include - /// /// The LedDeviceFactory is responsible for constructing 'LedDevices' /// @@ -20,5 +19,5 @@ public: /// @return The constructed LedDevice or nullptr if configuration is invalid. The ownership of /// the constructed LedDevice is tranferred to the caller /// - static LedDevice * construct(const QJsonObject & deviceConfig, const int ledCount); + static LedDevice * construct(const QJsonObject & deviceConfig); }; diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h index 12daf1c8..e285a6cb 100644 --- a/include/protoserver/ProtoServer.h +++ b/include/protoserver/ProtoServer.h @@ -4,23 +4,29 @@ #include // Qt includes -#include #include #include #include - -// Hyperion includes -#include +#include // hyperion includes #include #include #include #include +#include + +// settings +#include // forward decl class ProtoClientConnection; class ProtoConnection; +class QTcpServer; +class Hyperion; +class BonjourServiceRegister; +class ComponentRegister; +class NetOrigin; namespace proto { class HyperionRequest; @@ -38,10 +44,9 @@ class ProtoServer : public QObject public: /// /// ProtoServer constructor - /// @param hyperion Hyperion instance - /// @param port port number on which to start listening for connections + /// @param config the configuration /// - ProtoServer(uint16_t port = 19445); + ProtoServer(const QJsonDocument& config); ~ProtoServer(); /// @@ -53,6 +58,13 @@ public slots: void sendImageToProtoSlaves(int priority, const Image & image, int duration_ms); void componentStateChanged(const hyperion::Components component, bool enable); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + signals: /// /// Forwarding videoMode @@ -78,7 +90,7 @@ private: Hyperion * _hyperion; /// The TCP server object - QTcpServer _server; + QTcpServer * _server; /// List with open connections QSet _openConnections; @@ -90,6 +102,22 @@ private: /// Logger instance Logger * _log; + /// Component Register + ComponentRegister* _componentRegister; + + /// Network Origin Check + NetOrigin* _netOrigin; + + /// Service register + BonjourServiceRegister * _serviceRegister = nullptr; + /// flag if forwarder is enabled bool _forwarder_enabled; + + uint16_t _port = 0; + + /// Start server + void start(); + /// Stop server + void stop(); }; diff --git a/include/python/PythonInit.h b/include/python/PythonInit.h new file mode 100644 index 00000000..97b0cb16 --- /dev/null +++ b/include/python/PythonInit.h @@ -0,0 +1,13 @@ +#pragma once + +/// +/// @brief Handle the PythonInit, module registers and DeInit +/// +class PythonInit +{ +private: + friend class HyperionDaemon; + + PythonInit(); + ~PythonInit(); +}; diff --git a/include/python/PythonUtils.h b/include/python/PythonUtils.h new file mode 100644 index 00000000..e7cd08b4 --- /dev/null +++ b/include/python/PythonUtils.h @@ -0,0 +1,15 @@ +#pragma once + +#undef slots +#include +#define slots + +// decl +extern PyThreadState* mainThreadState; + +// py path seperator +#ifdef TARGET_WINDOWS + #define PY_PATH_SEP ";"; +#else // not windows + #define PY_PATH_SEP ":"; +#endif diff --git a/include/udplistener/UDPListener.h b/include/udplistener/UDPListener.h index 2e7c9e64..bf294df6 100644 --- a/include/udplistener/UDPListener.h +++ b/include/udplistener/UDPListener.h @@ -4,16 +4,22 @@ #include // Qt includes -#include #include #include +#include // Hyperion includes -#include #include #include +// settings +#include + +class Hyperion; class UDPClientConnection; +class BonjourServiceRegister; +class QUdpSocket; +class NetOrigin; /// /// This class creates a UDP server which accepts connections from boblight clients. @@ -28,26 +34,25 @@ public: /// @param hyperion Hyperion instance /// @param port port number on which to start listening for connections /// - UDPListener(const int priority, const int timeout, const QString& address, quint16 listenPort, bool shared); + UDPListener(const QJsonDocument& config); ~UDPListener(); /// /// @return the port number on which this UDP listens for incoming connections /// uint16_t getPort() const; - + /// /// @return true if server is active (bind to a port) /// bool active() { return _isActive; }; - bool componentState() { return active(); }; public slots: /// /// bind server to network /// void start(); - + /// /// close server /// @@ -55,8 +60,12 @@ public slots: void componentStateChanged(const hyperion::Components component, bool enable); -signals: - void statusChanged(bool isActive); + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// @@ -83,12 +92,18 @@ private: /// Logger instance Logger * _log; - + + /// Bonjour Service Register + BonjourServiceRegister* _bonjourService = nullptr; + /// state of connection bool _isActive; - + /// address to bind QHostAddress _listenAddress; - quint16 _listenPort; + uint16_t _listenPort; QAbstractSocket::BindFlag _bondage; + + /// Check Network Origin + NetOrigin* _netOrigin; }; diff --git a/include/utils/Components.h b/include/utils/Components.h index 6af45394..3e917d9a 100644 --- a/include/utils/Components.h +++ b/include/utils/Components.h @@ -3,12 +3,14 @@ namespace hyperion { + /** * Enumeration of components in Hyperion. */ enum Components { COMP_INVALID, + COMP_ALL, COMP_SMOOTHING, COMP_BLACKBORDER, COMP_FORWARDER, @@ -17,6 +19,7 @@ enum Components COMP_GRABBER, COMP_V4L, COMP_COLOR, + COMP_IMAGE, COMP_EFFECT, COMP_PROTOSERVER, COMP_LEDDEVICE @@ -26,6 +29,7 @@ inline const char* componentToString(Components c) { switch (c) { + case COMP_ALL: return "Hyperion"; case COMP_SMOOTHING: return "Smoothing"; case COMP_BLACKBORDER: return "Blackborder detector"; case COMP_FORWARDER: return "Json/Proto forwarder"; @@ -35,6 +39,7 @@ inline const char* componentToString(Components c) case COMP_V4L: return "V4L capture device"; case COMP_COLOR: return "Solid color"; case COMP_EFFECT: return "Effect"; + case COMP_IMAGE: return "Image"; case COMP_PROTOSERVER: return "Proto Server"; case COMP_LEDDEVICE: return "LED device"; default: return ""; @@ -45,6 +50,7 @@ inline const char* componentToIdString(Components c) { switch (c) { + case COMP_ALL: return "ALL"; case COMP_SMOOTHING: return "SMOOTHING"; case COMP_BLACKBORDER: return "BLACKBORDER"; case COMP_FORWARDER: return "FORWARDER"; @@ -54,6 +60,7 @@ inline const char* componentToIdString(Components c) case COMP_V4L: return "V4L"; case COMP_COLOR: return "COLOR"; case COMP_EFFECT: return "EFFECT"; + case COMP_IMAGE: return "IMAGE"; case COMP_PROTOSERVER: return "PROTOSERVER"; case COMP_LEDDEVICE: return "LEDDEVICE"; default: return ""; @@ -63,6 +70,7 @@ inline const char* componentToIdString(Components c) inline Components stringToComponent(QString component) { component = component.toUpper(); + if (component == "ALL") return COMP_ALL; if (component == "SMOOTHING") return COMP_SMOOTHING; if (component == "BLACKBORDER") return COMP_BLACKBORDER; if (component == "FORWARDER") return COMP_FORWARDER; @@ -72,6 +80,7 @@ inline Components stringToComponent(QString component) if (component == "V4L") return COMP_V4L; if (component == "COLOR") return COMP_COLOR; if (component == "EFFECT") return COMP_EFFECT; + if (component == "IMAGE") return COMP_IMAGE; if (component == "PROTOSERVER") return COMP_PROTOSERVER; if (component == "LEDDEVICE") return COMP_LEDDEVICE; diff --git a/include/utils/FileUtils.h b/include/utils/FileUtils.h index 52a7e569..e0d29e13 100644 --- a/include/utils/FileUtils.h +++ b/include/utils/FileUtils.h @@ -13,6 +13,11 @@ namespace FileUtils { QString getBaseName( QString sourceFile); QString getDirName( QString sourceFile); + /// + /// @brief remove directory recursive given by path + /// @param[in] path Path to directory + bool removeDir(const QString& path, Logger* log); + /// /// @brief check if the file exists /// @param[in] path The file path to check @@ -45,9 +50,10 @@ QString getDirName( QString sourceFile); /// @brief delete a file by given path /// @param[in] path The file path to delete /// @param[in] log The logger of the caller to print errors + /// @param[in] ignError Ignore errors during file delete (no log output) /// @return true on success else false /// - bool removeFile(const QString& path, Logger* log); + bool removeFile(const QString& path, Logger* log, bool ignError=false); /// /// @brief Convert a path that may contain special placeholders diff --git a/include/utils/Image.h b/include/utils/Image.h index d2c427e5..633dddf8 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -71,6 +71,38 @@ public: memcpy(_pixels, other._pixels, other._width * other._height * sizeof(Pixel_T)); } + // Define assignment operator in terms of the copy constructor + // More to read: https://stackoverflow.com/questions/255612/dynamically-allocating-an-array-of-objects?answertab=active#tab-top + Image& operator=(Image rhs) + { + rhs.swap(*this); + return *this; + } + + void swap(Image& s) noexcept + { + using std::swap; + swap(this->_width, s._width); + swap(this->_height, s._height); + swap(this->_pixels, s._pixels); + swap(this->_endOfPixels, s._endOfPixels); + } + + // C++11 + Image(Image&& src) noexcept + : _width(0) + , _height(0) + , _pixels(NULL) + , _endOfPixels(NULL) + { + src.swap(*this); + } + Image& operator=(Image&& src) noexcept + { + src.swap(*this); + return *this; + } + /// /// Destructor /// diff --git a/include/utils/JsonProcessor.h b/include/utils/JsonProcessor.h deleted file mode 100644 index e256ff79..00000000 --- a/include/utils/JsonProcessor.h +++ /dev/null @@ -1,280 +0,0 @@ -#pragma once - -// hyperion includes -#include -#include -#include -#include - -// qt includess -#include -#include -#include - -// createEffect helper -struct find_schema: std::unary_function -{ - QString pyFile; - find_schema(QString pyFile):pyFile(pyFile) { } - bool operator()(EffectSchema const& schema) const - { - return schema.pyFile == pyFile; - } -}; - -// deleteEffect helper -struct find_effect: std::unary_function -{ - QString effectName; - find_effect(QString effectName) :effectName(effectName) { } - bool operator()(EffectDefinition const& effectDefinition) const - { - return effectDefinition.name == effectName; - } -}; - -class ImageProcessor; - -class JsonProcessor : public QObject -{ - Q_OBJECT - -public: - /// - /// Constructor - /// - /// @param peerAddress provide the Address of the peer - /// @param log The Logger class of the creator - /// @param parent Parent QObject - /// @param noListener if true, this instance won't listen for hyperion push events - /// - JsonProcessor(QString peerAddress, Logger* log, QObject* parent, bool noListener = false); - - /// - /// Handle an incoming JSON message - /// - /// @param message the incoming message as string - /// - void handleMessage(const QString & message); - - /// - /// send a forced serverinfo to a client - /// - void forceServerInfo(); - -public slots: - /// - /// @brief is called whenever the current Hyperion instance pushes new led raw values (if enabled) - /// @param ledColors The current ledColors - /// - void streamLedcolorsUpdate(const std::vector& ledColors); - - /// push images whenever hyperion emits (if enabled) - void setImage(int priority, const Image & image, int duration_ms); - - /// process and push new log messages from logger (if enabled) - void incommingLogMessage(Logger::T_LOG_MESSAGE); - -signals: - /// - /// Signal which is emitted when a sendSuccessReply() has been executed - /// - void pushReq(); - /// - /// Signal emits with the reply message provided with handleMessage() - /// - void callbackMessage(QJsonObject); - - /// - /// Signal emits whenever a jsonmessage should be forwarded - /// - void forwardJsonMessage(QJsonObject); - -private: - /// The peer address of the client - QString _peerAddress; - - /// Log instance - Logger* _log; - - /// Hyperion instance - Hyperion* _hyperion; - - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - - /// holds the state before off state - static std::map _componentsPrevState; - - /// returns if hyperion is on or off - inline bool hyperionIsActive() { return JsonProcessor::_componentsPrevState.empty(); }; - - // streaming buffers - QJsonObject _streaming_leds_reply; - QJsonObject _streaming_image_reply; - QJsonObject _streaming_logging_reply; - bool _ledcolorsLedsActive = false; - - /// flag to determine state of log streaming - bool _streaming_logging_activated; - - /// mutex to determine state of image streaming - QMutex _image_stream_mutex; - /// mutex to determine state of led color streaming - QMutex _led_stream_mutex; - - /// timeout for live video refresh - volatile qint64 _image_stream_timeout; - - /// timeout for led color refresh - volatile qint64 _led_stream_timeout; - - /// - /// Handle an incoming JSON Color message - /// - /// @param message the incoming message - /// - void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Image message - /// - /// @param message the incoming message - /// - void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message - /// - /// @param message the incoming message - /// - void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Write JSON Effect) - /// - /// @param message the incoming message - /// - void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Delete JSON Effect) - /// - /// @param message the incoming message - /// - void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON System info message - /// - /// @param message the incoming message - /// - void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Server info message - /// - /// @param message the incoming message - /// - void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clear message - /// - /// @param message the incoming message - /// - void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clearall message - /// - /// @param message the incoming message - /// - void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Adjustment message - /// - /// @param message the incoming message - /// - void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON SourceSelect message - /// - /// @param message the incoming message - /// - void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message and check subcommand - /// - /// @param message the incoming message - /// - void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleConfigGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON SetConfig message from handleConfigCommand() - /// - /// @param message the incoming message - /// - void handleConfigSetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Component State message - /// - /// @param message the incoming message - /// - void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Led Colors message - /// - /// @param message the incoming message - /// - void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Logging message - /// - /// @param message the incoming message - /// - void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Proccessing message - /// - /// @param message the incoming message - /// - void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON VideoMode message - /// - /// @param message the incoming message - /// - void handleVideoModeCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON message of unknown type - /// - void handleNotImplemented(); - - /// - /// Send a standard reply indicating success - /// - void sendSuccessReply(const QString &command="", const int tan=0); - - /// - /// Send an error message back to the client - /// - /// @param error String describing the error - /// - void sendErrorReply(const QString & error, const QString &command="", const int tan=0); -}; diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h index 6187b226..4f55475d 100644 --- a/include/utils/JsonUtils.h +++ b/include/utils/JsonUtils.h @@ -26,7 +26,7 @@ namespace JsonUtils{ bool readSchema(const QString& path, QJsonObject& obj, Logger* log); /// - /// @brief parse a json QString and get the result on success + /// @brief parse a json QString and get a QJsonObject. Overloaded funtion /// @param[in] path The file path/name just used for log messages /// @param[in] data Data to parse /// @param[out] obj Retuns the parsed QJsonObject @@ -35,6 +35,26 @@ namespace JsonUtils{ /// bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log); + /// + /// @brief parse a json QString and get a QJsonArray. Overloaded function + /// @param[in] path The file path/name just used for log messages + /// @param[in] data Data to parse + /// @param[out] arr Retuns the parsed QJsonArray + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log); + + /// + /// @brief parse a json QString and get a QJsonDocument + /// @param[in] path The file path/name just used for log messages + /// @param[in] data Data to parse + /// @param[out] doc Retuns the parsed QJsonDocument + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log); + /// /// @brief Validate json data against a schema /// @param[in] file The path/name of json file just used for log messages @@ -45,6 +65,16 @@ namespace JsonUtils{ /// bool validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log); + /// + /// @brief Validate json data against a schema + /// @param[in] file The path/name of json file just used for log messages + /// @param[in] json The json data + /// @param[in] schema The schema object + /// @param[in] log The logger of the caller to print errors + /// @return true on success else false + /// + bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log); + /// /// @brief Write json data to file /// @param[in] filenameThe file path to write diff --git a/include/utils/hyperion.h b/include/utils/hyperion.h new file mode 100644 index 00000000..3751ec49 --- /dev/null +++ b/include/utils/hyperion.h @@ -0,0 +1,320 @@ +#pragma once + +#include +#include +#include +// fg effect +#include + +/// +/// @brief Provide utility methods for Hyperion class +/// +namespace hyperion { + + void handleInitialEffect(Hyperion* hyperion, const QJsonObject& FGEffectConfig) + { + #define FGCONFIG_ARRAY fgColorConfig.toArray() + const int FG_PRIORITY = 0; + const int DURATION_INFINITY = 0; + + // initial foreground effect/color + if (FGEffectConfig["enable"].toBool(true)) + { + const QString fgTypeConfig = FGEffectConfig["type"].toString("effect"); + const QString fgEffectConfig = FGEffectConfig["effect"].toString("Rainbow swirl fast"); + const QJsonValue fgColorConfig = FGEffectConfig["color"]; + int default_fg_duration_ms = 3000; + int fg_duration_ms = FGEffectConfig["duration_ms"].toInt(default_fg_duration_ms); + if (fg_duration_ms == DURATION_INFINITY) + { + fg_duration_ms = default_fg_duration_ms; + Warning(Logger::getInstance("HYPERION"), "foreground effect duration 'infinity' is forbidden, set to default value %d ms",default_fg_duration_ms); + } + if ( fgTypeConfig.contains("color") ) + { + ColorRgb fg_color = { + (uint8_t)FGCONFIG_ARRAY.at(0).toInt(0), + (uint8_t)FGCONFIG_ARRAY.at(1).toInt(0), + (uint8_t)FGCONFIG_ARRAY.at(2).toInt(0) + }; + hyperion->setColor(FG_PRIORITY, fg_color, fg_duration_ms); + Info(Logger::getInstance("HYPERION"),"Inital foreground color set (%d %d %d)",fg_color.red,fg_color.green,fg_color.blue); + } + else + { + int result = hyperion->setEffect(fgEffectConfig, FG_PRIORITY, fg_duration_ms); + Info(Logger::getInstance("HYPERION"),"Inital foreground effect '%s' %s", QSTRING_CSTR(fgEffectConfig), ((result == 0) ? "started" : "failed")); + } + } + #undef FGCONFIG_ARRAY + } + + ColorOrder createColorOrder(const QJsonObject &deviceConfig) + { + return stringToColorOrder(deviceConfig["colorOrder"].toString("rgb")); + } + + RgbTransform* createRgbTransform(const QJsonObject& colorConfig) + { + const double backlightThreshold = colorConfig["backlightThreshold"].toDouble(0.0); + const bool backlightColored = colorConfig["backlightColored"].toBool(false); + const double brightness = colorConfig["brightness"].toInt(100); + const double brightnessComp= colorConfig["brightnessCompensation"].toInt(100); + const double gammaR = colorConfig["gammaRed"].toDouble(1.0); + const double gammaG = colorConfig["gammaGreen"].toDouble(1.0); + const double gammaB = colorConfig["gammaBlue"].toDouble(1.0); + + RgbTransform* transform = new RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, brightness, brightnessComp); + return transform; + } + + RgbChannelAdjustment* createRgbChannelAdjustment(const QJsonObject& colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB) + { + const QJsonArray& channelConfig = colorConfig[channelName].toArray(); + RgbChannelAdjustment* adjustment = new RgbChannelAdjustment( + channelConfig[0].toInt(defaultR), + channelConfig[1].toInt(defaultG), + channelConfig[2].toInt(defaultB), + "ChannelAdjust_"+channelName.toUpper() + ); + return adjustment; + } + + ColorAdjustment * createColorAdjustment(const QJsonObject & adjustmentConfig) + { + const QString id = adjustmentConfig["id"].toString("default"); + + RgbChannelAdjustment * blackAdjustment = createRgbChannelAdjustment(adjustmentConfig, "black" , 0, 0, 0); + RgbChannelAdjustment * whiteAdjustment = createRgbChannelAdjustment(adjustmentConfig, "white" , 255,255,255); + RgbChannelAdjustment * redAdjustment = createRgbChannelAdjustment(adjustmentConfig, "red" , 255, 0, 0); + RgbChannelAdjustment * greenAdjustment = createRgbChannelAdjustment(adjustmentConfig, "green" , 0,255, 0); + RgbChannelAdjustment * blueAdjustment = createRgbChannelAdjustment(adjustmentConfig, "blue" , 0, 0,255); + RgbChannelAdjustment * cyanAdjustment = createRgbChannelAdjustment(adjustmentConfig, "cyan" , 0,255,255); + RgbChannelAdjustment * magentaAdjustment = createRgbChannelAdjustment(adjustmentConfig, "magenta", 255, 0,255); + RgbChannelAdjustment * yellowAdjustment = createRgbChannelAdjustment(adjustmentConfig, "yellow" , 255,255, 0); + RgbTransform * rgbTransform = createRgbTransform(adjustmentConfig); + + ColorAdjustment * adjustment = new ColorAdjustment(); + adjustment->_id = id; + adjustment->_rgbBlackAdjustment = *blackAdjustment; + adjustment->_rgbWhiteAdjustment = *whiteAdjustment; + adjustment->_rgbRedAdjustment = *redAdjustment; + adjustment->_rgbGreenAdjustment = *greenAdjustment; + adjustment->_rgbBlueAdjustment = *blueAdjustment; + adjustment->_rgbCyanAdjustment = *cyanAdjustment; + adjustment->_rgbMagentaAdjustment = *magentaAdjustment; + adjustment->_rgbYellowAdjustment = *yellowAdjustment; + adjustment->_rgbTransform = *rgbTransform; + + // Cleanup the allocated individual adjustments + delete blackAdjustment; + delete whiteAdjustment; + delete redAdjustment; + delete greenAdjustment; + delete blueAdjustment; + delete cyanAdjustment; + delete magentaAdjustment; + delete yellowAdjustment; + delete rgbTransform; + + return adjustment; + } + + MultiColorAdjustment * createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorConfig) + { + // Create the result, the transforms are added to this + MultiColorAdjustment * adjustment = new MultiColorAdjustment(ledCnt); + + const QJsonValue adjustmentConfig = colorConfig["channelAdjustment"]; + const QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); + + const QJsonArray & adjustmentConfigArray = adjustmentConfig.toArray(); + for (signed i = 0; i < adjustmentConfigArray.size(); ++i) + { + const QJsonObject & config = adjustmentConfigArray.at(i).toObject(); + ColorAdjustment * colorAdjustment = createColorAdjustment(config); + adjustment->addAdjustment(colorAdjustment); + + const QString ledIndicesStr = config["leds"].toString("").trimmed(); + if (ledIndicesStr.compare("*") == 0) + { + // Special case for indices '*' => all leds + adjustment->setAdjustmentForLed(colorAdjustment->_id, 0, ledCnt-1); + //Info(_log, "ColorAdjustment '%s' => [0; %d]", QSTRING_CSTR(colorAdjustment->_id), ledCnt-1); + continue; + } + + if (!overallExp.exactMatch(ledIndicesStr)) + { + //Error(_log, "Given led indices %d not correct format: %s", i, QSTRING_CSTR(ledIndicesStr)); + continue; + } + + std::stringstream ss; + const QStringList ledIndexList = ledIndicesStr.split(","); + for (int i=0; i 0) + { + ss << ", "; + } + if (ledIndexList[i].contains("-")) + { + QStringList ledIndices = ledIndexList[i].split("-"); + int startInd = ledIndices[0].toInt(); + int endInd = ledIndices[1].toInt(); + + adjustment->setAdjustmentForLed(colorAdjustment->_id, startInd, endInd); + ss << startInd << "-" << endInd; + } + else + { + int index = ledIndexList[i].toInt(); + adjustment->setAdjustmentForLed(colorAdjustment->_id, index, index); + ss << index; + } + } + //Info(_log, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); + } + + return adjustment; + } + + /** + * Construct the 'led-string' with the integration area definition per led and the color + * ordering of the RGB channels + * @param ledsConfig The configuration of the led areas + * @param deviceOrder The default RGB channel ordering + * @return The constructed ledstring + */ + LedString createLedString(const QJsonArray& ledConfigArray, const ColorOrder deviceOrder) + { + LedString ledString; + const QString deviceOrderStr = colorOrderToString(deviceOrder); + int maxLedId = ledConfigArray.size(); + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + Led led; + led.index = index["index"].toInt(); + led.clone = index["clone"].toInt(-1); + if ( led.clone < -1 || led.clone >= maxLedId ) + { + //Warning(_log, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); + led.clone = -1; + } + + if ( led.clone < 0 ) + { + const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); + const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); + led.minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); + led.maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); + led.minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); + led.maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); + // Fix if the user swapped min and max + if (led.minX_frac > led.maxX_frac) + { + std::swap(led.minX_frac, led.maxX_frac); + } + if (led.minY_frac > led.maxY_frac) + { + std::swap(led.minY_frac, led.maxY_frac); + } + + // Get the order of the rgb channels for this led (default is device order) + led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); + ledString.leds().push_back(led); + } + } + + // Make sure the leds are sorted (on their indices) + std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); + return ledString; + } + + LedString createLedStringClone(const QJsonArray& ledConfigArray, const ColorOrder deviceOrder) + { + LedString ledString; + const QString deviceOrderStr = colorOrderToString(deviceOrder); + int maxLedId = ledConfigArray.size(); + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + Led led; + led.index = index["index"].toInt(); + led.clone = index["clone"].toInt(-1); + if ( led.clone < -1 || led.clone >= maxLedId ) + { + //Warning(_log, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); + led.clone = -1; + } + + if ( led.clone >= 0 ) + { + //Debug(_log, "LED %d: clone from led %d", led.index, led.clone); + led.minX_frac = 0; + led.maxX_frac = 0; + led.minY_frac = 0; + led.maxY_frac = 0; + // Get the order of the rgb channels for this led (default is device order) + led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); + + ledString.leds().push_back(led); + } + + } + + // Make sure the leds are sorted (on their indices) + std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); + return ledString; + } + + QSize getLedLayoutGridSize(const QJsonArray& ledConfigArray) + { + std::vector midPointsX; + std::vector midPointsY; + + for (signed i = 0; i < ledConfigArray.size(); ++i) + { + const QJsonObject& index = ledConfigArray[i].toObject(); + + if (index["clone"].toInt(-1) < 0 ) + { + const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); + const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); + double minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); + double maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); + double minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); + double maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); + // Fix if the user swapped min and max + if (minX_frac > maxX_frac) + { + std::swap(minX_frac, maxX_frac); + } + if (minY_frac > maxY_frac) + { + std::swap(minY_frac, maxY_frac); + } + + // calculate mid point and make grid calculation + midPointsX.push_back( int(1000.0*(minX_frac + maxX_frac) / 2.0) ); + midPointsY.push_back( int(1000.0*(minY_frac + maxY_frac) / 2.0) ); + } + } + + // remove duplicates + std::sort(midPointsX.begin(), midPointsX.end()); + midPointsX.erase(std::unique(midPointsX.begin(), midPointsX.end()), midPointsX.end()); + std::sort(midPointsY.begin(), midPointsY.end()); + midPointsY.erase(std::unique(midPointsY.begin(), midPointsY.end()), midPointsY.end()); + + QSize gridSize( midPointsX.size(), midPointsY.size() ); + //Debug(_log, "led layout grid: %dx%d", gridSize.width(), gridSize.height()); + + return gridSize; + } +}; diff --git a/include/utils/settings.h b/include/utils/settings.h new file mode 100644 index 00000000..7af1a262 --- /dev/null +++ b/include/utils/settings.h @@ -0,0 +1,99 @@ +#pragma once +#include +#include + +/// +/// @brief Provide util methods to work with SettingsManager class +/// +namespace settings { +// all available settings sections +enum type { + BGEFFECT, + FGEFFECT, + BLACKBORDER, + BOBLSERVER, + COLOR, + DEVICE, + EFFECTS, + NETFORWARD, + SYSTEMCAPTURE, + GENERAL, + V4L2, + JSONSERVER, + LEDCONFIG, + LEDS, + LOGGER, + PROTOSERVER, + SMOOTHING, + UDPLISTENER, + WEBSERVER, + INSTCAPTURE, + NETWORK, + INVALID +}; + +/// +/// @brief Convert settings::type to string representation +/// @param type The settings::type from enum +/// @return The settings type as string +/// +inline QString typeToString(const type& type) +{ + switch (type) + { + case BGEFFECT: return "backgroundEffect"; + case FGEFFECT: return "foregroundEffect"; + case BLACKBORDER: return "blackborderdetector"; + case BOBLSERVER: return "boblightServer"; + case COLOR: return "color"; + case DEVICE: return "device"; + case EFFECTS: return "effects"; + case NETFORWARD: return "forwarder"; + case SYSTEMCAPTURE: return "framegrabber"; + case GENERAL: return "general"; + case V4L2: return "grabberV4L2"; + case JSONSERVER: return "jsonServer"; + case LEDCONFIG: return "ledConfig"; + case LEDS: return "leds"; + case LOGGER: return "logger"; + case PROTOSERVER: return "protoServer"; + case SMOOTHING: return "smoothing"; + case UDPLISTENER: return "udpListener"; + case WEBSERVER: return "webConfig"; + case INSTCAPTURE: return "instCapture"; + case NETWORK: return "network"; + default: return "invalid"; + } +} + +/// +/// @brief Convert string to settings::type representation +/// @param type The string to convert +/// @return The settings type from enum +/// +inline type stringToType(const QString& type) +{ + if (type == "backgroundEffect") return BGEFFECT; + else if (type == "foregroundEffect") return FGEFFECT; + else if (type == "blackborderdetector") return BLACKBORDER; + else if (type == "boblightServer") return BOBLSERVER; + else if (type == "color") return COLOR; + else if (type == "device") return DEVICE; + else if (type == "effects") return EFFECTS; + else if (type == "forwarder") return NETFORWARD; + else if (type == "framegrabber") return SYSTEMCAPTURE; + else if (type == "general") return GENERAL; + else if (type == "grabberV4L2") return V4L2; + else if (type == "jsonServer") return JSONSERVER; + else if (type == "ledConfig") return LEDCONFIG; + else if (type == "leds") return LEDS; + else if (type == "logger") return LOGGER; + else if (type == "protoServer") return PROTOSERVER; + else if (type == "smoothing") return SMOOTHING; + else if (type == "udpListener") return UDPLISTENER; + else if (type == "webConfig") return WEBSERVER; + else if (type == "instCapture") return INSTCAPTURE; + else if (type == "network") return NETWORK; + else return INVALID; +} +}; diff --git a/include/webconfig/WebConfig.h b/include/webconfig/WebConfig.h deleted file mode 100644 index c9b58bfe..00000000 --- a/include/webconfig/WebConfig.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WEBCONFIG_H -#define WEBCONFIG_H - -#include -#include -#include - -class StaticFileServing; - -class WebConfig : public QObject { - Q_OBJECT - -public: - WebConfig (QObject * parent = NULL); - - virtual ~WebConfig (void); - - void start(); - void stop(); - - quint16 getPort() { return _port; }; - -private: - Hyperion* _hyperion; - QString _baseUrl; - quint16 _port; - StaticFileServing* _server; - - const QString WEBCONFIG_DEFAULT_PATH = ":/webconfig"; - const quint16 WEBCONFIG_DEFAULT_PORT = 8099; -}; - -#endif // WEBCONFIG_H - diff --git a/include/webserver/WebServer.h b/include/webserver/WebServer.h new file mode 100644 index 00000000..7d0aaf39 --- /dev/null +++ b/include/webserver/WebServer.h @@ -0,0 +1,55 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include +#include +#include + +// hyperion / utils +#include +#include + +// settings +#include + +class StaticFileServing; +class QtHttpServer; + +class WebServer : public QObject { + Q_OBJECT + +public: + WebServer (const QJsonDocument& config, QObject * parent = 0); + + virtual ~WebServer (void); + + void start(); + void stop(); + + quint16 getPort() { return _port; }; + +public slots: + void onServerStopped (void); + void onServerStarted (quint16 port); + void onServerError (QString msg); + + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + +private: + Logger* _log; + Hyperion* _hyperion; + QString _baseUrl; + quint16 _port; + StaticFileServing* _staticFileServing; + QtHttpServer* _server; + + const QString WEBSERVER_DEFAULT_PATH = ":/webconfig"; + const quint16 WEBSERVER_DEFAULT_PORT = 8090; +}; + +#endif // WEBSERVER_H diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index c2f15b5a..d2d85b23 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -15,4 +15,6 @@ add_subdirectory(leddevice) add_subdirectory(utils) add_subdirectory(effectengine) add_subdirectory(grabber) -add_subdirectory(webconfig) +add_subdirectory(webserver) +add_subdirectory(api) +add_subdirectory(python) diff --git a/libsrc/utils/JSONRPC_schema/schema-adjustment.json b/libsrc/api/JSONRPC_schema/schema-adjustment.json similarity index 92% rename from libsrc/utils/JSONRPC_schema/schema-adjustment.json rename to libsrc/api/JSONRPC_schema/schema-adjustment.json index a16796b1..c095c09c 100644 --- a/libsrc/utils/JSONRPC_schema/schema-adjustment.json +++ b/libsrc/api/JSONRPC_schema/schema-adjustment.json @@ -84,17 +84,6 @@ "minItems": 3, "maxItems": 3 }, - "black": { - "type": "array", - "required": false, - "items" : { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "minItems": 3, - "maxItems": 3 - }, "white": { "type": "array", "required": false, diff --git a/libsrc/utils/JSONRPC_schema/schema-clear.json b/libsrc/api/JSONRPC_schema/schema-clear.json similarity index 94% rename from libsrc/utils/JSONRPC_schema/schema-clear.json rename to libsrc/api/JSONRPC_schema/schema-clear.json index 9d1cb9e5..c63a77f7 100644 --- a/libsrc/utils/JSONRPC_schema/schema-clear.json +++ b/libsrc/api/JSONRPC_schema/schema-clear.json @@ -12,7 +12,7 @@ }, "priority": { "type": "integer", - "minimum" : 1, + "minimum" : -1, "maximum" : 253, "required": true } diff --git a/libsrc/utils/JSONRPC_schema/schema-color.json b/libsrc/api/JSONRPC_schema/schema-color.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-color.json rename to libsrc/api/JSONRPC_schema/schema-color.json diff --git a/libsrc/utils/JSONRPC_schema/schema-componentstate.json b/libsrc/api/JSONRPC_schema/schema-componentstate.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-componentstate.json rename to libsrc/api/JSONRPC_schema/schema-componentstate.json diff --git a/libsrc/utils/JSONRPC_schema/schema-config.json b/libsrc/api/JSONRPC_schema/schema-config.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-config.json rename to libsrc/api/JSONRPC_schema/schema-config.json diff --git a/libsrc/utils/JSONRPC_schema/schema-create-effect.json b/libsrc/api/JSONRPC_schema/schema-create-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-create-effect.json rename to libsrc/api/JSONRPC_schema/schema-create-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-delete-effect.json b/libsrc/api/JSONRPC_schema/schema-delete-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-delete-effect.json rename to libsrc/api/JSONRPC_schema/schema-delete-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-effect.json b/libsrc/api/JSONRPC_schema/schema-effect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-effect.json rename to libsrc/api/JSONRPC_schema/schema-effect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-image.json b/libsrc/api/JSONRPC_schema/schema-image.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-image.json rename to libsrc/api/JSONRPC_schema/schema-image.json diff --git a/libsrc/utils/JSONRPC_schema/schema-ledcolors.json b/libsrc/api/JSONRPC_schema/schema-ledcolors.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-ledcolors.json rename to libsrc/api/JSONRPC_schema/schema-ledcolors.json diff --git a/libsrc/utils/JSONRPC_schema/schema-logging.json b/libsrc/api/JSONRPC_schema/schema-logging.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-logging.json rename to libsrc/api/JSONRPC_schema/schema-logging.json diff --git a/libsrc/utils/JSONRPC_schema/schema-processing.json b/libsrc/api/JSONRPC_schema/schema-processing.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-processing.json rename to libsrc/api/JSONRPC_schema/schema-processing.json diff --git a/libsrc/utils/JSONRPC_schema/schema-serverinfo.json b/libsrc/api/JSONRPC_schema/schema-serverinfo.json similarity index 83% rename from libsrc/utils/JSONRPC_schema/schema-serverinfo.json rename to libsrc/api/JSONRPC_schema/schema-serverinfo.json index 869d9617..990eec04 100644 --- a/libsrc/utils/JSONRPC_schema/schema-serverinfo.json +++ b/libsrc/api/JSONRPC_schema/schema-serverinfo.json @@ -7,6 +7,9 @@ "required" : true, "enum" : ["serverinfo"] }, + "subscribe" : { + "type" : "array" + }, "tan" : { "type" : "integer" } diff --git a/libsrc/utils/JSONRPC_schema/schema-sourceselect.json b/libsrc/api/JSONRPC_schema/schema-sourceselect.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-sourceselect.json rename to libsrc/api/JSONRPC_schema/schema-sourceselect.json diff --git a/libsrc/utils/JSONRPC_schema/schema-sysinfo.json b/libsrc/api/JSONRPC_schema/schema-sysinfo.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-sysinfo.json rename to libsrc/api/JSONRPC_schema/schema-sysinfo.json diff --git a/libsrc/utils/JSONRPC_schema/schema-videomode.json b/libsrc/api/JSONRPC_schema/schema-videomode.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema-videomode.json rename to libsrc/api/JSONRPC_schema/schema-videomode.json diff --git a/libsrc/utils/JSONRPC_schema/schema.json b/libsrc/api/JSONRPC_schema/schema.json similarity index 100% rename from libsrc/utils/JSONRPC_schema/schema.json rename to libsrc/api/JSONRPC_schema/schema.json diff --git a/libsrc/utils/JSONRPC_schemas.qrc b/libsrc/api/JSONRPC_schemas.qrc similarity index 94% rename from libsrc/utils/JSONRPC_schemas.qrc rename to libsrc/api/JSONRPC_schemas.qrc index 1c708443..925566a1 100644 --- a/libsrc/utils/JSONRPC_schemas.qrc +++ b/libsrc/api/JSONRPC_schemas.qrc @@ -6,7 +6,6 @@ JSONRPC_schema/schema-serverinfo.json JSONRPC_schema/schema-sysinfo.json JSONRPC_schema/schema-clear.json - JSONRPC_schema/schema-clearall.json JSONRPC_schema/schema-adjustment.json JSONRPC_schema/schema-effect.json JSONRPC_schema/schema-create-effect.json diff --git a/libsrc/blackborder/BlackBorderDetector.cpp b/libsrc/blackborder/BlackBorderDetector.cpp index 8d789e31..e1a5249b 100644 --- a/libsrc/blackborder/BlackBorderDetector.cpp +++ b/libsrc/blackborder/BlackBorderDetector.cpp @@ -23,7 +23,7 @@ uint8_t BlackBorderDetector::calculateThreshold(double threshold) uint8_t blackborderThreshold = uint8_t(rgbThreshold); - Debug(Logger::getInstance("BLACKBORDER"), "threshold set to %f (%d)", threshold , int(blackborderThreshold)); + //Debug(Logger::getInstance("BLACKBORDER"), "threshold set to %f (%d)", threshold , int(blackborderThreshold)); return blackborderThreshold; } diff --git a/libsrc/blackborder/BlackBorderProcessor.cpp b/libsrc/blackborder/BlackBorderProcessor.cpp index c1c12fef..608c970a 100644 --- a/libsrc/blackborder/BlackBorderProcessor.cpp +++ b/libsrc/blackborder/BlackBorderProcessor.cpp @@ -1,32 +1,105 @@ #include -#include +#include // Blackborder includes #include - using namespace hyperion; -BlackBorderProcessor::BlackBorderProcessor(const QJsonObject &blackborderConfig) - : _enabled(blackborderConfig["enable"].toBool(true)) - , _unknownSwitchCnt(blackborderConfig["unknownFrameCnt"].toInt(600)) - , _borderSwitchCnt(blackborderConfig["borderFrameCnt"].toInt(50)) - , _maxInconsistentCnt(blackborderConfig["maxInconsistentCnt"].toInt(10)) - , _blurRemoveCnt(blackborderConfig["blurRemoveCnt"].toInt(1)) - , _detectionMode(blackborderConfig["mode"].toString("default")) - , _detector(blackborderConfig["threshold"].toDouble(5.0)/100) +BlackBorderProcessor::BlackBorderProcessor(Hyperion* hyperion, QObject* parent) + : QObject(parent) + , _hyperion(hyperion) + , _enabled(false) + , _unknownSwitchCnt(600) + , _borderSwitchCnt(50) + , _maxInconsistentCnt(10) + , _blurRemoveCnt(1) + , _detectionMode("default") + , _detector(nullptr) , _currentBorder({true, -1, -1}) , _previousDetectedBorder({true, -1, -1}) , _consistentCnt(0) , _inconsistentCnt(10) + , _oldThreshold(-0.1) + , _hardDisabled(false) + , _userEnabled(false) { - if (_enabled) + // init + handleSettingsUpdate(settings::BLACKBORDER, _hyperion->getSetting(settings::BLACKBORDER)); + + // listen for settings updates + connect(_hyperion, &Hyperion::settingsChanged, this, &BlackBorderProcessor::handleSettingsUpdate); + + // listen for component state changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &BlackBorderProcessor::componentStateChanged); +} + +BlackBorderProcessor::~BlackBorderProcessor() +{ + delete _detector; +} + +void BlackBorderProcessor::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::BLACKBORDER) { - Debug(Logger::getInstance("BLACKBORDER"), "mode: %s", QSTRING_CSTR(_detectionMode)); + const QJsonObject& obj = config.object(); + _unknownSwitchCnt = obj["unknownFrameCnt"].toInt(600); + _borderSwitchCnt = obj["borderFrameCnt"].toInt(50); + _maxInconsistentCnt = obj["maxInconsistentCnt"].toInt(10); + _blurRemoveCnt = obj["blurRemoveCnt"].toInt(1); + _detectionMode = obj["mode"].toString("default"); + + if(_oldThreshold != obj["threshold"].toDouble(5.0/100)) + { + _oldThreshold = obj["threshold"].toDouble(5.0/100); + if(_detector != nullptr) delete _detector; + _detector = new BlackBorderDetector(obj["threshold"].toDouble(5.0/100)); + } + + Debug(Logger::getInstance("BLACKBORDER"), "Set mode to: %s", QSTRING_CSTR(_detectionMode)); + + // eval the comp state + componentStateChanged(hyperion::COMP_BLACKBORDER, obj["enable"].toBool(true)); } } +void BlackBorderProcessor::componentStateChanged(const hyperion::Components component, bool enable) +{ + if(component == hyperion::COMP_BLACKBORDER) + { + _userEnabled = enable; + if(enable) + { + // eg effects and probably other components don't want a BB, mimik a wrong comp state to the comp register + if(!_hardDisabled) + _enabled = enable; + } + else + { + _enabled = enable; + } + + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BLACKBORDER, enable); + } +} + +void BlackBorderProcessor::setHardDisable(const bool& disable) { + + if (disable) + { + _enabled = false; + } + else + { + // the user has the last word to enable + if(_userEnabled) + _enabled = true; + } + _hardDisabled = disable; +}; + BlackBorder BlackBorderProcessor::getCurrentBorder() const { return _currentBorder; diff --git a/libsrc/blackborder/CMakeLists.txt b/libsrc/blackborder/CMakeLists.txt index 023ebb60..e93c5e47 100644 --- a/libsrc/blackborder/CMakeLists.txt +++ b/libsrc/blackborder/CMakeLists.txt @@ -7,4 +7,7 @@ FILE ( GLOB Blackborder_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_D add_library(blackborder ${Blackborder_SOURCES} ) -target_link_libraries(blackborder hyperion-utils ) +target_link_libraries(blackborder + hyperion-utils + hyperion +) diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index 17dc3491..9997517b 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -15,8 +15,8 @@ #include // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" +//#include "hyperion/ImageProcessorFactory.h" +//#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" #include "HyperionConfig.h" @@ -27,7 +27,7 @@ BoblightClientConnection::BoblightClientConnection(QTcpSocket *socket, const int : QObject() , _locale(QLocale::C) , _socket(socket) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) + //, _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _priority(priority) @@ -167,7 +167,7 @@ void BoblightClientConnection::handleMessage(const QString & message) // send current color values to hyperion if this is the last led assuming leds values are send in order of id if ((ledIndex == _ledColors.size() -1) && _priority < 255) { - _hyperion->setColors(_priority, _ledColors, -1, true, hyperion::COMP_BOBLIGHTSERVER, _clientAddress); + _hyperion->setInput(_priority, _ledColors); } return; @@ -205,7 +205,7 @@ void BoblightClientConnection::handleMessage(const QString & message) // send current color values to hyperion if (_priority < 255) { - _hyperion->setColors(_priority, _ledColors, -1, true, hyperion::COMP_BOBLIGHTSERVER, _clientAddress); + _hyperion->setInput(_priority, _ledColors); } return; } @@ -227,11 +227,11 @@ void BoblightClientConnection::sendLightMessage() int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount()); sendMessage(QByteArray(buffer, n)); - double h0, h1, v0, v1; + //double h0, h1, v0, v1; for (unsigned i = 0; i < _hyperion->getLedCount(); ++i) { - _imageProcessor->getScanParameters(i, h0, h1, v0, v1); - n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); - sendMessage(QByteArray(buffer, n)); + //_imageProcessor->getScanParameters(i, h0, h1, v0, v1); + //n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); + //sendMessage(QByteArray(buffer, n)); } } diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h index d4baffa6..76eb1280 100644 --- a/libsrc/boblightserver/BoblightClientConnection.h +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -9,8 +9,6 @@ #include #include -class ImageProcessor; - /// /// The Connection object created by \a BoblightServer when a new connection is establshed /// @@ -76,9 +74,6 @@ private: /// The TCP-Socket that is connected tot the boblight-client QTcpSocket * _socket; - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - /// Link to Hyperion for writing led-values to a priority channel Hyperion * _hyperion; @@ -90,7 +85,7 @@ private: /// The latest led color data std::vector _ledColors; - + /// logger instance Logger * _log; diff --git a/libsrc/boblightserver/BoblightServer.cpp b/libsrc/boblightserver/BoblightServer.cpp index 68a45f0c..bba2c493 100644 --- a/libsrc/boblightserver/BoblightServer.cpp +++ b/libsrc/boblightserver/BoblightServer.cpp @@ -5,20 +5,31 @@ #include #include "BoblightClientConnection.h" +// hyperion includes +#include +// qt incl +#include + using namespace hyperion; -BoblightServer::BoblightServer(const int priority, uint16_t port) +BoblightServer::BoblightServer(const QJsonDocument& config) : QObject() , _hyperion(Hyperion::getInstance()) - , _server() + , _server(new QTcpServer(this)) , _openConnections() - , _priority(priority) + , _priority(0) , _log(Logger::getInstance("BOBLIGHT")) - , _isActive(false) - , _port(port) + , _port(0) { - // Set trigger for incoming connections - connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + Debug(_log, "Instance created"); + + // listen for component change + connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); + // listen new connection signal from server + connect(_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + + // init + handleSettingsUpdate(settings::BOBLSERVER, config); } BoblightServer::~BoblightServer() @@ -28,63 +39,63 @@ BoblightServer::~BoblightServer() void BoblightServer::start() { - if ( active() ) + if ( _server->isListening() ) return; - - if (!_server.listen(QHostAddress::Any, _port)) + + if (!_server->listen(QHostAddress::Any, _port)) { - throw std::runtime_error("BOBLIGHT ERROR: server could not bind to port"); + Error(_log, "Could not bind to port '%d', please use an available port", _port); + return; } - Info(_log, "Boblight server started on port %d", _port); + Info(_log, "Started on port %d", _port); - _isActive = true; - emit statusChanged(_isActive); - - _hyperion->registerPriority("Boblight", _priority); + _hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening()); } void BoblightServer::stop() { - if ( ! active() ) + if ( ! _server->isListening() ) return; - + foreach (BoblightClientConnection * connection, _openConnections) { delete connection; } - _server.close(); - _isActive = false; - emit statusChanged(_isActive); + _server->close(); - _hyperion->unRegisterPriority("Boblight"); + Info(_log, "Stopped"); + _hyperion->getComponentRegister().componentStateChanged(COMP_BOBLIGHTSERVER, _server->isListening()); +} +bool BoblightServer::active() +{ + return _server->isListening(); } void BoblightServer::componentStateChanged(const hyperion::Components component, bool enable) { if (component == COMP_BOBLIGHTSERVER) { - if (_isActive != enable) + if (_server->isListening() != enable) { if (enable) start(); else stop(); - Info(_log, "change state to %s", (_isActive ? "enabled" : "disabled") ); } - _hyperion->getComponentRegister().componentStateChanged(component, _isActive); } } uint16_t BoblightServer::getPort() const { - return _server.serverPort(); + return _server->serverPort(); } void BoblightServer::newConnection() { - QTcpSocket * socket = _server.nextPendingConnection(); + QTcpSocket * socket = _server->nextPendingConnection(); if (socket != nullptr) { Info(_log, "new connection"); + _hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(socket->peerAddress().toString())); BoblightClientConnection * connection = new BoblightClientConnection(socket, _priority); _openConnections.insert(connection); @@ -101,3 +112,16 @@ void BoblightServer::closedConnection(BoblightClientConnection *connection) // schedule to delete the connection object connection->deleteLater(); } + +void BoblightServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::BOBLSERVER) + { + QJsonObject obj = config.object(); + _port = obj["port"].toInt(); + _priority = obj["priority"].toInt(); + stop(); + if(obj["enable"].toBool()) + start(); + } +} diff --git a/libsrc/bonjour/bonjourbrowserwrapper.cpp b/libsrc/bonjour/bonjourbrowserwrapper.cpp new file mode 100644 index 00000000..d3d94bbc --- /dev/null +++ b/libsrc/bonjour/bonjourbrowserwrapper.cpp @@ -0,0 +1,81 @@ +#include + +//qt incl +#include + +// bonjour +#include +#include + +BonjourBrowserWrapper* BonjourBrowserWrapper::instance = nullptr; + +BonjourBrowserWrapper::BonjourBrowserWrapper(QObject * parent) + : QObject(parent) + , _bonjourResolver(new BonjourServiceResolver(this)) + , _timerBonjourResolver( new QTimer(this)) +{ + BonjourBrowserWrapper::instance = this; + connect(_bonjourResolver, &BonjourServiceResolver::bonjourRecordResolved, this, &BonjourBrowserWrapper::bonjourRecordResolved); + + connect(_timerBonjourResolver, &QTimer::timeout, this, &BonjourBrowserWrapper::bonjourResolve); + _timerBonjourResolver->setInterval(1000); + _timerBonjourResolver->start(); + + // browse for _hyperiond-http._tcp + browseForServiceType(QLatin1String("_hyperiond-http._tcp")); +} + +bool BonjourBrowserWrapper::browseForServiceType(const QString &serviceType) +{ + if(!_browsedServices.contains(serviceType)) + { + BonjourServiceBrowser* newBrowser = new BonjourServiceBrowser(this); + connect(newBrowser, &BonjourServiceBrowser::currentBonjourRecordsChanged, this, &BonjourBrowserWrapper::currentBonjourRecordsChanged); + newBrowser->browseForServiceType(serviceType); + _browsedServices.insert(serviceType, newBrowser); + return true; + } + return false; +} + +void BonjourBrowserWrapper::currentBonjourRecordsChanged(const QList &list) +{ + _hyperionSessions.clear(); + for ( auto rec : list ) + { + _hyperionSessions.insert(rec.serviceName, rec); + } +} + +void BonjourBrowserWrapper::bonjourRecordResolved(const QHostInfo &hostInfo, int port) +{ + if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve)) + { + QString host = hostInfo.hostName(); + QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain; + if (host.endsWith("."+domain)) + { + host.remove(host.length()-domain.length()-1,domain.length()+1); + } + _hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host; + _hyperionSessions[_bonjourCurrentServiceToResolve].port = port; + _hyperionSessions[_bonjourCurrentServiceToResolve].address = hostInfo.addresses().isEmpty() ? "" : hostInfo.addresses().first().toString(); + //Debug(_log, "found hyperion session: %s:%d",QSTRING_CSTR(hostInfo.hostName()), port); + + //emit change + emit browserChange(_hyperionSessions); + } +} + +void BonjourBrowserWrapper::bonjourResolve() +{ + for(auto key : _hyperionSessions.keys()) + { + if (_hyperionSessions[key].port < 0) + { + _bonjourCurrentServiceToResolve = key; + _bonjourResolver->resolveBonjourRecord(_hyperionSessions[key]); + break; + } + } +} diff --git a/libsrc/bonjour/bonjourserviceregister.cpp b/libsrc/bonjour/bonjourserviceregister.cpp index 96c5dc7f..d3124b6f 100755 --- a/libsrc/bonjour/bonjourserviceregister.cpp +++ b/libsrc/bonjour/bonjourserviceregister.cpp @@ -30,8 +30,12 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include +#include +#include + BonjourServiceRegister::BonjourServiceRegister(QObject *parent) : QObject(parent), dnssref(0), bonjourSocket(0) @@ -48,6 +52,19 @@ BonjourServiceRegister::~BonjourServiceRegister() } } +void BonjourServiceRegister::registerService(const QString& service, const int& port) +{ + // zeroconf $configname@$hostname:port + QString prettyName = Hyperion::getInstance()->getQJsonConfig()["general"].toObject()["name"].toString(); + registerService( + BonjourRecord(prettyName+"@"+QHostInfo::localHostName()+ ":" + QString::number(port), + service, + QString() + ), + port + ); +} + void BonjourServiceRegister::registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt) { if (dnssref) @@ -61,22 +78,25 @@ void BonjourServiceRegister::registerService(const BonjourRecord &record, quint1 bigEndianPort = 0 | ((servicePort & 0x00ff) << 8) | ((servicePort & 0xff00) >> 8); } #endif - + // base txtRec + std::vector > txtBase = {{"id",Hyperion::getInstance()->getId().toStdString()},{"version",HYPERION_VERSION}}; // create txt record TXTRecordRef txtRec; TXTRecordCreate(&txtRec,0,NULL); - // add txt records - if(!txt.empty()) + if(!txt.empty()) { - for(std::vector >::const_iterator it = txt.begin(); it != txt.end(); ++it) - { - //Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str()); - uint8_t txtLen = (uint8_t)strlen(it->second.c_str()); - TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str()); - } + txtBase.insert(txtBase.end(), txt.begin(), txt.end()); + } + // add txt records + for(std::vector >::const_iterator it = txtBase.begin(); it != txtBase.end(); ++it) + { + //Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str()); + uint8_t txtLen = (uint8_t)strlen(it->second.c_str()); + TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str()); } + DNSServiceErrorType err = DNSServiceRegister(&dnssref, 0, 0, record.serviceName.toUtf8().constData(), record.registeredType.toUtf8().constData(), (record.replyDomain.isEmpty() ? 0 : record.replyDomain.toUtf8().constData()), diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt index c016b4cc..aac420f7 100644 --- a/libsrc/effectengine/CMakeLists.txt +++ b/libsrc/effectengine/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(effectengine target_link_libraries(effectengine hyperion + python Qt5::Core Qt5::Gui ${PYTHON_LIBRARIES} diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index fda2f934..7e1c52c3 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -15,87 +15,34 @@ #include // effect engin eincludes -#include "Effect.h" +#include +#include #include #include -// Python method table -PyMethodDef Effect::effectMethods[] = { - {"setColor" , Effect::wrapSetColor , METH_VARARGS, "Set a new color for the leds."}, - {"setImage" , Effect::wrapSetImage , METH_VARARGS, "Set a new image to process and determine new led colors."}, - {"getImage" , Effect::wrapGetImage , METH_VARARGS, "get image data from file."}, - {"abort" , Effect::wrapAbort , METH_NOARGS, "Check if the effect should abort execution."}, - {"imageShow" , Effect::wrapImageShow , METH_VARARGS, "set current effect image to hyperion core."}, - {"imageLinearGradient" , Effect::wrapImageLinearGradient , METH_VARARGS, ""}, - {"imageConicalGradient" , Effect::wrapImageConicalGradient , METH_VARARGS, ""}, - {"imageRadialGradient" , Effect::wrapImageRadialGradient , METH_VARARGS, ""}, - {"imageSolidFill" , Effect::wrapImageSolidFill , METH_VARARGS, ""}, - {"imageDrawLine" , Effect::wrapImageDrawLine , METH_VARARGS, ""}, - {"imageDrawPoint" , Effect::wrapImageDrawPoint , METH_VARARGS, ""}, - {"imageDrawRect" , Effect::wrapImageDrawRect , METH_VARARGS, ""}, - {"imageDrawPolygon" , Effect::wrapImageDrawPolygon , METH_VARARGS, ""}, - {"imageDrawPie" , Effect::wrapImageDrawPie , METH_VARARGS, ""}, - {"imageSetPixel" , Effect::wrapImageSetPixel , METH_VARARGS, "set pixel color of image"}, - {"imageGetPixel" , Effect::wrapImageGetPixel , METH_VARARGS, "get pixel color of image"}, - {"imageSave" , Effect::wrapImageSave , METH_NOARGS, "adds a new background image"}, - {"imageMinSize" , Effect::wrapImageMinSize , METH_VARARGS, "sets minimal dimension of background image"}, - {"imageWidth" , Effect::wrapImageWidth , METH_NOARGS, "gets image width"}, - {"imageHeight" , Effect::wrapImageHeight , METH_NOARGS, "gets image height"}, - {"imageCRotate" , Effect::wrapImageCRotate , METH_VARARGS, "rotate the coordinate system by given angle"}, - {"imageCOffset" , Effect::wrapImageCOffset , METH_VARARGS, "Add offset to the coordinate system"}, - {"imageCShear" , Effect::wrapImageCShear , METH_VARARGS, "Shear of coordinate system by the given horizontal/vertical axis"}, - {"imageResetT" , Effect::wrapImageResetT , METH_NOARGS, "Resets all coords modifications (rotate,offset,shear)"}, - {NULL, NULL, 0, NULL} -}; +// python utils/ global mainthread +#include +//impl +PyThreadState* mainThreadState; - -// create the hyperion module -struct PyModuleDef Effect::moduleDef = { - PyModuleDef_HEAD_INIT, - "hyperion", /* m_name */ - "Hyperion module", /* m_doc */ - -1, /* m_size */ - Effect::effectMethods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -PyObject* Effect::PyInit_hyperion() -{ - return PyModule_Create(&moduleDef); -} - -void Effect::registerHyperionExtensionModule() -{ - PyImport_AppendInittab("hyperion", &PyInit_hyperion); -} - -Effect::Effect(PyThreadState* mainThreadState, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args, const QString & origin, unsigned smoothCfg) +Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args) : QThread() - , _mainThreadState(mainThreadState) + , _hyperion(hyperion) , _priority(priority) , _timeout(timeout) , _script(script) , _name(name) - , _smoothCfg(smoothCfg) , _args(args) , _endTime(-1) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _colors() - , _origin(origin) - , _imageSize(Hyperion::getInstance()->getLedGridSize()) + , _imageSize(hyperion->getLedGridSize()) , _image(_imageSize,QImage::Format_ARGB32_Premultiplied) { - _colors.resize(_imageProcessor->getLedCount()); + _colors.resize(_hyperion->getLedCount()); _colors.fill(ColorRgb::BLACK); _log = Logger::getInstance("EFFECTENGINE"); - // disable the black border detector for effects - _imageProcessor->enableBlackBorderDetector(false); - // init effect image for image based effects, size is based on led layout _image.fill(Qt::black); _painter = new QPainter(&_image); @@ -112,7 +59,7 @@ Effect::~Effect() void Effect::run() { // get global lock - PyEval_RestoreThread(_mainThreadState); + PyEval_RestoreThread(mainThreadState); // Initialize a new thread state PyThreadState* tstate = Py_NewInterpreter(); @@ -131,13 +78,13 @@ void Effect::run() PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr)); // add ledCount variable to the interpreter - PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount())); + PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _hyperion->getLedCount())); // add minimumWriteTime variable to the interpreter PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", Hyperion::getInstance()->getLatchTime())); // add a args variable to the interpreter - PyObject_SetAttrString(module, "args", json2python(_args)); + PyObject_SetAttrString(module, "args", EffectModule::json2python(_args)); // decref the module Py_XDECREF(module); @@ -286,1024 +233,3 @@ void Effect::run() Py_EndInterpreter(tstate); PyEval_ReleaseLock(); } - -PyObject *Effect::json2python(const QJsonValue &jsonData) const -{ - switch (jsonData.type()) - { - case QJsonValue::Null: - return Py_BuildValue(""); - case QJsonValue::Undefined: - return Py_BuildValue(""); - case QJsonValue::Double: - { - if (std::rint(jsonData.toDouble()) != jsonData.toDouble()) - { - return Py_BuildValue("d", jsonData.toDouble()); - } - return Py_BuildValue("i", jsonData.toInt()); - } - case QJsonValue::Bool: - return Py_BuildValue("i", jsonData.toBool() ? 1 : 0); - case QJsonValue::String: - return Py_BuildValue("s", jsonData.toString().toUtf8().constData()); - case QJsonValue::Object: - { - PyObject * dict= PyDict_New(); - QJsonObject objectData = jsonData.toObject(); - for (QJsonObject::iterator i = objectData.begin(); i != objectData.end(); ++i) - { - PyObject * obj = json2python(*i); - PyDict_SetItemString(dict, i.key().toStdString().c_str(), obj); - Py_XDECREF(obj); - } - return dict; - } - case QJsonValue::Array: - { - QJsonArray arrayData = jsonData.toArray(); - PyObject * list = PyList_New(arrayData.size()); - int index = 0; - for (QJsonArray::iterator i = arrayData.begin(); i != arrayData.end(); ++i, ++index) - { - PyObject * obj = json2python(*i); - Py_INCREF(obj); - PyList_SetItem(list, index, obj); - Py_XDECREF(obj); - } - return list; - } - } - - assert(false); - return nullptr; -} - -PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) -{ - // get the effect - Effect * effect = getEffect(); - - // check if we have aborted already - if (effect->isInterruptionRequested()) - { - return Py_BuildValue(""); - } - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - // check the number of arguments - int argCount = PyTuple_Size(args); - if (argCount == 3) - { - // three seperate arguments for red, green, and blue - ColorRgb color; - if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) - { - effect->_colors.fill(color); - effect->setColors(effect->_priority, effect->_colors.toStdVector(), timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - return nullptr; - } - else if (argCount == 1) - { - // bytearray of values - PyObject * bytearray = nullptr; - if (PyArg_ParseTuple(args, "O", &bytearray)) - { - if (PyByteArray_Check(bytearray)) - { - size_t length = PyByteArray_Size(bytearray); - if (length == 3 * effect->_imageProcessor->getLedCount()) - { - char * data = PyByteArray_AS_STRING(bytearray); - memcpy(effect->_colors.data(), data, length); - effect->setColors(effect->_priority, effect->_colors.toStdVector(), timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument is not a bytearray"); - return nullptr; - } - } - else - { - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Function expect 1 or 3 arguments"); - return nullptr; - } - - // error - PyErr_SetString(PyExc_RuntimeError, "Unknown error"); - return nullptr; -} - -PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) -{ - // get the effect - Effect * effect = getEffect(); - - // check if we have aborted already - if (effect->isInterruptionRequested()) - { - return Py_BuildValue(""); - } - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - // bytearray of values - int width, height; - PyObject * bytearray = nullptr; - if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray)) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length == 3 * width * height) - { - Image image(width, height); - char * data = PyByteArray_AS_STRING(bytearray); - memcpy(image.memptr(), data, length); - std::vector v = effect->_colors.toStdVector(); - effect->_imageProcessor->process(image, v); - effect->setColors(effect->_priority, v, timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*width*height"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 3 is not a bytearray"); - return nullptr; - } - } - else - { - return nullptr; - } - - // error - PyErr_SetString(PyExc_RuntimeError, "Unknown error"); - return nullptr; -} - -PyObject* Effect::wrapGetImage(PyObject *self, PyObject *args) -{ - Q_INIT_RESOURCE(EffectEngine); - - char *source; - if(!PyArg_ParseTuple(args, "s", &source)) - { - PyErr_SetString(PyExc_TypeError, "String required"); - return NULL; - } - - QString file = QString::fromUtf8(source); - - if (file.mid(0, 1) == ":") - file = ":/effects/"+file.mid(1); - - QImageReader reader(file); - - if (reader.canRead()) - { - PyObject* result = PyList_New(reader.imageCount()); - - for (int i = 0; i < reader.imageCount(); ++i) - { - reader.jumpToImage(i); - if (reader.canRead()) - { - QImage qimage = reader.read(); - - int width = qimage.width(); - int height = qimage.height(); - - QByteArray binaryImage; - for (int i = 0; i(qimage.scanLine(i)); - for (int j = 0; j< width; ++j) - { - binaryImage.append((char) qRed(scanline[j])); - binaryImage.append((char) qGreen(scanline[j])); - binaryImage.append((char) qBlue(scanline[j])); - } - } - PyList_SET_ITEM(result, i, Py_BuildValue("{s:i,s:i,s:O}", "imageWidth", width, "imageHeight", height, "imageData", PyByteArray_FromStringAndSize(binaryImage.constData(),binaryImage.size()))); - } - else - { - PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); - return NULL; - } - } - return result; - } - else - { - PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); - return NULL; - } -} - -PyObject* Effect::wrapAbort(PyObject *self, PyObject *) -{ - Effect * effect = getEffect(); - - // Test if the effect has reached it end time - if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) - { - effect->requestInterruption(); - } - - return Py_BuildValue("i", effect->isInterruptionRequested() ? 1 : 0); -} - - -PyObject* Effect::wrapImageShow(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - // determine the timeout - int timeout = effect->_timeout; - if (timeout > 0) - { - timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); - - // we are done if the time has passed - if (timeout <= 0) - { - return Py_BuildValue(""); - } - } - - int argCount = PyTuple_Size(args); - int imgId = -1; - bool argsOk = (argCount == 0); - if (argCount == 1 && PyArg_ParseTuple(args, "i", &imgId)) - { - argsOk = true; - } - - if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size())) - { - return nullptr; - } - - - QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]); - int width = qimage->width(); - int height = qimage->height(); - - Image image(width, height); - QByteArray binaryImage; - - for (int i = 0; i(qimage->scanLine(i)); - for (int j = 0; j< width; ++j) - { - binaryImage.append((char) qRed(scanline[j])); - binaryImage.append((char) qGreen(scanline[j])); - binaryImage.append((char) qBlue(scanline[j])); - } - } - - memcpy(image.memptr(), binaryImage.data(), binaryImage.size()); - std::vector v = effect->_colors.toStdVector(); - effect->_imageProcessor->process(image, v); - effect->setColors(effect->_priority, v, timeout, false, hyperion::COMP_EFFECT, effect->_origin, effect->_smoothCfg); - - return Py_BuildValue(""); -} - -PyObject* Effect::wrapImageLinearGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int startRX = 0; - int startRY = 0; - int startX = 0; - int startY = 0; - int endX, width = effect->_imageSize.width(); - int endY, height = effect->_imageSize.height(); - int spread = 0; - - bool argsOK = false; - - if ( argCount == 10 && PyArg_ParseTuple(args, "iiiiiiiiOi", &startRX, &startRY, &width, &height, &startX, &startY, &endX, &endY, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiOi", &startX, &startY, &endX, &endY, &bytearray, &spread) ) - { - argsOK = true; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - const int length = PyByteArray_Size(bytearray); - const unsigned arrayItemLength = 5; - if (length % arrayItemLength == 0) - { - QRect myQRect(startRX,startRY,width,height); - QLinearGradient gradient(QPoint(startX,startY), QPoint(endX,endY)); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx(spread)); - effect->_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "No bytearray properly defined"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageConicalGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int centerX, centerY, angle; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, ¢erX, ¢erY, &angle, &bytearray) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", ¢erX, ¢erY, &angle, &bytearray) ) - { - argsOK = true; - } - angle = qMax(qMin(angle,360),0); - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - const int length = PyByteArray_Size(bytearray); - const unsigned arrayItemLength = 5; - if (length % arrayItemLength == 0) - { - QRect myQRect(startX,startY,width,height); - QConicalGradient gradient(QPoint(centerX,centerY), angle ); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 8 is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - - -PyObject* Effect::wrapImageRadialGradient(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - PyObject * bytearray = nullptr; - int centerX, centerY, radius, focalX, focalY, focalRadius, spread; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 12 && PyArg_ParseTuple(args, "iiiiiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &bytearray, &spread) ) - { - argsOK = true; - focalX = centerX; - focalY = centerY; - focalRadius = radius; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiOi", ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) - { - argsOK = true; - } - if ( argCount == 5 && PyArg_ParseTuple(args, "iiiOi", ¢erX, ¢erY, &radius, &bytearray, &spread) ) - { - argsOK = true; - focalX = centerX; - focalY = centerY; - focalRadius = radius; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 4 == 0) - { - - QRect myQRect(startX,startY,width,height); - QRadialGradient gradient(QPoint(centerX,centerY), qMax(radius,0) ); - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx(spread)); - effect->_painter->fillRect(myQRect, gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPolygon(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - PyObject * bytearray = nullptr; - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - - bool argsOK = false; - - if ( argCount == 5 && PyArg_ParseTuple(args, "Oiiii", &bytearray, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "Oiii", &bytearray, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 2 == 0) - { - QVector points; - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idx_painter; - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - painter->setPen(newPen); - painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); - painter->drawPolygon(points); - painter->setPen(oldPen); - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 2"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Argument 1 is not a bytearray"); - return nullptr; - } - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPie(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - PyObject * bytearray = nullptr; - - QString brush; - int argCount = PyTuple_Size(args); - int radius, centerX, centerY; - int startAngle = 0; - int spanAngle = 360; - int r = 0; - int g = 0; - int b = 0; - int a = 255; - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b) ) - { - argsOK = true; - } - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiisO", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &brush, &bytearray) ) - { - argsOK = true; - } - if ( argCount == 5 && PyArg_ParseTuple(args, "iiisO", ¢erX, ¢erY, &radius, &brush, &bytearray) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - startAngle = qMax(qMin(startAngle,360),0); - spanAngle = qMax(qMin(spanAngle,360),-360); - - if( argCount == 7 || argCount == 5 ) - { - a = 0; - if (PyByteArray_Check(bytearray)) - { - int length = PyByteArray_Size(bytearray); - if (length % 5 == 0) - { - - QConicalGradient gradient(QPoint(centerX,centerY), startAngle); - - - char * data = PyByteArray_AS_STRING(bytearray); - - for (int idx=0; idxsetBrush(gradient); - - return Py_BuildValue(""); - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); - return nullptr; - } - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); - return nullptr; - } - } - else - { - painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); - } - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - painter->setPen(newPen); - painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16); - painter->setPen(oldPen); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageSolidFill(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &startX, &startY, &width, &height, &r, &g, &b) ) - { - argsOK = true; - } - if ( argCount == 4 && PyArg_ParseTuple(args, "iiii",&r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 3 && PyArg_ParseTuple(args, "iii",&r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QRect myQRect(startX,startY,width,height); - effect->_painter->fillRect(myQRect, QColor(r,g,b,a)); - return Py_BuildValue(""); - } - return nullptr; -} - - -PyObject* Effect::wrapImageDrawLine(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int thick = 1; - int endX = effect->_imageSize.width(); - int endY = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QRect myQRect(startX, startY, endX, endY); - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawLine(startX, startY, endX, endY); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawPoint(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b, x, y; - int a = 255; - int thick = 1; - - bool argsOK = false; - - if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &x, &y, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiii", &x, &y, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawPoint(x, y); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageDrawRect(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b; - int a = 255; - int startX = 0; - int startY = 0; - int thick = 1; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - bool argsOK = false; - - if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b, &a) ) - { - argsOK = true; - } - if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b) ) - { - argsOK = true; - } - - if (argsOK) - { - QPainter * painter = effect->_painter; - QRect myQRect(startX,startY,width,height); - QPen oldPen = painter->pen(); - QPen newPen(QColor(r,g,b,a)); - newPen.setWidth(thick); - painter->setPen(newPen); - painter->drawRect(startX, startY, width, height); - painter->setPen(oldPen); - - return Py_BuildValue(""); - } - return nullptr; -} - - -PyObject* Effect::wrapImageSetPixel(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int r, g, b, x, y; - - if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) ) - { - effect->_image.setPixel(x,y,qRgb(r,g,b)); - return Py_BuildValue(""); - } - - return nullptr; -} - - -PyObject* Effect::wrapImageGetPixel(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int x, y; - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) ) - { - QRgb rgb = effect->_image.pixel(x,y); - return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb)); - } - return nullptr; -} - -PyObject* Effect::wrapImageSave(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - QImage img(effect->_image.copy()); - effect->_imageStack.append(img); - - return Py_BuildValue("i", effect->_imageStack.size()-1); -} - -PyObject* Effect::wrapImageMinSize(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int w, h; - int width = effect->_imageSize.width(); - int height = effect->_imageSize.height(); - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) ) - { - if (width_painter; - - effect->_image = effect->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - effect->_imageSize = effect->_image.size(); - effect->_painter = new QPainter(&(effect->_image)); - } - return Py_BuildValue("ii", effect->_image.width(), effect->_image.height()); - } - return nullptr; -} - -PyObject* Effect::wrapImageWidth(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - return Py_BuildValue("i", effect->_imageSize.width()); -} - -PyObject* Effect::wrapImageHeight(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - return Py_BuildValue("i", effect->_imageSize.height()); -} - -PyObject* Effect::wrapImageCRotate(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int argCount = PyTuple_Size(args); - int angle; - - if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) ) - { - angle = qMax(qMin(angle,360),0); - effect->_painter->rotate(angle); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageCOffset(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int offsetX = 0; - int offsetY = 0; - int argCount = PyTuple_Size(args); - - if ( argCount == 2 ) - { - PyArg_ParseTuple(args, "ii", &offsetX, &offsetY ); - } - - effect->_painter->translate(QPoint(offsetX,offsetY)); - return Py_BuildValue(""); -} - -PyObject* Effect::wrapImageCShear(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - int sh,sv; - int argCount = PyTuple_Size(args); - - if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv )) - { - effect->_painter->shear(sh,sv); - return Py_BuildValue(""); - } - return nullptr; -} - -PyObject* Effect::wrapImageResetT(PyObject *self, PyObject *args) -{ - Effect * effect = getEffect(); - - effect->_painter->resetTransform(); - return Py_BuildValue(""); -} - -Effect * Effect::getEffect() -{ - // extract the module from the runtime - PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); - - if (!PyModule_Check(module)) - { - // something is wrong - Py_XDECREF(module); - Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); - return nullptr; - } - - // retrieve the capsule with the effect - PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); - Py_XDECREF(module); - - if (!PyCapsule_CheckExact(effectCapsule)) - { - // something is wrong - Py_XDECREF(effectCapsule); - Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); - return nullptr; - } - - // Get the effect from the capsule - Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); - Py_XDECREF(effectCapsule); - return effect; -} diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index bfa35c31..26bed8a7 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -16,7 +16,8 @@ // effect engine includes #include -#include "Effect.h" +#include +#include #include "HyperionConfig.h" EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectConfig) @@ -25,11 +26,11 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectCo , _availableEffects() , _activeEffects() , _log(Logger::getInstance("EFFECTENGINE")) - , _mainThreadState(nullptr) { Q_INIT_RESOURCE(EffectEngine); qRegisterMetaType>("std::vector"); + qRegisterMetaType>("Image"); qRegisterMetaType("hyperion::Components"); // connect the Hyperion channel clear feedback @@ -38,21 +39,10 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectCo // read all effects readEffects(); - - // initialize the python interpreter - Debug(_log, "Initializing Python interpreter"); - Effect::registerHyperionExtensionModule(); - Py_InitializeEx(0); - PyEval_InitThreads(); // Create the GIL - _mainThreadState = PyEval_SaveThread(); } EffectEngine::~EffectEngine() { - // clean up the Python interpreter - Debug(_log, "Cleaning up Python interpreter"); - PyEval_RestoreThread(_mainThreadState); - Py_Finalize(); } const std::list &EffectEngine::getActiveEffects() @@ -73,6 +63,33 @@ const std::list &EffectEngine::getActiveEffects() return _availableActiveEffects; } +void EffectEngine::cacheRunningEffects() +{ + _cachedActiveEffects.clear(); + + for (Effect * effect : _activeEffects) + { + ActiveEffectDefinition activeEffectDefinition; + activeEffectDefinition.script = effect->getScript(); + activeEffectDefinition.name = effect->getName(); + activeEffectDefinition.priority = effect->getPriority(); + activeEffectDefinition.timeout = effect->getTimeout(); + activeEffectDefinition.args = effect->getArgs(); + _cachedActiveEffects.push_back(activeEffectDefinition); + channelCleared(effect->getPriority()); + } +} + +void EffectEngine::startCachedEffects() +{ + for (const auto & def : _cachedActiveEffects) + { + // the smooth cfg AND origin are ignored for this start! + runEffect(def.name, def.args, def.priority, def.timeout, def.script); + } + _cachedActiveEffects.clear(); +} + bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effectConfigFile, EffectDefinition & effectDefinition) { QString fileName = path + QDir::separator() + effectConfigFile; @@ -241,6 +258,8 @@ void EffectEngine::readEffects() } ErrorIf(_availableEffects.size()==0, _log, "no effects found, check your effect directories"); + + emit effectListUpdated(); } int EffectEngine::runEffect(const QString &effectName, int priority, int timeout, const QString &origin) @@ -266,7 +285,7 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, if (effectDefinition == nullptr) { // no such effect - Error(_log, "effect %s not found", QSTRING_CSTR(effectName)); + Error(_log, "Effect %s not found", QSTRING_CSTR(effectName)); return -1; } @@ -281,13 +300,14 @@ int EffectEngine::runEffectScript(const QString &script, const QString &name, co channelCleared(priority); // create the effect - Effect * effect = new Effect(_mainThreadState, priority, timeout, script, name, args, origin, smoothCfg); - connect(effect, SIGNAL(setColors(int,std::vector,int,bool,hyperion::Components,const QString,unsigned)), _hyperion, SLOT(setColors(int,std::vector,int,bool,hyperion::Components,const QString,unsigned)), Qt::QueuedConnection); + Effect * effect = new Effect(_hyperion, priority, timeout, script, name, args); + connect(effect, &Effect::setInput, _hyperion, &Hyperion::setInput, Qt::QueuedConnection); + connect(effect, &Effect::setInputImage, _hyperion, &Hyperion::setInputImage, Qt::QueuedConnection); connect(effect, &QThread::finished, this, &EffectEngine::effectFinished); _activeEffects.push_back(effect); // start the effect - _hyperion->registerPriority(name, priority); + _hyperion->registerInput(priority, hyperion::COMP_EFFECT, origin, name ,smoothCfg); effect->start(); return 0; @@ -299,7 +319,7 @@ void EffectEngine::channelCleared(int priority) { if (effect->getPriority() == priority) { - effect->requestInterruption(); + effect->setInteruptionFlag(); } } } @@ -310,7 +330,7 @@ void EffectEngine::allChannelsCleared() { if (effect->getPriority() != 254) { - effect->requestInterruption(); + effect->setInteruptionFlag(); } } } @@ -318,7 +338,7 @@ void EffectEngine::allChannelsCleared() void EffectEngine::effectFinished() { Effect* effect = qobject_cast(sender()); - if (!effect->isInterruptionRequested()) + if (!effect->hasInteruptionFlag()) { // effect stopped by itself. Clear the channel _hyperion->clear(effect->getPriority()); @@ -336,5 +356,4 @@ void EffectEngine::effectFinished() // cleanup the effect effect->deleteLater(); - _hyperion->unRegisterPriority(effect->getName()); } diff --git a/libsrc/effectengine/EffectModule.cpp b/libsrc/effectengine/EffectModule.cpp new file mode 100644 index 00000000..65166c19 --- /dev/null +++ b/libsrc/effectengine/EffectModule.cpp @@ -0,0 +1,1077 @@ + +#include +#include + +// hyperion +#include +#include + +// qt +#include +#include +#include + +// create the hyperion module +struct PyModuleDef EffectModule::moduleDef = { + PyModuleDef_HEAD_INIT, + "hyperion", /* m_name */ + "Hyperion module", /* m_doc */ + -1, /* m_size */ + EffectModule::effectMethods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyObject* EffectModule::PyInit_hyperion() +{ + return PyModule_Create(&moduleDef); +} + +void EffectModule::registerHyperionExtensionModule() +{ + PyImport_AppendInittab("hyperion", &PyInit_hyperion); +} + +PyObject *EffectModule::json2python(const QJsonValue &jsonData) +{ + switch (jsonData.type()) + { + case QJsonValue::Null: + return Py_BuildValue(""); + case QJsonValue::Undefined: + return Py_BuildValue(""); + case QJsonValue::Double: + { + if (std::rint(jsonData.toDouble()) != jsonData.toDouble()) + { + return Py_BuildValue("d", jsonData.toDouble()); + } + return Py_BuildValue("i", jsonData.toInt()); + } + case QJsonValue::Bool: + return Py_BuildValue("i", jsonData.toBool() ? 1 : 0); + case QJsonValue::String: + return Py_BuildValue("s", jsonData.toString().toUtf8().constData()); + case QJsonValue::Object: + { + PyObject * dict= PyDict_New(); + QJsonObject objectData = jsonData.toObject(); + for (QJsonObject::iterator i = objectData.begin(); i != objectData.end(); ++i) + { + PyObject * obj = json2python(*i); + PyDict_SetItemString(dict, i.key().toStdString().c_str(), obj); + Py_XDECREF(obj); + } + return dict; + } + case QJsonValue::Array: + { + QJsonArray arrayData = jsonData.toArray(); + PyObject * list = PyList_New(arrayData.size()); + int index = 0; + for (QJsonArray::iterator i = arrayData.begin(); i != arrayData.end(); ++i, ++index) + { + PyObject * obj = json2python(*i); + Py_INCREF(obj); + PyList_SetItem(list, index, obj); + Py_XDECREF(obj); + } + return list; + } + } + + assert(false); + return nullptr; +} + +// Python method table +PyMethodDef EffectModule::effectMethods[] = { + {"setColor" , EffectModule::wrapSetColor , METH_VARARGS, "Set a new color for the leds."}, + {"setImage" , EffectModule::wrapSetImage , METH_VARARGS, "Set a new image to process and determine new led colors."}, + {"getImage" , EffectModule::wrapGetImage , METH_VARARGS, "get image data from file."}, + {"abort" , EffectModule::wrapAbort , METH_NOARGS, "Check if the effect should abort execution."}, + {"imageShow" , EffectModule::wrapImageShow , METH_VARARGS, "set current effect image to hyperion core."}, + {"imageLinearGradient" , EffectModule::wrapImageLinearGradient , METH_VARARGS, ""}, + {"imageConicalGradient" , EffectModule::wrapImageConicalGradient , METH_VARARGS, ""}, + {"imageRadialGradient" , EffectModule::wrapImageRadialGradient , METH_VARARGS, ""}, + {"imageSolidFill" , EffectModule::wrapImageSolidFill , METH_VARARGS, ""}, + {"imageDrawLine" , EffectModule::wrapImageDrawLine , METH_VARARGS, ""}, + {"imageDrawPoint" , EffectModule::wrapImageDrawPoint , METH_VARARGS, ""}, + {"imageDrawRect" , EffectModule::wrapImageDrawRect , METH_VARARGS, ""}, + {"imageDrawPolygon" , EffectModule::wrapImageDrawPolygon , METH_VARARGS, ""}, + {"imageDrawPie" , EffectModule::wrapImageDrawPie , METH_VARARGS, ""}, + {"imageSetPixel" , EffectModule::wrapImageSetPixel , METH_VARARGS, "set pixel color of image"}, + {"imageGetPixel" , EffectModule::wrapImageGetPixel , METH_VARARGS, "get pixel color of image"}, + {"imageSave" , EffectModule::wrapImageSave , METH_NOARGS, "adds a new background image"}, + {"imageMinSize" , EffectModule::wrapImageMinSize , METH_VARARGS, "sets minimal dimension of background image"}, + {"imageWidth" , EffectModule::wrapImageWidth , METH_NOARGS, "gets image width"}, + {"imageHeight" , EffectModule::wrapImageHeight , METH_NOARGS, "gets image height"}, + {"imageCRotate" , EffectModule::wrapImageCRotate , METH_VARARGS, "rotate the coordinate system by given angle"}, + {"imageCOffset" , EffectModule::wrapImageCOffset , METH_VARARGS, "Add offset to the coordinate system"}, + {"imageCShear" , EffectModule::wrapImageCShear , METH_VARARGS, "Shear of coordinate system by the given horizontal/vertical axis"}, + {"imageResetT" , EffectModule::wrapImageResetT , METH_NOARGS, "Resets all coords modifications (rotate,offset,shear)"}, + {NULL, NULL, 0, NULL} +}; + +PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(); + + // check if we have aborted already + if (effect->hasInteruptionFlag()) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // check the number of arguments + int argCount = PyTuple_Size(args); + if (argCount == 3) + { + // three seperate arguments for red, green, and blue + ColorRgb color; + if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) + { + effect->_colors.fill(color); + effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false); + return Py_BuildValue(""); + } + return nullptr; + } + else if (argCount == 1) + { + // bytearray of values + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "O", &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + size_t length = PyByteArray_Size(bytearray); + if (length == 3 * effect->_hyperion->getLedCount()) + { + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(effect->_colors.data(), data, length); + effect->setInput(effect->_priority, effect->_colors.toStdVector(), timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Function expect 1 or 3 arguments"); + return nullptr; + } +} + +PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(); + + // check if we have aborted already + if (effect->hasInteruptionFlag()) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // bytearray of values + int width, height; + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length == 3 * width * height) + { + Image image(width, height); + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(image.memptr(), data, length); + effect->setInputImage(effect->_priority, image, timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*width*height"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 3 is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + + // error + PyErr_SetString(PyExc_RuntimeError, "Unknown error"); + return nullptr; +} + +PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args) +{ + Q_INIT_RESOURCE(EffectEngine); + + char *source; + if(!PyArg_ParseTuple(args, "s", &source)) + { + PyErr_SetString(PyExc_TypeError, "String required"); + return NULL; + } + + QString file = QString::fromUtf8(source); + + if (file.mid(0, 1) == ":") + file = ":/effects/"+file.mid(1); + + QImageReader reader(file); + + if (reader.canRead()) + { + PyObject* result = PyList_New(reader.imageCount()); + + for (int i = 0; i < reader.imageCount(); ++i) + { + reader.jumpToImage(i); + if (reader.canRead()) + { + QImage qimage = reader.read(); + + int width = qimage.width(); + int height = qimage.height(); + + QByteArray binaryImage; + for (int i = 0; i(qimage.scanLine(i)); + for (int j = 0; j< width; ++j) + { + binaryImage.append((char) qRed(scanline[j])); + binaryImage.append((char) qGreen(scanline[j])); + binaryImage.append((char) qBlue(scanline[j])); + } + } + PyList_SET_ITEM(result, i, Py_BuildValue("{s:i,s:i,s:O}", "imageWidth", width, "imageHeight", height, "imageData", PyByteArray_FromStringAndSize(binaryImage.constData(),binaryImage.size()))); + } + else + { + PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); + return NULL; + } + } + return result; + } + else + { + PyErr_SetString(PyExc_TypeError, reader.errorString().toUtf8().constData()); + return NULL; + } +} + +PyObject* EffectModule::wrapAbort(PyObject *self, PyObject *) +{ + Effect * effect = getEffect(); + + // Test if the effect has reached it end time + if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) + { + effect->setInteruptionFlag(); + } + + return Py_BuildValue("i", effect->hasInteruptionFlag() ? 1 : 0); +} + + +PyObject* EffectModule::wrapImageShow(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + int argCount = PyTuple_Size(args); + int imgId = -1; + bool argsOk = (argCount == 0); + if (argCount == 1 && PyArg_ParseTuple(args, "i", &imgId)) + { + argsOk = true; + } + + if ( ! argsOk || (imgId>-1 && imgId >= effect->_imageStack.size())) + { + return nullptr; + } + + + QImage * qimage = (imgId<0) ? &(effect->_image) : &(effect->_imageStack[imgId]); + int width = qimage->width(); + int height = qimage->height(); + + Image image(width, height); + QByteArray binaryImage; + + for (int i = 0; i(qimage->scanLine(i)); + for (int j = 0; j< width; ++j) + { + binaryImage.append((char) qRed(scanline[j])); + binaryImage.append((char) qGreen(scanline[j])); + binaryImage.append((char) qBlue(scanline[j])); + } + } + + memcpy(image.memptr(), binaryImage.data(), binaryImage.size()); + effect->setInputImage(effect->_priority, image, timeout, false); + + return Py_BuildValue(""); +} + +PyObject* EffectModule::wrapImageLinearGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int startRX = 0; + int startRY = 0; + int startX = 0; + int startY = 0; + int endX, width = effect->_imageSize.width(); + int endY, height = effect->_imageSize.height(); + int spread = 0; + + bool argsOK = false; + + if ( argCount == 10 && PyArg_ParseTuple(args, "iiiiiiiiOi", &startRX, &startRY, &width, &height, &startX, &startY, &endX, &endY, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiOi", &startX, &startY, &endX, &endY, &bytearray, &spread) ) + { + argsOK = true; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + const int length = PyByteArray_Size(bytearray); + const unsigned arrayItemLength = 5; + if (length % arrayItemLength == 0) + { + QRect myQRect(startRX,startRY,width,height); + QLinearGradient gradient(QPoint(startX,startY), QPoint(endX,endY)); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx(spread)); + effect->_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "No bytearray properly defined"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageConicalGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int centerX, centerY, angle; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiiO", &startX, &startY, &width, &height, ¢erX, ¢erY, &angle, &bytearray) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "iiiO", ¢erX, ¢erY, &angle, &bytearray) ) + { + argsOK = true; + } + angle = qMax(qMin(angle,360),0); + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + const int length = PyByteArray_Size(bytearray); + const unsigned arrayItemLength = 5; + if (length % arrayItemLength == 0) + { + QRect myQRect(startX,startY,width,height); + QConicalGradient gradient(QPoint(centerX,centerY), angle ); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 8 is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageRadialGradient(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + PyObject * bytearray = nullptr; + int centerX, centerY, radius, focalX, focalY, focalRadius, spread; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 12 && PyArg_ParseTuple(args, "iiiiiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiOi", &startX, &startY, &width, &height, ¢erX, ¢erY, &radius, &bytearray, &spread) ) + { + argsOK = true; + focalX = centerX; + focalY = centerY; + focalRadius = radius; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiOi", ¢erX, ¢erY, &radius, &focalX, &focalY, &focalRadius, &bytearray, &spread) ) + { + argsOK = true; + } + if ( argCount == 5 && PyArg_ParseTuple(args, "iiiOi", ¢erX, ¢erY, &radius, &bytearray, &spread) ) + { + argsOK = true; + focalX = centerX; + focalY = centerY; + focalRadius = radius; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 4 == 0) + { + + QRect myQRect(startX,startY,width,height); + QRadialGradient gradient(QPoint(centerX,centerY), qMax(radius,0) ); + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx(spread)); + effect->_painter->fillRect(myQRect, gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 4"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPolygon(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + PyObject * bytearray = nullptr; + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + + bool argsOK = false; + + if ( argCount == 5 && PyArg_ParseTuple(args, "Oiiii", &bytearray, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "Oiii", &bytearray, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 2 == 0) + { + QVector points; + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idx_painter; + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + painter->setPen(newPen); + painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); + painter->drawPolygon(points); + painter->setPen(oldPen); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 2"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 1 is not a bytearray"); + return nullptr; + } + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPie(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + PyObject * bytearray = nullptr; + + QString brush; + int argCount = PyTuple_Size(args); + int radius, centerX, centerY; + int startAngle = 0; + int spanAngle = 360; + int r = 0; + int g = 0; + int b = 0; + int a = 255; + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &r, &g, &b) ) + { + argsOK = true; + } + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiisO", ¢erX, ¢erY, &radius, &startAngle, &spanAngle, &brush, &bytearray) ) + { + argsOK = true; + } + if ( argCount == 5 && PyArg_ParseTuple(args, "iiisO", ¢erX, ¢erY, &radius, &brush, &bytearray) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + startAngle = qMax(qMin(startAngle,360),0); + spanAngle = qMax(qMin(spanAngle,360),-360); + + if( argCount == 7 || argCount == 5 ) + { + a = 0; + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length % 5 == 0) + { + + QConicalGradient gradient(QPoint(centerX,centerY), startAngle); + + + char * data = PyByteArray_AS_STRING(bytearray); + + for (int idx=0; idxsetBrush(gradient); + + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should multiple of 5"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Last argument is not a bytearray"); + return nullptr; + } + } + else + { + painter->setBrush(QBrush(QColor(r,g,b,a), Qt::SolidPattern)); + } + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + painter->setPen(newPen); + painter->drawPie(centerX - radius, centerY - radius, centerX + radius, centerY + radius, startAngle * 16, spanAngle * 16); + painter->setPen(oldPen); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageSolidFill(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &startX, &startY, &width, &height, &r, &g, &b) ) + { + argsOK = true; + } + if ( argCount == 4 && PyArg_ParseTuple(args, "iiii",&r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 3 && PyArg_ParseTuple(args, "iii",&r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QRect myQRect(startX,startY,width,height); + effect->_painter->fillRect(myQRect, QColor(r,g,b,a)); + return Py_BuildValue(""); + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageDrawLine(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int thick = 1; + int endX = effect->_imageSize.width(); + int endY = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &endX, &endY, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QRect myQRect(startX, startY, endX, endY); + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawLine(startX, startY, endX, endY); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawPoint(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b, x, y; + int a = 255; + int thick = 1; + + bool argsOK = false; + + if ( argCount == 7 && PyArg_ParseTuple(args, "iiiiiii", &x, &y, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 6 && PyArg_ParseTuple(args, "iiiiii", &x, &y, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawPoint(x, y); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageDrawRect(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b; + int a = 255; + int startX = 0; + int startY = 0; + int thick = 1; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + bool argsOK = false; + + if ( argCount == 9 && PyArg_ParseTuple(args, "iiiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b, &a) ) + { + argsOK = true; + } + if ( argCount == 8 && PyArg_ParseTuple(args, "iiiiiiii", &startX, &startY, &width, &height, &thick, &r, &g, &b) ) + { + argsOK = true; + } + + if (argsOK) + { + QPainter * painter = effect->_painter; + QRect myQRect(startX,startY,width,height); + QPen oldPen = painter->pen(); + QPen newPen(QColor(r,g,b,a)); + newPen.setWidth(thick); + painter->setPen(newPen); + painter->drawRect(startX, startY, width, height); + painter->setPen(oldPen); + + return Py_BuildValue(""); + } + return nullptr; +} + + +PyObject* EffectModule::wrapImageSetPixel(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int r, g, b, x, y; + + if ( argCount == 5 && PyArg_ParseTuple(args, "iiiii", &x, &y, &r, &g, &b ) ) + { + effect->_image.setPixel(x,y,qRgb(r,g,b)); + return Py_BuildValue(""); + } + + return nullptr; +} + + +PyObject* EffectModule::wrapImageGetPixel(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int x, y; + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &x, &y) ) + { + QRgb rgb = effect->_image.pixel(x,y); + return Py_BuildValue("iii",qRed(rgb),qGreen(rgb),qBlue(rgb)); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageSave(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + QImage img(effect->_image.copy()); + effect->_imageStack.append(img); + + return Py_BuildValue("i", effect->_imageStack.size()-1); +} + +PyObject* EffectModule::wrapImageMinSize(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int w, h; + int width = effect->_imageSize.width(); + int height = effect->_imageSize.height(); + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &w, &h) ) + { + if (width_painter; + + effect->_image = effect->_image.scaled(qMax(width,w),qMax(height,h), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + effect->_imageSize = effect->_image.size(); + effect->_painter = new QPainter(&(effect->_image)); + } + return Py_BuildValue("ii", effect->_image.width(), effect->_image.height()); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageWidth(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + return Py_BuildValue("i", effect->_imageSize.width()); +} + +PyObject* EffectModule::wrapImageHeight(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + return Py_BuildValue("i", effect->_imageSize.height()); +} + +PyObject* EffectModule::wrapImageCRotate(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int argCount = PyTuple_Size(args); + int angle; + + if ( argCount == 1 && PyArg_ParseTuple(args, "i", &angle ) ) + { + angle = qMax(qMin(angle,360),0); + effect->_painter->rotate(angle); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageCOffset(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int offsetX = 0; + int offsetY = 0; + int argCount = PyTuple_Size(args); + + if ( argCount == 2 ) + { + PyArg_ParseTuple(args, "ii", &offsetX, &offsetY ); + } + + effect->_painter->translate(QPoint(offsetX,offsetY)); + return Py_BuildValue(""); +} + +PyObject* EffectModule::wrapImageCShear(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + int sh,sv; + int argCount = PyTuple_Size(args); + + if ( argCount == 2 && PyArg_ParseTuple(args, "ii", &sh, &sv )) + { + effect->_painter->shear(sh,sv); + return Py_BuildValue(""); + } + return nullptr; +} + +PyObject* EffectModule::wrapImageResetT(PyObject *self, PyObject *args) +{ + Effect * effect = getEffect(); + + effect->_painter->resetTransform(); + return Py_BuildValue(""); +} + +Effect * EffectModule::getEffect() +{ + // extract the module from the runtime + PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); + + if (!PyModule_Check(module)) + { + // something is wrong + Py_XDECREF(module); + Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); + return nullptr; + } + + // retrieve the capsule with the effect + PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); + Py_XDECREF(module); + + if (!PyCapsule_CheckExact(effectCapsule)) + { + // something is wrong + Py_XDECREF(effectCapsule); + Error(Logger::getInstance("EFFECTENGINE"), "Unable to retrieve the effect object from the Python runtime"); + return nullptr; + } + + // Get the effect from the capsule + Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); + Py_XDECREF(effectCapsule); + return effect; +} diff --git a/libsrc/grabber/amlogic/AmlogicWrapper.cpp b/libsrc/grabber/amlogic/AmlogicWrapper.cpp index 8db02591..205787e8 100644 --- a/libsrc/grabber/amlogic/AmlogicWrapper.cpp +++ b/libsrc/grabber/amlogic/AmlogicWrapper.cpp @@ -1,7 +1,7 @@ #include -AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("AmLogic", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("AmLogic", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(grabWidth, grabHeight) {} diff --git a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp index ec7aacc3..c1222ef7 100644 --- a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp +++ b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp @@ -7,7 +7,7 @@ #include "grabber/DispmanxFrameGrabber.h" DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned height) - : Grabber("DISPMANXGRABBER", width, height) + : Grabber("DISPMANXGRABBER", 0, 0) , _vc_display(0) , _vc_resource(0) , _vc_flags(0) @@ -20,48 +20,60 @@ DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned // Initiase BCM bcm_host_init(); - { - // Check if the display can be opened and display the current resolution - // Open the connection to the display - _vc_display = vc_dispmanx_display_open(0); - assert(_vc_display > 0); + // Check if the display can be opened and display the current resolution + // Open the connection to the display + _vc_display = vc_dispmanx_display_open(0); + assert(_vc_display > 0); - // Obtain the display information - DISPMANX_MODEINFO_T vc_info; - int result = vc_dispmanx_display_get_info(_vc_display, &vc_info); - // Keep compiler happy in 'release' mode - (void)result; - assert(result == 0); - Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); + // Obtain the display information + DISPMANX_MODEINFO_T vc_info; + int result = vc_dispmanx_display_get_info(_vc_display, &vc_info); + // Keep compiler happy in 'release' mode + (void)result; + assert(result == 0); + Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); - // Close the displaye - vc_dispmanx_display_close(_vc_display); - } + // Close the displaye + vc_dispmanx_display_close(_vc_display); - // Create the resources for capturing image - uint32_t vc_nativeImageHandle; - _vc_resource = vc_dispmanx_resource_create( - VC_IMAGE_RGBA32, - width, - height, - &vc_nativeImageHandle); - assert(_vc_resource); - - // Define the capture rectangle with the same size - vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); + // init the resource and capture rectangle + setWidthHeight(width, height); } DispmanxFrameGrabber::~DispmanxFrameGrabber() { - delete[] _captureBuffer; - - // Clean up resources - vc_dispmanx_resource_delete(_vc_resource); + freeResources(); // De-init BCM bcm_host_deinit(); } +void DispmanxFrameGrabber::freeResources() +{ + delete[] _captureBuffer; + // Clean up resources + vc_dispmanx_resource_delete(_vc_resource); +} + +void DispmanxFrameGrabber::setWidthHeight(int width, int height) +{ + if(_width != width || _height != height) + { + freeResources(); + // Create the resources for capturing image + uint32_t vc_nativeImageHandle; + _vc_resource = vc_dispmanx_resource_create( + VC_IMAGE_RGBA32, + width, + height, + &vc_nativeImageHandle); + assert(_vc_resource); + + // Define the capture rectangle with the same size + vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); + } +} + void DispmanxFrameGrabber::setFlags(const int vc_flags) { _vc_flags = vc_flags; @@ -143,8 +155,8 @@ int DispmanxFrameGrabber::grabFrame(Image & image) unsigned capturePitch = (_rectangle.width * sizeof(ColorRgba) + 63) & (~63); // grab to temp buffer if image pitch isn't valid or if we are cropping - if (imagePitch != capturePitch - || (unsigned)_rectangle.width != imageWidth + if (imagePitch != capturePitch + || (unsigned)_rectangle.width != imageWidth || (unsigned)_rectangle.height != imageHeight) { // check if we need to resize the capture buffer diff --git a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp index 633fa60d..8a12ed27 100644 --- a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp +++ b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp @@ -1,7 +1,7 @@ #include -DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("Dispmanx", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("Dispmanx", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(grabWidth, grabHeight) { setImageProcessorEnabled(false); diff --git a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp index 3eec042c..9cf2675b 100755 --- a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp +++ b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp @@ -16,31 +16,9 @@ FramebufferFrameGrabber::FramebufferFrameGrabber(const QString & device, const u : Grabber("FRAMEBUFFERGRABBER", width, height) , _fbfd(0) , _fbp(0) - , _fbDevice(device) + , _fbDevice() { - int result; - struct fb_var_screeninfo vinfo; - - // Check if the framebuffer device can be opened and display the current resolution - _fbfd = open(QSTRING_CSTR(_fbDevice), O_RDONLY); - if (_fbfd == 0) - { - Error(_log, "Error openning %s", QSTRING_CSTR(_fbDevice)); - } - else - { - // get variable screen information - result = ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); - if (result != 0) - { - Error(_log, "Could not get screen information"); - } - else - { - Info(_log, "Display opened with resolution: %dx%d@%dbit", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); - } - close(_fbfd); - } + setDevicePath(device); } FramebufferFrameGrabber::~FramebufferFrameGrabber() @@ -63,7 +41,7 @@ int FramebufferFrameGrabber::grabFrame(Image & image) bytesPerPixel = vinfo.bits_per_pixel / 8; capSize = vinfo.xres * vinfo.yres * bytesPerPixel; - + switch (vinfo.bits_per_pixel) { case 16: pixelFormat = PIXELFORMAT_BGR16; break; @@ -76,7 +54,7 @@ int FramebufferFrameGrabber::grabFrame(Image & image) } /* map the device to memory */ - _fbp = (unsigned char*)mmap(0, capSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, _fbfd, 0); + _fbp = (unsigned char*)mmap(0, capSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, _fbfd, 0); _imageResampler.setHorizontalPixelDecimation(vinfo.xres/_width); _imageResampler.setVerticalPixelDecimation(vinfo.yres/_height); @@ -86,9 +64,41 @@ int FramebufferFrameGrabber::grabFrame(Image & image) vinfo.xres * bytesPerPixel, pixelFormat, image); - + munmap(_fbp, capSize); close(_fbfd); return 0; } + +void FramebufferFrameGrabber::setDevicePath(const QString& path) +{ + if(_fbDevice != path) + { + _fbDevice = path; + int result; + struct fb_var_screeninfo vinfo; + + // Check if the framebuffer device can be opened and display the current resolution + _fbfd = open(QSTRING_CSTR(_fbDevice), O_RDONLY); + if (_fbfd == 0) + { + Error(_log, "Error openning %s", QSTRING_CSTR(_fbDevice)); + } + else + { + // get variable screen information + result = ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); + if (result != 0) + { + Error(_log, "Could not get screen information"); + } + else + { + Info(_log, "Display opened with resolution: %dx%d@%dbit", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); + } + close(_fbfd); + } + + } +} diff --git a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp index 9a7cc367..4a88bbee 100644 --- a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp +++ b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp @@ -1,7 +1,7 @@ #include -FramebufferWrapper::FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("FrameBuffer", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +FramebufferWrapper::FramebufferWrapper(const QString & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("FrameBuffer", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(device, grabWidth, grabHeight) {} diff --git a/libsrc/grabber/osx/OsxFrameGrabber.cpp b/libsrc/grabber/osx/OsxFrameGrabber.cpp index 97862f0f..37b384ef 100755 --- a/libsrc/grabber/osx/OsxFrameGrabber.cpp +++ b/libsrc/grabber/osx/OsxFrameGrabber.cpp @@ -7,30 +7,10 @@ OsxFrameGrabber::OsxFrameGrabber(const unsigned display, const unsigned width, const unsigned height) : Grabber("OSXGRABBER", width, height) - , _screenIndex(display) + , _screenIndex(100) { - CGImageRef image; - CGDisplayCount displayCount; - CGDirectDisplayID displays[8]; - - // get list of displays - CGGetActiveDisplayList(8, displays, &displayCount); - if (_screenIndex + 1 > displayCount) - { - Error(_log, "Display with index %d is not available. Using main display", _screenIndex); - _display = kCGDirectMainDisplay; - } - else - { - _display = displays[_screenIndex]; - } - - image = CGDisplayCreateImage(_display); - assert(image != NULL); - - Info(_log, "Display opened with resolution: %dx%d@%dbit", CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerPixel(image)); - - CGImageRelease(image); + // check if display is available + setDisplayIndex(display); } OsxFrameGrabber::~OsxFrameGrabber() @@ -43,11 +23,11 @@ int OsxFrameGrabber::grabFrame(Image & image) CGImageRef dispImage; CFDataRef imgData; - unsigned char * pImgData; + unsigned char * pImgData; unsigned dspWidth, dspHeight; - + dispImage = CGDisplayCreateImage(_display); - + // display lost, use main if (dispImage == NULL && _display) { @@ -63,7 +43,7 @@ int OsxFrameGrabber::grabFrame(Image & image) pImgData = (unsigned char*) CFDataGetBytePtr(imgData); dspWidth = CGImageGetWidth(dispImage); dspHeight = CGImageGetHeight(dispImage); - + _imageResampler.setHorizontalPixelDecimation(dspWidth/_width); _imageResampler.setVerticalPixelDecimation(dspHeight/_height); _imageResampler.processImage( pImgData, @@ -72,9 +52,40 @@ int OsxFrameGrabber::grabFrame(Image & image) CGImageGetBytesPerRow(dispImage), PIXELFORMAT_BGR32, image); - + CFRelease(imgData); CGImageRelease(dispImage); return 0; } + +void OsxFrameGrabber::setDisplayIndex(int index) +{ + if(_screenIndex != index) + { + _screenIndex = index; + + CGImageRef image; + CGDisplayCount displayCount; + CGDirectDisplayID displays[8]; + + // get list of displays + CGGetActiveDisplayList(8, displays, &displayCount); + if (_screenIndex + 1 > displayCount) + { + Error(_log, "Display with index %d is not available. Using main display", _screenIndex); + _display = kCGDirectMainDisplay; + } + else + { + _display = displays[_screenIndex]; + } + + image = CGDisplayCreateImage(_display); + assert(image != NULL); + + Info(_log, "Display opened with resolution: %dx%d@%dbit", CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerPixel(image)); + + CGImageRelease(image); + } +} diff --git a/libsrc/grabber/osx/OsxWrapper.cpp b/libsrc/grabber/osx/OsxWrapper.cpp index f543eada..6324532c 100644 --- a/libsrc/grabber/osx/OsxWrapper.cpp +++ b/libsrc/grabber/osx/OsxWrapper.cpp @@ -1,7 +1,7 @@ #include -OsxWrapper::OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("OSX FrameGrabber", &_grabber, grabWidth, grabHeight, updateRate_Hz, priority, hyperion::COMP_GRABBER) +OsxWrapper::OsxWrapper(const unsigned display, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) + : GrabberWrapper("OSX FrameGrabber", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(display, grabWidth, grabHeight) {} diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 16df0b17..64f4d418 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -27,13 +27,9 @@ V4L2Grabber::V4L2Grabber(const QString & device , int input , VideoStandard videoStandard , PixelFormat pixelFormat - , unsigned width - , unsigned height - , int frameDecimation - , int horizontalPixelDecimation - , int verticalPixelDecimation + , int pixelDecimation ) - : Grabber("V4L2:"+device, width, height) + : Grabber("V4L2:"+device) , _deviceName(device) , _input(input) , _videoStandard(videoStandard) @@ -41,9 +37,9 @@ V4L2Grabber::V4L2Grabber(const QString & device , _fileDescriptor(-1) , _buffers() , _pixelFormat(pixelFormat) + , _pixelDecimation(pixelDecimation) , _lineLength(-1) , _frameByteSize(-1) - , _frameDecimation(qMax(1, frameDecimation)) , _noSignalCounterThreshold(50) , _noSignalThresholdColor(ColorRgb{0,0,0}) , _signalDetectionEnabled(true) @@ -53,14 +49,13 @@ V4L2Grabber::V4L2Grabber(const QString & device , _y_frac_min(0.25) , _x_frac_max(0.75) , _y_frac_max(0.75) - , _currentFrame(0) , _streamNotifier(nullptr) , _initialized(false) , _deviceAutoDiscoverEnabled(false) { - _imageResampler.setHorizontalPixelDecimation(qMax(1, horizontalPixelDecimation)); - _imageResampler.setVerticalPixelDecimation(qMax(1, verticalPixelDecimation)); + //_imageResampler.setHorizontalPixelDecimation(pixelDecimation); + //_imageResampler.setVerticalPixelDecimation(pixelDecimation); getV4Ldevices(); } @@ -245,11 +240,13 @@ void V4L2Grabber::open_device() if (-1 == stat(QSTRING_CSTR(_deviceName), &st)) { throw_errno_exception("Cannot identify '" + _deviceName + "'"); + return; } if (!S_ISCHR(st.st_mode)) { throw_exception("'" + _deviceName + "' is no device"); + return; } _fileDescriptor = open(QSTRING_CSTR(_deviceName), O_RDWR | O_NONBLOCK, 0); @@ -257,6 +254,7 @@ void V4L2Grabber::open_device() if (-1 == _fileDescriptor) { throw_errno_exception("Cannot open '" + _deviceName + "'"); + return; } // create the notifier for when a new frame is available @@ -268,7 +266,10 @@ void V4L2Grabber::open_device() void V4L2Grabber::close_device() { if (-1 == close(_fileDescriptor)) + { throw_errno_exception("close"); + return; + } _fileDescriptor = -1; @@ -288,6 +289,7 @@ void V4L2Grabber::init_read(unsigned int buffer_size) if (!_buffers[0].start) { throw_exception("Out of memory"); + return; } } @@ -304,13 +306,16 @@ void V4L2Grabber::init_mmap() if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { throw_exception("'" + _deviceName + "' does not support memory mapping"); + return; } else { throw_errno_exception("VIDIOC_REQBUFS"); + return; } } if (req.count < 2) { throw_exception("Insufficient buffer memory on " + _deviceName); + return; } _buffers.resize(req.count); @@ -325,7 +330,10 @@ void V4L2Grabber::init_mmap() buf.index = n_buffers; if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) + { throw_errno_exception("VIDIOC_QUERYBUF"); + return; + } _buffers[n_buffers].length = buf.length; _buffers[n_buffers].start = @@ -336,7 +344,10 @@ void V4L2Grabber::init_mmap() _fileDescriptor, buf.m.offset); if (MAP_FAILED == _buffers[n_buffers].start) + { throw_errno_exception("mmap"); + return; + } } } @@ -354,8 +365,10 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) if (EINVAL == errno) { throw_exception("'" + _deviceName + "' does not support user pointer"); + return; } else { throw_errno_exception("VIDIOC_REQBUFS"); + return; } } @@ -367,6 +380,7 @@ void V4L2Grabber::init_userp(unsigned int buffer_size) if (!_buffers[n_buffers].start) { throw_exception("Out of memory"); + return; } } } @@ -378,14 +392,17 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { if (EINVAL == errno) { throw_exception("'" + _deviceName + "' is no V4L2 device"); + return; } else { throw_errno_exception("VIDIOC_QUERYCAP"); + return; } } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { throw_exception("'" + _deviceName + "' is no video capture device"); + return; } switch (_ioMethod) { @@ -393,6 +410,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (!(cap.capabilities & V4L2_CAP_READWRITE)) { throw_exception("'" + _deviceName + "' does not support read i/o"); + return; } break; @@ -401,6 +419,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (!(cap.capabilities & V4L2_CAP_STREAMING)) { throw_exception("'" + _deviceName + "' does not support streaming i/o"); + return; } break; } @@ -438,6 +457,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_INPUT, &input)) { throw_errno_exception("VIDIOC_S_INPUT"); + return; } } @@ -450,6 +470,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -459,6 +480,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -468,6 +490,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_STD, &std_id)) { throw_errno_exception("VIDIOC_S_STD"); + return; } } break; @@ -485,6 +508,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { throw_errno_exception("VIDIOC_G_FMT"); + return; } // set the requested pixel format @@ -505,19 +529,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; } - // set the requested withd and height - if (_width > 0 || _height > 0) - { - if (_width > 0) - { - fmt.fmt.pix.width = _width; - } - - if (fmt.fmt.pix.height > 0) - { - fmt.fmt.pix.height = _height; - } - } + // calc the size based on pixelDecimation + fmt.fmt.pix.width = fmt.fmt.pix.width / _pixelDecimation; + fmt.fmt.pix.height = fmt.fmt.pix.height / _pixelDecimation; // set the line length _lineLength = fmt.fmt.pix.bytesperline; @@ -526,6 +540,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) { throw_errno_exception("VIDIOC_S_FMT"); + return; } // get the format settings again @@ -533,6 +548,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) { throw_errno_exception("VIDIOC_G_FMT"); + return; } // store width & height @@ -563,6 +579,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; default: throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); + return; } switch (_ioMethod) { @@ -590,7 +607,10 @@ void V4L2Grabber::uninit_device() case IO_METHOD_MMAP: for (size_t i = 0; i < _buffers.size(); ++i) if (-1 == munmap(_buffers[i].start, _buffers[i].length)) + { throw_errno_exception("munmap"); + return; + } break; case IO_METHOD_USERPTR: @@ -620,11 +640,17 @@ void V4L2Grabber::start_capturing() buf.index = i; if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { throw_errno_exception("VIDIOC_QBUF"); + return; + } } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) + { throw_errno_exception("VIDIOC_STREAMON"); + return; + } break; } case IO_METHOD_USERPTR: @@ -640,11 +666,17 @@ void V4L2Grabber::start_capturing() buf.length = _buffers[i].length; if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { throw_errno_exception("VIDIOC_QBUF"); + return; + } } v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(VIDIOC_STREAMON, &type)) + { throw_errno_exception("VIDIOC_STREAMON"); + return; + } break; } } @@ -692,6 +724,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("read"); + return 0; } } @@ -718,6 +751,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("VIDIOC_DQBUF"); + return 0; } } @@ -728,6 +762,7 @@ int V4L2Grabber::read_frame() if (-1 == xioctl(VIDIOC_QBUF, &buf)) { throw_errno_exception("VIDIOC_QBUF"); + return 0; } break; @@ -752,6 +787,7 @@ int V4L2Grabber::read_frame() default: throw_errno_exception("VIDIOC_DQBUF"); + return 0; } } @@ -768,6 +804,7 @@ int V4L2Grabber::read_frame() if (-1 == xioctl(VIDIOC_QBUF, &buf)) { throw_errno_exception("VIDIOC_QBUF"); + return 0; } break; } @@ -783,19 +820,15 @@ int V4L2Grabber::read_frame() bool V4L2Grabber::process_image(const void *p, int size) { - if (++_currentFrame >= _frameDecimation) + // We do want a new frame... + if (size != _frameByteSize) { - // We do want a new frame... - if (size != _frameByteSize) - { - Error(_log, "Frame too small: %d != %d", size, _frameByteSize); - } - else - { - process_image(reinterpret_cast(p)); - _currentFrame = 0; // restart counting - return true; - } + Error(_log, "Frame too small: %d != %d", size, _frameByteSize); + } + else + { + process_image(reinterpret_cast(p)); + return true; } return false; @@ -874,20 +907,44 @@ int V4L2Grabber::xioctl(int request, void *arg) void V4L2Grabber::throw_exception(const QString & error) { - throw std::runtime_error(error.toStdString()); + Error(_log, "Throws error: %s", QSTRING_CSTR(error)); } void V4L2Grabber::throw_errno_exception(const QString & error) { - throw std::runtime_error(QString(error + " error code " + QString::number(errno) + ", " + strerror(errno)).toStdString()); + Error(_log, "Throws error nr: %s", QSTRING_CSTR(QString(error + " error code " + QString::number(errno) + ", " + strerror(errno)))); } void V4L2Grabber::setSignalDetectionEnable(bool enable) { - _signalDetectionEnabled = enable; + if(_signalDetectionEnabled != enable) + { + _signalDetectionEnabled = enable; + Info(_log, "Signal detection is now %s", enable ? "enabled" : "disabled"); + } } bool V4L2Grabber::getSignalDetectionEnabled() { return _signalDetectionEnabled; } + +void V4L2Grabber::setPixelDecimation(int pixelDecimation) +{ + if(_pixelDecimation != pixelDecimation) + { + uninit(); + init(); + } +} + +void V4L2Grabber::setInputVideoStandard(int input, VideoStandard videoStandard) +{ + if(_input != input || _videoStandard != videoStandard) + { + _input = input; + _videoStandard = videoStandard; + uninit(); + init(); + } +} diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index aef95648..809b1e09 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -2,45 +2,29 @@ #include -#include +// qt +#include V4L2Wrapper::V4L2Wrapper(const QString &device, int input, VideoStandard videoStandard, PixelFormat pixelFormat, - unsigned width, - unsigned height, - int frameDecimation, - int pixelDecimation, - double redSignalThreshold, - double greenSignalThreshold, - double blueSignalThreshold, - const int priority) - : GrabberWrapper("V4L2:"+device, &_grabber, width, height, 8, priority, hyperion::COMP_V4L) + int pixelDecimation ) + : GrabberWrapper("V4L2:"+device, &_grabber, 0, 0, 10) , _grabber(device, input, videoStandard, pixelFormat, - width, - height, - frameDecimation, - pixelDecimation, pixelDecimation) { - // set the signal detection threshold of the grabber - _grabber.setSignalThreshold( redSignalThreshold, greenSignalThreshold, blueSignalThreshold, 50); _ggrabber = &_grabber; // register the image type qRegisterMetaType>("Image"); - qRegisterMetaType>("std::vector"); - qRegisterMetaType("hyperion::Components"); // Handle the image in the captured thread using a direct connection QObject::connect(&_grabber, SIGNAL(newFrame(Image)), this, SLOT(newFrame(Image)), Qt::DirectConnection); QObject::connect(&_grabber, SIGNAL(readError(const char*)), this, SLOT(readError(const char*)), Qt::DirectConnection); - - _timer.setInterval(500); } bool V4L2Wrapper::start() @@ -54,6 +38,11 @@ void V4L2Wrapper::stop() GrabberWrapper::stop(); } +void V4L2Wrapper::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold) +{ + _grabber.setSignalThreshold( redSignalThreshold, greenSignalThreshold, blueSignalThreshold, 50); +} + void V4L2Wrapper::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) { _grabber.setCropping(cropLeft, cropRight, cropTop, cropBottom); @@ -66,11 +55,7 @@ void V4L2Wrapper::setSignalDetectionOffset(double verticalMin, double horizontal void V4L2Wrapper::newFrame(const Image &image) { - emit emitImage(_priority, image, _timeout_ms); - - // process the new image - _processor->process(image, _ledColors); - setColors(_ledColors, _timeout_ms); + emit systemImage(image); } void V4L2Wrapper::readError(const char* err) @@ -79,21 +64,9 @@ void V4L2Wrapper::readError(const char* err) stop(); } -void V4L2Wrapper::checkSources() -{ - if ( _hyperion->isCurrentPriority(_priority)) - { - _grabber.start(); - } - else - { - _grabber.stop(); - } -} - void V4L2Wrapper::action() { - checkSources(); + } void V4L2Wrapper::setSignalDetectionEnable(bool enable) diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp index 2756f055..f4bc62d0 100755 --- a/libsrc/grabber/x11/X11Grabber.cpp +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -1,17 +1,15 @@ #include #include -X11Grabber::X11Grabber(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) +X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) : Grabber("X11GRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) - , _useXGetImage(useXGetImage) , _x11Display(nullptr) , _pixmap(None) , _srcFormat(nullptr) , _dstFormat(nullptr) , _srcPicture(None) , _dstPicture(None) - , _horizontalDecimation(horizontalPixelDecimation) - , _verticalDecimation(verticalPixelDecimation) + , _pixelDecimation(pixelDecimation) , _screenWidth(0) , _screenHeight(0) , _src_x(cropLeft) @@ -37,13 +35,13 @@ void X11Grabber::freeResources() { // Cleanup allocated resources of the X11 grab XDestroyImage(_xImage); - if(_XShmAvailable && !_useXGetImage) + if(_XShmAvailable) { XShmDetach(_x11Display, &_shminfo); shmdt(_shminfo.shmaddr); shmctl(_shminfo.shmid, IPC_RMID, 0); } - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { XRenderFreePicture(_x11Display, _srcPicture); XRenderFreePicture(_x11Display, _dstPicture); @@ -53,7 +51,7 @@ void X11Grabber::freeResources() void X11Grabber::setupResources() { - if(_XShmAvailable && !_useXGetImage) + if(_XShmAvailable) { _xImage = XShmCreateImage(_x11Display, _windowAttr.visual, _windowAttr.depth, ZPixmap, NULL, &_shminfo, _width, _height); _shminfo.shmid = shmget(IPC_PRIVATE, _xImage->bytes_per_line * _xImage->height, IPC_CREAT|0777); @@ -62,7 +60,7 @@ void X11Grabber::setupResources() _shminfo.readOnly = False; XShmAttach(_x11Display, &_shminfo); } - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { if(_XShmPixmapAvailable) { @@ -96,19 +94,19 @@ bool X11Grabber::Setup() } return false; } - + _window = DefaultRootWindow(_x11Display); int dummy, pixmaps_supported; - + _XRenderAvailable = XRenderQueryExtension(_x11Display, &dummy, &dummy); _XShmAvailable = XShmQueryExtension(_x11Display); XShmQueryVersion(_x11Display, &dummy, &dummy, &pixmaps_supported); _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display) == ZPixmap; - + // Image scaling is performed by XRender when available, otherwise by ImageResampler - _imageResampler.setHorizontalPixelDecimation(_XRenderAvailable ? 1 : _horizontalDecimation); - _imageResampler.setVerticalPixelDecimation(_XRenderAvailable ? 1 : _verticalDecimation); + _imageResampler.setHorizontalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); + _imageResampler.setVerticalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); bool result = (updateScreenDimensions(true) >=0); ErrorIf(!result, _log, "X11 Grabber start failed"); @@ -119,14 +117,15 @@ int X11Grabber::grabFrame(Image & image, bool forceUpdate) { if (!_enabled) return 0; - updateScreenDimensions(forceUpdate); - - if (_XRenderAvailable && !_useXGetImage) + if (forceUpdate) + updateScreenDimensions(forceUpdate); + + if (_XRenderAvailable) { - double scale_x = static_cast(_windowAttr.width / _horizontalDecimation) / static_cast(_windowAttr.width); - double scale_y = static_cast(_windowAttr.height / _verticalDecimation) / static_cast(_windowAttr.height); + double scale_x = static_cast(_windowAttr.width / _pixelDecimation) / static_cast(_windowAttr.width); + double scale_y = static_cast(_windowAttr.height / _pixelDecimation) / static_cast(_windowAttr.height); double scale = qMin(scale_y, scale_x); - + _transform = { { @@ -147,27 +146,27 @@ int X11Grabber::grabFrame(Image & image, bool forceUpdate) } } }; - + XRenderSetPictureTransform (_x11Display, _srcPicture, &_transform); - + // display, op, src, mask, dest, src_x = cropLeft, - // src_y = cropTop, mask_x, mask_y, dest_x, dest_y, width, height + // src_y = cropTop, mask_x, mask_y, dest_x, dest_y, width, height XRenderComposite( - _x11Display, PictOpSrc, _srcPicture, None, _dstPicture, ( _src_x/_horizontalDecimation), - (_src_y/_verticalDecimation), 0, 0, 0, 0, _width, _height); - + _x11Display, PictOpSrc, _srcPicture, None, _dstPicture, ( _src_x/_pixelDecimation), + (_src_y/_pixelDecimation), 0, 0, 0, 0, _width, _height); + XSync(_x11Display, False); - + if (_XShmAvailable) { XShmGetImage(_x11Display, _pixmap, _xImage, 0, 0, AllPlanes); } else { - _xImage = XGetImage(_x11Display, _pixmap, 0, 0, _width, _height, AllPlanes, ZPixmap); + _xImage = XGetImage(_x11Display, _pixmap, 0, 0, _width, _height, AllPlanes, ZPixmap); } } - else if (_XShmAvailable && !_useXGetImage) + else if (_XShmAvailable) { // use xshm XShmGetImage(_x11Display, _window, _xImage, _src_x, _src_y, AllPlanes); @@ -212,20 +211,20 @@ int X11Grabber::updateScreenDimensions(bool force) Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, _windowAttr.width, _windowAttr.height); _screenWidth = _windowAttr.width; _screenHeight = _windowAttr.height; - + int width=0, height=0; - + // Image scaling is performed by XRender when available, otherwise by ImageResampler - if (_XRenderAvailable && !_useXGetImage) + if (_XRenderAvailable) { width = (_screenWidth > unsigned(_cropLeft + _cropRight)) - ? ((_screenWidth - _cropLeft - _cropRight) / _horizontalDecimation) - : _screenWidth / _horizontalDecimation; - + ? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) + : _screenWidth / _pixelDecimation; + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) - ? ((_screenHeight - _cropTop - _cropBottom) / _verticalDecimation) - : _screenHeight / _verticalDecimation; - + ? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) + : _screenHeight / _pixelDecimation; + Info(_log, "Using XRender for grabbing"); } else @@ -233,11 +232,11 @@ int X11Grabber::updateScreenDimensions(bool force) width = (_screenWidth > unsigned(_cropLeft + _cropRight)) ? (_screenWidth - _cropLeft - _cropRight) : _screenWidth; - + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) ? (_screenHeight - _cropTop - _cropBottom) : _screenHeight; - + Info(_log, "Using XGetImage for grabbing"); } @@ -264,7 +263,7 @@ int X11Grabber::updateScreenDimensions(bool force) _src_y = _cropTop; break; } - + Info(_log, "Update output image resolution: [%dx%d] to [%dx%d]", _image.width(), _image.height(), _width, _height); _image.resize(_width, _height); @@ -275,7 +274,26 @@ int X11Grabber::updateScreenDimensions(bool force) void X11Grabber::setVideoMode(VideoMode mode) { - Info(_log, "a %d", mode); Grabber::setVideoMode(mode); updateScreenDimensions(true); } + +void X11Grabber::setWidthHeight(int width, int height) +{ + // empty overwrite +} + +void X11Grabber::setPixelDecimation(int pixelDecimation) +{ + if(_pixelDecimation != pixelDecimation) + { + _pixelDecimation = pixelDecimation; + updateScreenDimensions(true); + } +} + +void X11Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) +{ + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + if(_x11Display != nullptr) updateScreenDimensions(true); // segfault on init +} diff --git a/libsrc/grabber/x11/X11Wrapper.cpp b/libsrc/grabber/x11/X11Wrapper.cpp index 75f5f117..ee445ea2 100644 --- a/libsrc/grabber/x11/X11Wrapper.cpp +++ b/libsrc/grabber/x11/X11Wrapper.cpp @@ -1,8 +1,8 @@ #include -X11Wrapper::X11Wrapper(bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation, const unsigned updateRate_Hz, const int priority) - : GrabberWrapper("X11", &_grabber, 0, 0, updateRate_Hz, priority, hyperion::COMP_GRABBER) - , _grabber(useXGetImage, cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation) +X11Wrapper::X11Wrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz) + : GrabberWrapper("X11", &_grabber, 0, 0, updateRate_Hz) + , _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) , _init(false) {} diff --git a/libsrc/hyperion/CaptureCont.cpp b/libsrc/hyperion/CaptureCont.cpp new file mode 100644 index 00000000..cc9a2a1f --- /dev/null +++ b/libsrc/hyperion/CaptureCont.cpp @@ -0,0 +1,106 @@ +#include + +#include + +CaptureCont::CaptureCont(Hyperion* hyperion) + : QObject() + , _hyperion(hyperion) + , _systemCaptEnabled(false) + , _v4lCaptEnabled(false) +{ + // settings changes + connect(_hyperion, &Hyperion::settingsChanged, this, &CaptureCont::handleSettingsUpdate); + + // comp changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &CaptureCont::componentStateChanged); + + // init + handleSettingsUpdate(settings::INSTCAPTURE, _hyperion->getSetting(settings::INSTCAPTURE)); +} + +CaptureCont::~CaptureCont() +{ + +} + +void CaptureCont::handleV4lImage(const Image & image) +{ + _hyperion->setInputImage(_v4lCaptPrio, image); +} + +void CaptureCont::handleSystemImage(const Image& image) +{ + _hyperion->setInputImage(_systemCaptPrio, image); +} + + +void CaptureCont::setSystemCaptureEnable(const bool& enable) +{ + if(_systemCaptEnabled != enable) + { + if(enable) + { + _hyperion->registerInput(_systemCaptPrio, hyperion::COMP_GRABBER, "System", "DoNotKnow"); + connect(_hyperion, &Hyperion::systemImage, this, &CaptureCont::handleSystemImage); + } + else + { + disconnect(_hyperion, &Hyperion::systemImage, this, &CaptureCont::handleSystemImage); + _hyperion->clear(_systemCaptPrio); + } + _systemCaptEnabled = enable; + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, enable); + } +} + +void CaptureCont::setV4LCaptureEnable(const bool& enable) +{ + if(_v4lCaptEnabled != enable) + { + if(enable) + { + _hyperion->registerInput(_v4lCaptPrio, hyperion::COMP_V4L, "System", "DoNotKnow"); + connect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); + } + else + { + disconnect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); + _hyperion->clear(_v4lCaptPrio); + } + _v4lCaptEnabled = enable; + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, enable); + } +} + +void CaptureCont::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::INSTCAPTURE) + { + const QJsonObject& obj = config.object(); + if(_v4lCaptPrio != obj["v4lPriority"].toInt(240)) + { + setV4LCaptureEnable(false); // clear prio + _v4lCaptPrio = obj["v4lPriority"].toInt(240); + } + if(_systemCaptPrio != obj["systemPriority"].toInt(250)) + { + setSystemCaptureEnable(false); // clear prio + _systemCaptPrio = obj["systemPriority"].toInt(250); + } + + setV4LCaptureEnable(obj["v4lEnable"].toBool(true)); + setSystemCaptureEnable(obj["systemEnable"].toBool(true)); + } +} + +void CaptureCont::componentStateChanged(const hyperion::Components component, bool enable) +{ + if(component == hyperion::COMP_GRABBER) + { + setSystemCaptureEnable(enable); + } + else if(component == hyperion::COMP_V4L) + { + setV4LCaptureEnable(enable); + } +} diff --git a/libsrc/hyperion/ComponentRegister.cpp b/libsrc/hyperion/ComponentRegister.cpp index 41f52c75..baa573d7 100644 --- a/libsrc/hyperion/ComponentRegister.cpp +++ b/libsrc/hyperion/ComponentRegister.cpp @@ -1,27 +1,72 @@ #include #include -ComponentRegister::ComponentRegister() - : _log(Logger::getInstance("ComponentRegister")) +#include + +using namespace hyperion; + +ComponentRegister::ComponentRegister(Hyperion* hyperion) + : _hyperion(hyperion) + , _log(Logger::getInstance("ComponentRegister")) { + // init all comps to false + QVector vect; + vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_UDPLISTENER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE; + for(auto e : vect) + { + _componentStates.emplace(e, ((e == COMP_ALL) ? true : false)); + } } ComponentRegister::~ComponentRegister() { } +bool ComponentRegister::setHyperionEnable(const bool& state) +{ + if(!state && _prevComponentStates.empty()) + { + Debug(_log,"Disable Hyperion, store current component states"); + for(const auto comp : _componentStates) + { + // save state + _prevComponentStates.emplace(comp.first, comp.second); + // disable if enabled + if(comp.second) + _hyperion->setComponentState(comp.first, false); + } + componentStateChanged(COMP_ALL, false); + return true; + } + else if(state && !_prevComponentStates.empty()) + { + Debug(_log,"Enable Hyperion, recover previous component states"); + for(const auto comp : _prevComponentStates) + { + // if comp was enabled, enable again + if(comp.second) + _hyperion->setComponentState(comp.first, true); + + } + _prevComponentStates.clear(); + componentStateChanged(COMP_ALL, true); + return true; + } + return false; +} + +bool ComponentRegister::isComponentEnabled(const hyperion::Components& comp) const +{ + return _componentStates.at(comp); +} void ComponentRegister::componentStateChanged(const hyperion::Components comp, const bool activated) { - Info(_log, "%s: %s", componentToString(comp), (activated? "activated" : "off")); - _componentStates.emplace(comp,activated); - _componentStates[comp] = activated; - -/* for(auto comp : _componentStates) + if(_componentStates[comp] != activated) { - std::cout << hyperion::componentToIdString(comp.first) << " " << comp.second << std::endl; + Debug( _log, "%s: %s", componentToString(comp), (activated? "enabled" : "disabled")); + _componentStates[comp] = activated; + // emit component has changed state + emit updatedComponentState(comp, activated); } - std::cout << "\n"; - */ } - diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index bf0edd1b..9811f79f 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -13,7 +13,6 @@ Grabber::Grabber(QString grabberName, int width, int height, int cropLeft, int c , _cropBottom(0) , _enabled(true) , _log(Logger::getInstance(grabberName)) - { setVideoMode(VIDEO_2D); setCropping(cropLeft, cropRight, cropTop, cropBottom); @@ -44,7 +43,7 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo { if (cropLeft + cropRight >= (unsigned)_width || cropTop + cropBottom >= (unsigned)_height) { - Error(_log, "Rejecting invalid crop values: left: %d, right: %d, top: %d, bottom: %d", cropLeft, cropRight, cropTop, cropBottom); + Error(_log, "Rejecting invalid crop values: left: %d, right: %d, top: %d, bottom: %d, higher than height/width %d/%d", cropLeft, cropRight, cropTop, cropBottom, _height, _width); return; } } @@ -68,3 +67,19 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo Info(_log, "Cropping image: width=%d height=%d; crop: left=%d right=%d top=%d bottom=%d ", _width, _height, cropLeft, cropRight, cropTop, cropBottom); } } + +void Grabber::setWidthHeight(int width, int height) +{ + // eval changes with crop + if (width>0 && height>0) + { + if (_cropLeft + _cropRight >= width || _cropTop + _cropBottom >= height) + { + Error(_log, "Rejecting invalid width/height values as it collides with image cropping: width: %d, height: %d", width, height); + return; + } + _width = width; + _height = height; + } + +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 9af13779..08e77fa7 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -1,111 +1,51 @@ // Hyperion includes -#include -#include #include #include #include -GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz, const int priority, hyperion::Components grabberComponentId) +//forwarder +#include + +// qt +#include + +GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz) : _grabberName(grabberName) , _hyperion(Hyperion::getInstance()) - , _priority(priority) - , _timer() + , _timer(new QTimer(this)) , _updateInterval_ms(1000/updateRate_Hz) - , _timeout_ms(2 * _updateInterval_ms) , _log(Logger::getInstance(grabberName)) , _forward(true) - , _processor(ImageProcessorFactory::getInstance().newImageProcessor()) - , _grabberComponentId(grabberComponentId) , _ggrabber(ggrabber) , _image(0,0) - , _ledColors(Hyperion::getInstance()->getLedCount(), ColorRgb{0,0,0}) - , _imageProcessorEnabled(true) { - _timer.setSingleShot(false); // Configure the timer to generate events every n milliseconds - _timer.setInterval(_updateInterval_ms); + _timer->setInterval(_updateInterval_ms); _image.resize(width, height); - _processor->setSize(width, height); _forward = _hyperion->getForwarder()->protoForwardingEnabled(); - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BLACKBORDER, _processor->blackBorderDetectorEnabled()); - qRegisterMetaType("hyperion::Components"); - - connect(_hyperion, SIGNAL(imageToLedsMappingChanged(int)), _processor, SLOT(setLedMappingType(int))); - connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); - connect(_hyperion, SIGNAL(videoMode(VideoMode)), this, SLOT(setVideoMode(VideoMode))); - connect(this, SIGNAL(emitImage(int, const Image&, const int)), _hyperion, SLOT(setImage(int, const Image&, const int)) ); - connect(&_timer, SIGNAL(timeout()), this, SLOT(actionWrapper())); + connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action); } GrabberWrapper::~GrabberWrapper() { stop(); Debug(_log,"Close grabber: %s", QSTRING_CSTR(_grabberName)); - delete _processor; } bool GrabberWrapper::start() { // Start the timer with the pre configured interval - _timer.start(); - _hyperion->registerPriority(_grabberName, _priority); - return _timer.isActive(); - + _timer->start(); + return _timer->isActive(); } void GrabberWrapper::stop() { // Stop the timer, effectivly stopping the process - _timer.stop(); - _hyperion->unRegisterPriority(_grabberName); -} - -void GrabberWrapper::actionWrapper() -{ - _ggrabber->setEnabled(_hyperion->isCurrentPriority(_priority)); - action(); -} - -void GrabberWrapper::componentStateChanged(const hyperion::Components component, bool enable) -{ - if (component == _grabberComponentId) - { - if (_timer.isActive() != enable) - { - if (enable) start(); - else stop(); - - _forward = _hyperion->getForwarder()->protoForwardingEnabled(); - - if ( enable == _timer.isActive() ) - { - Info(_log, "grabber change state to %s", (_timer.isActive() ? "enabled" : "disabled") ); - } - else - { - WarningIf( enable, _log, "enable grabber failed"); - } - } - _hyperion->getComponentRegister().componentStateChanged(component, _timer.isActive()); - } - - if (component == hyperion::COMP_BLACKBORDER) - { - if (_processor->blackBorderDetectorEnabled() != enable) - { - _processor->enableBlackBorderDetector(enable); - Info(_log, "bb detector change state to %s", (_processor->blackBorderDetectorEnabled() ? "enabled" : "disabled") ); - } - _hyperion->getComponentRegister().componentStateChanged(component, _processor->blackBorderDetectorEnabled()); - } -} - -void GrabberWrapper::setColors(const std::vector &ledColors, const int timeout_ms) -{ - _hyperion->setColors(_priority, ledColors, timeout_ms, true, _grabberComponentId); + _timer->stop(); } QStringList GrabberWrapper::availableGrabbers() @@ -140,7 +80,7 @@ QStringList GrabberWrapper::availableGrabbers() } -void GrabberWrapper::setVideoMode(const VideoMode mode) +void GrabberWrapper::setVideoMode(const VideoMode& mode) { if (_ggrabber != nullptr) { @@ -154,7 +94,77 @@ void GrabberWrapper::setCropping(unsigned cropLeft, unsigned cropRight, unsigned _ggrabber->setCropping(cropLeft, cropRight, cropTop, cropBottom); } -void GrabberWrapper::setImageProcessorEnabled(bool enable) +void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - _imageProcessorEnabled = enable; + if(type == settings::V4L2 || type == settings::SYSTEMCAPTURE) + { + // extract settings + QJsonObject obj; + if(config.isArray() && !config.isEmpty()) + obj = config.array().at(0).toObject(); + else + obj = config.object(); + + if(type == settings::SYSTEMCAPTURE) + { + // width/height + _ggrabber->setWidthHeight(obj["width"].toInt(96), obj["height"].toInt(96)); + + // display index for MAC + _ggrabber->setDisplayIndex(obj["display"].toInt(0)); + + // device path for Framebuffer + _ggrabber->setDevicePath(obj["device"].toString("/dev/fb0")); + + // pixel decimation for x11 + _ggrabber->setPixelDecimation(obj["pixelDecimation"].toInt(8)); + + // crop for system capture + _ggrabber->setCropping( + obj["cropLeft"].toInt(0), + obj["cropRight"].toInt(0), + obj["cropTop"].toInt(0), + obj["cropBottom"].toInt(0)); + + // eval new update timer (not for v4l) + if(_updateInterval_ms != 1000/obj["frequency_Hz"].toInt(10)) + { + _updateInterval_ms = 1000/obj["frequency_Hz"].toInt(10); + const bool& timerWasActive = _timer->isActive(); + _timer->stop(); + _timer->setInterval(_updateInterval_ms); + if(timerWasActive) + _timer->start(); + } + } + + if(type == settings::V4L2) + { + // pixel decimation for v4l + _ggrabber->setPixelDecimation(obj["sizeDecimation"].toInt(8)); + + // crop for v4l + _ggrabber->setCropping( + obj["cropLeft"].toInt(0), + obj["cropRight"].toInt(0), + obj["cropTop"].toInt(0), + obj["cropBottom"].toInt(0)); + + _ggrabber->setSignalDetectionEnable(obj["signalDetection"].toBool(true)); + _ggrabber->setSignalDetectionOffset( + obj["sDHOffsetMin"].toDouble(0.25), + obj["sDVOffsetMin"].toDouble(0.25), + obj["sDHOffsetMax"].toDouble(0.75), + obj["sDVOffsetMax"].toDouble(0.75)); + _ggrabber->setSignalThreshold( + obj["redSignalThreshold"].toDouble(0.0)/100.0, + obj["greenSignalThreshold"].toDouble(0.0)/100.0, + obj["blueSignalThreshold"].toDouble(0.0)/100.0); + _ggrabber->setInputVideoStandard( + obj["input"].toInt(0), + parseVideoStandard(obj["standard"].toString("no-change"))); + + } + } + } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index c626b7db..669994a1 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -11,35 +11,49 @@ #include #include #include +#include #include #include #include // hyperion include #include -#include +#include #include #include +// utils +#include + // Leddevice includes #include #include -#include "MultiColorAdjustment.h" +#include #include "LinearColorSmoothing.h" // effect engine includes #include -#define CORE_LOGGER Logger::getInstance("Core") +// Hyperion Daemon +#include <../src/hyperiond/hyperiond.h> + +// settingsManagaer +#include + +// BGEffectHandler +#include + +// CaptureControl (Daemon capture) +#include Hyperion* Hyperion::_hyperion = nullptr; -Hyperion* Hyperion::initInstance(const QJsonObject& qjsonConfig, const QString configFile, const QString rootPath) +Hyperion* Hyperion::initInstance( HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath) { if ( Hyperion::_hyperion != nullptr ) throw std::runtime_error("Hyperion::initInstance can be called only one time"); - Hyperion::_hyperion = new Hyperion(qjsonConfig, configFile, rootPath); + Hyperion::_hyperion = new Hyperion(daemon, instance, configFile, rootPath); return Hyperion::_hyperion; } @@ -52,400 +66,74 @@ Hyperion* Hyperion::getInstance() return Hyperion::_hyperion; } -ColorOrder Hyperion::createColorOrder(const QJsonObject &deviceConfig) -{ - return stringToColorOrder(deviceConfig["colorOrder"].toString("rgb")); -} - -ColorAdjustment * Hyperion::createColorAdjustment(const QJsonObject & adjustmentConfig) -{ - const QString id = adjustmentConfig["id"].toString("default"); - - RgbChannelAdjustment * blackAdjustment = createRgbChannelAdjustment(adjustmentConfig, "black" , 0, 0, 0); - RgbChannelAdjustment * whiteAdjustment = createRgbChannelAdjustment(adjustmentConfig, "white" , 255,255,255); - RgbChannelAdjustment * redAdjustment = createRgbChannelAdjustment(adjustmentConfig, "red" , 255, 0, 0); - RgbChannelAdjustment * greenAdjustment = createRgbChannelAdjustment(adjustmentConfig, "green" , 0,255, 0); - RgbChannelAdjustment * blueAdjustment = createRgbChannelAdjustment(adjustmentConfig, "blue" , 0, 0,255); - RgbChannelAdjustment * cyanAdjustment = createRgbChannelAdjustment(adjustmentConfig, "cyan" , 0,255,255); - RgbChannelAdjustment * magentaAdjustment = createRgbChannelAdjustment(adjustmentConfig, "magenta", 255, 0,255); - RgbChannelAdjustment * yellowAdjustment = createRgbChannelAdjustment(adjustmentConfig, "yellow" , 255,255, 0); - RgbTransform * rgbTransform = createRgbTransform(adjustmentConfig); - - ColorAdjustment * adjustment = new ColorAdjustment(); - adjustment->_id = id; - adjustment->_rgbBlackAdjustment = *blackAdjustment; - adjustment->_rgbWhiteAdjustment = *whiteAdjustment; - adjustment->_rgbRedAdjustment = *redAdjustment; - adjustment->_rgbGreenAdjustment = *greenAdjustment; - adjustment->_rgbBlueAdjustment = *blueAdjustment; - adjustment->_rgbCyanAdjustment = *cyanAdjustment; - adjustment->_rgbMagentaAdjustment = *magentaAdjustment; - adjustment->_rgbYellowAdjustment = *yellowAdjustment; - adjustment->_rgbTransform = *rgbTransform; - - // Cleanup the allocated individual adjustments - delete blackAdjustment; - delete whiteAdjustment; - delete redAdjustment; - delete greenAdjustment; - delete blueAdjustment; - delete cyanAdjustment; - delete magentaAdjustment; - delete yellowAdjustment; - delete rgbTransform; - - return adjustment; -} - - -MultiColorAdjustment * Hyperion::createLedColorsAdjustment(const unsigned ledCnt, const QJsonObject & colorConfig) -{ - // Create the result, the transforms are added to this - MultiColorAdjustment * adjustment = new MultiColorAdjustment(ledCnt); - - const QJsonValue adjustmentConfig = colorConfig["channelAdjustment"]; - const QRegExp overallExp("([0-9]+(\\-[0-9]+)?)(,[ ]*([0-9]+(\\-[0-9]+)?))*"); - - const QJsonArray & adjustmentConfigArray = adjustmentConfig.toArray(); - for (signed i = 0; i < adjustmentConfigArray.size(); ++i) - { - const QJsonObject & config = adjustmentConfigArray.at(i).toObject(); - ColorAdjustment * colorAdjustment = createColorAdjustment(config); - adjustment->addAdjustment(colorAdjustment); - - const QString ledIndicesStr = config["leds"].toString("").trimmed(); - if (ledIndicesStr.compare("*") == 0) - { - // Special case for indices '*' => all leds - adjustment->setAdjustmentForLed(colorAdjustment->_id, 0, ledCnt-1); - Info(CORE_LOGGER, "ColorAdjustment '%s' => [0; %d]", QSTRING_CSTR(colorAdjustment->_id), ledCnt-1); - continue; - } - - if (!overallExp.exactMatch(ledIndicesStr)) - { - Error(CORE_LOGGER, "Given led indices %d not correct format: %s", i, QSTRING_CSTR(ledIndicesStr)); - continue; - } - - std::stringstream ss; - const QStringList ledIndexList = ledIndicesStr.split(","); - for (int i=0; i 0) - { - ss << ", "; - } - if (ledIndexList[i].contains("-")) - { - QStringList ledIndices = ledIndexList[i].split("-"); - int startInd = ledIndices[0].toInt(); - int endInd = ledIndices[1].toInt(); - - adjustment->setAdjustmentForLed(colorAdjustment->_id, startInd, endInd); - ss << startInd << "-" << endInd; - } - else - { - int index = ledIndexList[i].toInt(); - adjustment->setAdjustmentForLed(colorAdjustment->_id, index, index); - ss << index; - } - } - Info(CORE_LOGGER, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); - } - - return adjustment; -} - -RgbTransform* Hyperion::createRgbTransform(const QJsonObject& colorConfig) -{ - const double backlightThreshold = colorConfig["backlightThreshold"].toDouble(0.0); - const bool backlightColored = colorConfig["backlightColored"].toBool(false); - const double brightness = colorConfig["brightness"].toInt(100); - const double brightnessComp= colorConfig["brightnessCompensation"].toInt(100); - const double gammaR = colorConfig["gammaRed"].toDouble(1.0); - const double gammaG = colorConfig["gammaGreen"].toDouble(1.0); - const double gammaB = colorConfig["gammaBlue"].toDouble(1.0); - - RgbTransform* transform = new RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, brightness, brightnessComp); - return transform; -} - -RgbChannelAdjustment* Hyperion::createRgbChannelAdjustment(const QJsonObject& colorConfig, const QString channelName, const int defaultR, const int defaultG, const int defaultB) -{ - const QJsonArray& channelConfig = colorConfig[channelName].toArray(); - RgbChannelAdjustment* adjustment = new RgbChannelAdjustment( - channelConfig[0].toInt(defaultR), - channelConfig[1].toInt(defaultG), - channelConfig[2].toInt(defaultB), - "ChannelAdjust_"+channelName.toUpper()); - return adjustment; -} - -LedString Hyperion::createLedString(const QJsonValue& ledsConfig, const ColorOrder deviceOrder) -{ - LedString ledString; - const QString deviceOrderStr = colorOrderToString(deviceOrder); - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - int maxLedId = ledConfigArray.size(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - Led led; - led.index = index["index"].toInt(); - led.clone = index["clone"].toInt(-1); - if ( led.clone < -1 || led.clone >= maxLedId ) - { - Warning(CORE_LOGGER, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); - led.clone = -1; - } - - if ( led.clone < 0 ) - { - const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); - const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); - led.minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); - led.maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); - led.minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); - led.maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); - // Fix if the user swapped min and max - if (led.minX_frac > led.maxX_frac) - { - std::swap(led.minX_frac, led.maxX_frac); - } - if (led.minY_frac > led.maxY_frac) - { - std::swap(led.minY_frac, led.maxY_frac); - } - - // Get the order of the rgb channels for this led (default is device order) - led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); - ledString.leds().push_back(led); - } - } - - // Make sure the leds are sorted (on their indices) - std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); - return ledString; -} - -LedString Hyperion::createLedStringClone(const QJsonValue& ledsConfig, const ColorOrder deviceOrder) -{ - LedString ledString; - const QString deviceOrderStr = colorOrderToString(deviceOrder); - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - int maxLedId = ledConfigArray.size(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - Led led; - led.index = index["index"].toInt(); - led.clone = index["clone"].toInt(-1); - if ( led.clone < -1 || led.clone >= maxLedId ) - { - Warning(CORE_LOGGER, "LED %d: clone index of %d is out of range, clone ignored", led.index, led.clone); - led.clone = -1; - } - - if ( led.clone >= 0 ) - { - Debug(CORE_LOGGER, "LED %d: clone from led %d", led.index, led.clone); - led.minX_frac = 0; - led.maxX_frac = 0; - led.minY_frac = 0; - led.maxY_frac = 0; - // Get the order of the rgb channels for this led (default is device order) - led.colorOrder = stringToColorOrder(index["colorOrder"].toString(deviceOrderStr)); - - ledString.leds().push_back(led); - } - - } - - // Make sure the leds are sorted (on their indices) - std::sort(ledString.leds().begin(), ledString.leds().end(), [](const Led& lhs, const Led& rhs){ return lhs.index < rhs.index; }); - return ledString; -} - -QSize Hyperion::getLedLayoutGridSize(const QJsonValue& ledsConfig) -{ - std::vector midPointsX; - std::vector midPointsY; - const QJsonArray & ledConfigArray = ledsConfig.toArray(); - - for (signed i = 0; i < ledConfigArray.size(); ++i) - { - const QJsonObject& index = ledConfigArray[i].toObject(); - - if (index["clone"].toInt(-1) < 0 ) - { - const QJsonObject& hscanConfig = ledConfigArray[i].toObject()["hscan"].toObject(); - const QJsonObject& vscanConfig = ledConfigArray[i].toObject()["vscan"].toObject(); - double minX_frac = qMax(0.0, qMin(1.0, hscanConfig["minimum"].toDouble())); - double maxX_frac = qMax(0.0, qMin(1.0, hscanConfig["maximum"].toDouble())); - double minY_frac = qMax(0.0, qMin(1.0, vscanConfig["minimum"].toDouble())); - double maxY_frac = qMax(0.0, qMin(1.0, vscanConfig["maximum"].toDouble())); - // Fix if the user swapped min and max - if (minX_frac > maxX_frac) - { - std::swap(minX_frac, maxX_frac); - } - if (minY_frac > maxY_frac) - { - std::swap(minY_frac, maxY_frac); - } - - // calculate mid point and make grid calculation - midPointsX.push_back( int(1000.0*(minX_frac + maxX_frac) / 2.0) ); - midPointsY.push_back( int(1000.0*(minY_frac + maxY_frac) / 2.0) ); - } - } - - // remove duplicates - std::sort(midPointsX.begin(), midPointsX.end()); - midPointsX.erase(std::unique(midPointsX.begin(), midPointsX.end()), midPointsX.end()); - std::sort(midPointsY.begin(), midPointsY.end()); - midPointsY.erase(std::unique(midPointsY.begin(), midPointsY.end()), midPointsY.end()); - - QSize gridSize( midPointsX.size(), midPointsY.size() ); - Debug(CORE_LOGGER, "led layout grid: %dx%d", gridSize.width(), gridSize.height()); - - return gridSize; -} - - - -LinearColorSmoothing * Hyperion::createColorSmoothing(const QJsonObject & smoothingConfig, LedDevice* leddevice){ - QString type = smoothingConfig["type"].toString("linear").toLower(); - LinearColorSmoothing * device = nullptr; - type = "linear"; // TODO currently hardcoded type, delete it if we have more types - - if (type == "linear") - { - Info( CORE_LOGGER, "Creating linear smoothing"); - device = new LinearColorSmoothing( - leddevice, - smoothingConfig["updateFrequency"].toDouble(25.0), - smoothingConfig["time_ms"].toInt(200), - smoothingConfig["updateDelay"].toInt(0), - smoothingConfig["continuousOutput"].toBool(true) - ); - } - else - { - Error(CORE_LOGGER, "Smoothing disabled, because of unknown type '%s'.", QSTRING_CSTR(type)); - } - - device->setEnable(smoothingConfig["enable"].toBool(true)); - InfoIf(!device->enabled(), CORE_LOGGER,"Smoothing disabled"); - - Q_ASSERT(device != nullptr); - return device; -} - -MessageForwarder * Hyperion::createMessageForwarder(const QJsonObject & forwarderConfig) -{ - MessageForwarder * forwarder = new MessageForwarder(); - if ( !forwarderConfig.isEmpty() && forwarderConfig["enable"].toBool(true) ) - { - if ( !forwarderConfig["json"].isNull() && forwarderConfig["json"].isArray() ) - { - const QJsonArray & addr = forwarderConfig["json"].toArray(); - for (signed i = 0; i < addr.size(); ++i) - { - Info(CORE_LOGGER, "Json forward to %s", addr.at(i).toString().toStdString().c_str()); - forwarder->addJsonSlave(addr[i].toString()); - } - } - - if ( !forwarderConfig["proto"].isNull() && forwarderConfig["proto"].isArray() ) - { - const QJsonArray & addr = forwarderConfig["proto"].toArray(); - for (signed i = 0; i < addr.size(); ++i) - { - Info(CORE_LOGGER, "Proto forward to %s", addr.at(i).toString().toStdString().c_str()); - forwarder->addProtoSlave(addr[i].toString()); - } - } - } - - return forwarder; -} - MessageForwarder * Hyperion::getForwarder() { return _messageForwarder; } -Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile, const QString rootPath) - : _ledString(createLedString(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) - , _ledStringClone(createLedStringClone(qjsonConfig["leds"], createColorOrder(qjsonConfig["device"].toObject()))) +Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath) + : _daemon(daemon) + , _settingsManager(new SettingsManager(this, instance, configFile)) + , _componentRegister(this) + , _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) + , _ledStringClone(hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) + , _imageProcessor(new ImageProcessor(_ledString, this)) , _muxer(_ledString.leds().size()) - , _raw2ledAdjustment(createLedColorsAdjustment(_ledString.leds().size(), qjsonConfig["color"].toObject())) + , _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object())) , _effectEngine(nullptr) - , _messageForwarder(createMessageForwarder(qjsonConfig["forwarder"].toObject())) - , _qjsonConfig(qjsonConfig) + , _messageForwarder(new MessageForwarder(this, getSetting(settings::NETFORWARD))) , _configFile(configFile) , _rootPath(rootPath) - , _timer() - , _timerBonjourResolver() - , _log(CORE_LOGGER) - , _hwLedCount(_ledString.leds().size()) - , _sourceAutoSelectEnabled(true) + , _log(Logger::getInstance("HYPERION")) + , _hwLedCount() , _configHash() - , _ledGridSize(getLedLayoutGridSize(qjsonConfig["leds"])) + , _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array())) , _prevCompId(hyperion::COMP_INVALID) - , _bonjourBrowser(this) - , _bonjourResolver(this) - , _videoMode(VIDEO_2D) + , _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK) { - if (!_raw2ledAdjustment->verifyAdjustments()) { - throw std::runtime_error("Color adjustment incorrectly set"); + Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!"); } + + // handle hwLedCount + _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // init colororder vector + for (Led& led : _ledString.leds()) + { + _ledStringColorOrder.push_back(led.colorOrder); + } + for (Led& led : _ledStringClone.leds()) + { + _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + } + + // connect Hyperion::update with Muxer visible priority changes as muxer updates independent + connect(&_muxer, &PriorityMuxer::visiblePriorityChanged, this, &Hyperion::update); + + // listens for ComponentRegister changes of COMP_ALL to perform core enable/disable actions + connect(&_componentRegister, &ComponentRegister::updatedComponentState, this, &Hyperion::updatedComponentState); + + // listen for settings updates of this instance (LEDS & COLOR) + connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate); + // set color correction activity state - const QJsonObject& color = qjsonConfig["color"].toObject(); - - _bonjourBrowser.browseForServiceType(QLatin1String("_hyperiond-http._tcp")); - connect(&_bonjourBrowser, SIGNAL(currentBonjourRecordsChanged(const QList&)),this, SLOT(currentBonjourRecordsChanged(const QList &))); - connect(&_bonjourResolver, SIGNAL(bonjourRecordResolved(const QHostInfo &, int)), this, SLOT(bonjourRecordResolved(const QHostInfo &, int))); - - // initialize the image processor factory - _ledMAppingType = ImageProcessor::mappingTypeToInt(color["imageToLedMappingType"].toString()); - ImageProcessorFactory::getInstance().init(_ledString, qjsonConfig["blackborderdetector"].toObject(),_ledMAppingType ); - - getComponentRegister().componentStateChanged(hyperion::COMP_FORWARDER, _messageForwarder->forwardingEnabled()); + const QJsonObject color = getSetting(settings::COLOR).object(); // initialize leddevices - _device = LedDeviceFactory::construct(qjsonConfig["device"].toObject(),_hwLedCount); - _deviceSmooth = createColorSmoothing(qjsonConfig["smoothing"].toObject(), _device); - getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, _deviceSmooth->componentState()); + const QJsonObject ledDevice = getSetting(settings::DEVICE).object(); + ledDevice["currentLedCount"] = int(_hwLedCount); // Inject led count info + + _device = LedDeviceFactory::construct(ledDevice); + _deviceSmooth = new LinearColorSmoothing(_device, getSetting(settings::SMOOTHING), this); + connect(this, &Hyperion::settingsChanged, _deviceSmooth, &LinearColorSmoothing::handleSettingsUpdate); + getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); - _deviceSmooth->addConfig(true); // add pause to config 1 - - // setup the timer - _timer.setSingleShot(true); - QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(update())); - - _timerBonjourResolver.setSingleShot(false); - _timerBonjourResolver.setInterval(1000); - QObject::connect(&_timerBonjourResolver, SIGNAL(timeout()), this, SLOT(bonjourResolve())); - _timerBonjourResolver.start(); - - // create the effect engine, must be initialized after smoothing! - _effectEngine = new EffectEngine(this,qjsonConfig["effects"].toObject()); - - const QJsonObject& device = qjsonConfig["device"].toObject(); - unsigned int hwLedCount = device["ledCount"].toInt(getLedCount()); - _hwLedCount = qMax(hwLedCount, getLedCount()); - Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount); - WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section"); + // create the effect engine and pipe the updateEmit; must be initialized after smoothing! + _effectEngine = new EffectEngine(this,getSetting(settings::EFFECTS).object()); + connect(_effectEngine, &EffectEngine::effectListUpdated, this, &Hyperion::effectListUpdated); // setup config state checks and initial shot checkConfigState(); @@ -455,31 +143,27 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile, con } else { + _cTimer = new QTimer(this); Warning(_log,"Filesystem Observer failed for file: %s, use fallback timer", _configFile.toStdString().c_str()); - QObject::connect(&_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); - _cTimer.start(2000); + connect(_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); + _cTimer->start(2000); } - // pipe muxer signal for effect/color timerunner to hyperionStateChanged slot - QObject::connect(&_muxer, &PriorityMuxer::timerunner, this, &Hyperion::hyperionStateChanged); + // initial startup effect + hyperion::handleInitialEffect(this, getSetting(settings::FGEFFECT).object()); + // handle background effect + _BGEffectHandler = new BGEffectHandler(this); - // prepare processing of hyperionStateChanged for forced serverinfo - connect(&_fsi_timer, SIGNAL(timeout()), this, SLOT(hyperionStateChanged())); - _fsi_timer.setSingleShot(true); - _fsi_blockTimer.setSingleShot(true); + // create the Daemon capture interface + _captureCont = new CaptureCont(this); - // initialize the leds + // if there is no startup / background eff and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a prioritiy change) update(); } -int Hyperion::getLatchTime() const +Hyperion::~Hyperion() { - return _device->getLatchTime(); -} - -unsigned Hyperion::addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) -{ - return _deviceSmooth->addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); + freeObjects(false); } void Hyperion::freeObjects(bool emitCloseSignal) @@ -493,17 +177,138 @@ void Hyperion::freeObjects(bool emitCloseSignal) emit closing(); } - // delete components on exit of hyperion core + delete _captureCont; delete _effectEngine; + //delete _deviceSmooth; delete _device; delete _raw2ledAdjustment; delete _messageForwarder; + delete _settingsManager; } -Hyperion::~Hyperion() +void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - freeObjects(false); + if(type == settings::COLOR) + { + const QJsonObject obj = config.object(); + // change in color recreate ledAdjustments + delete _raw2ledAdjustment; + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), obj); + + if (!_raw2ledAdjustment->verifyAdjustments()) + { + Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!"); + } + } + else if(type == settings::LEDS) + { + const QJsonArray leds = config.array(); + + // lock update() + _lockUpdate = true; + // stop and cache all running effects, as effects depend heavily on ledlayout + _effectEngine->cacheRunningEffects(); + + // ledstring, clone, img processor, muxer, ledGridSize (eff engine image based effects), _ledBuffer and ByteOrder of ledstring + _ledString = hyperion::createLedString(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object())); + _ledStringClone = hyperion::createLedStringClone(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object())); + _imageProcessor->setLedString(_ledString); + _muxer.updateLedColorsLength(_ledString.leds().size()); + _ledGridSize = hyperion::getLedLayoutGridSize(leds); + + std::vector color(_ledString.leds().size(), ColorRgb{0,0,0}); + _ledBuffer = color; + + _ledStringColorOrder.clear(); + for (Led& led : _ledString.leds()) + { + _ledStringColorOrder.push_back(led.colorOrder); + } + for (Led& led : _ledStringClone.leds()) + { + _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + } + + // handle hwLedCount update + _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // update led count in device + _device->setLedCount(_hwLedCount); + + // change in leds are also reflected in adjustment + delete _raw2ledAdjustment; + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()); + + // start cached effects + _effectEngine->startCachedEffects(); + + // unlock + _lockUpdate = false; + } + else if(type == settings::DEVICE) + { + _lockUpdate = true; + const QJsonObject dev = config.object(); + + // handle hwLedCount update + _hwLedCount = qMax(unsigned(dev["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + + // force ledString update, if device ByteOrder changed + if(_device->getColorOrder() != dev["colorOrder"].toString("rgb")) + { + _ledString = hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(dev)); + _ledStringClone = hyperion::createLedStringClone(getSetting(settings::LEDS).array(), hyperion::createColorOrder(dev)); + _imageProcessor->setLedString(_ledString); + } + + /* // reinit led device type on change + if(_device->getActiveDevice() != dev["type"].toString("file").toLower()) + { + } + // update led count + _device->setLedCount(_hwLedCount); + */ + // do always reinit + // TODO segfaulting in LinearColorSmoothing::queueColor triggert from QTimer because of device->setLEdValues (results from gdb debugging and testing) + bool wasEnabled = _deviceSmooth->enabled(); + _deviceSmooth->stopTimer(); + delete _device; + dev["currentLedCount"] = int(_hwLedCount); // Inject led count info + _device = LedDeviceFactory::construct(dev); + getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); + if(wasEnabled) + _deviceSmooth->startTimerDelayed(); + _lockUpdate = false; + } + // update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer + update(); +} + +QJsonDocument Hyperion::getSetting(const settings::type& type) +{ + return _settingsManager->getSetting(type); +} + +bool Hyperion::saveSettings(QJsonObject config, const bool& correct) +{ + return _settingsManager->saveSettings(config, correct); +} + +QString Hyperion::getConfigFileName() const +{ + QFileInfo cF(_configFile); + return cF.fileName(); +} + +int Hyperion::getLatchTime() const +{ + return _device->getLatchTime(); +} + +unsigned Hyperion::addSmoothingConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) +{ + return _deviceSmooth->addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); } unsigned Hyperion::getLedCount() const @@ -511,53 +316,6 @@ unsigned Hyperion::getLedCount() const return _ledString.leds().size(); } -void Hyperion::currentBonjourRecordsChanged(const QList &list) -{ - _hyperionSessions.clear(); - for ( auto rec : list ) - { - _hyperionSessions.insert(rec.serviceName, rec); - } -} - -void Hyperion::bonjourRecordResolved(const QHostInfo &hostInfo, int port) -{ - if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve)) - { - QString host = hostInfo.hostName(); - QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain; - if (host.endsWith("."+domain)) - { - host.remove(host.length()-domain.length()-1,domain.length()+1); - } - _hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host; - _hyperionSessions[_bonjourCurrentServiceToResolve].port = port; - _hyperionSessions[_bonjourCurrentServiceToResolve].address = hostInfo.addresses().isEmpty() ? "" : hostInfo.addresses().first().toString(); - Debug(_log, "found hyperion session: %s:%d",QSTRING_CSTR(hostInfo.hostName()), port); - - //emit change - emit hyperionStateChanged(); - } -} - -void Hyperion::bonjourResolve() -{ - for(auto key : _hyperionSessions.keys()) - { - if (_hyperionSessions[key].port < 0) - { - _bonjourCurrentServiceToResolve = key; - _bonjourResolver.resolveBonjourRecord(_hyperionSessions[key]); - break; - } - } -} - -Hyperion::BonjourRegister Hyperion::getHyperionSessions() -{ - return _hyperionSessions; -} - void Hyperion::checkConfigState(QString cfile) { // Check config modifications @@ -578,7 +336,6 @@ void Hyperion::checkConfigState(QString cfile) if(_prevConfigMod != _configMod) { - emit hyperionStateChanged(); _prevConfigMod = _configMod; } @@ -589,67 +346,30 @@ void Hyperion::checkConfigState(QString cfile) if(_prevConfigWrite != _configWrite) { - emit hyperionStateChanged(); _prevConfigWrite = _configWrite; } } -void Hyperion::registerPriority(const QString &name, const int priority/*, const QString &origin*/) -{ - Info(_log, "Register new input source named '%s' for priority channel '%d'", QSTRING_CSTR(name), priority ); - - for(auto key : _priorityRegister.keys()) - { - WarningIf( ( key != name && _priorityRegister.value(key) == priority), _log, - "Input source '%s' uses same priority channel (%d) as '%s'.", QSTRING_CSTR(name), priority, QSTRING_CSTR(key)); - } - - _priorityRegister.insert(name, priority); - emit hyperionStateChanged(); -} - -void Hyperion::unRegisterPriority(const QString &name) -{ - Info(_log, "Unregister input source named '%s' from priority register", QSTRING_CSTR(name)); - _priorityRegister.remove(name); - emit hyperionStateChanged(); -} - void Hyperion::setSourceAutoSelectEnabled(bool enabled) { - _sourceAutoSelectEnabled = enabled; - if (! _sourceAutoSelectEnabled) - { - setCurrentSourcePriority(_muxer.getCurrentPriority()); - } - update(); - DebugIf( !_sourceAutoSelectEnabled, _log, "source auto select is disabled"); - InfoIf(_sourceAutoSelectEnabled, _log, "set current input source to auto select"); + if(_muxer.setSourceAutoSelectEnabled(enabled)) + update(); } bool Hyperion::setCurrentSourcePriority(int priority ) { - bool priorityValid = _muxer.hasPriority(priority); - if (priorityValid) - { - DebugIf(_sourceAutoSelectEnabled, _log, "source auto select is disabled"); - _sourceAutoSelectEnabled = false; - _currentSourcePriority = priority; - Info(_log, "set current input source to priority channel %d", _currentSourcePriority); - } + return _muxer.setPriority(priority); +} - return priorityValid; +bool Hyperion::sourceAutoSelectEnabled() +{ + return _muxer.isSourceAutoSelectEnabled(); } void Hyperion::setComponentState(const hyperion::Components component, const bool state) { switch (component) { - case hyperion::COMP_SMOOTHING: - _deviceSmooth->setEnable(state); - getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, _deviceSmooth->componentState()); - break; - case hyperion::COMP_LEDDEVICE: _device->setEnable(state); getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState()); @@ -660,45 +380,61 @@ void Hyperion::setComponentState(const hyperion::Components component, const boo } } -void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, bool clearEffects) +void Hyperion::registerInput(const int priority, const hyperion::Components& component, const QString& origin, const QString& owner, unsigned smooth_cfg) { - // create led output + _muxer.registerInput(priority, component, origin, owner, smooth_cfg); +} + +const bool Hyperion::setInput(const int priority, const std::vector& ledColors, int timeout_ms, const bool& clearEffect) +{ + if(_muxer.setInput(priority, ledColors, timeout_ms)) + { + // clear effect if this call does not come from an effect + if(clearEffect) + _effectEngine->channelCleared(priority); + + // if this priority is visible, update immediately + if(priority == _muxer.getCurrentPriority()) + update(); + + return true; + } + return false; +} + +const bool Hyperion::setInputImage(const int priority, const Image& image, int64_t timeout_ms, const bool& clearEffect) +{ + if(_muxer.setInputImage(priority, image, timeout_ms)) + { + // clear effect if this call does not come from an effect + if(clearEffect) + _effectEngine->channelCleared(priority); + + // if this priority is visible, update immediately + if(priority == _muxer.getCurrentPriority()) + { + update(); + } + + return true; + } + return false; +} + +void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, const QString& origin, bool clearEffects) +{ + // clear effect if this call does not come from an effect + if(clearEffects) + _effectEngine->channelCleared(priority); + + // create led vector from single color std::vector ledColors(_ledString.leds().size(), color); - // set colors - setColors(priority, ledColors, timeout_ms, clearEffects, hyperion::COMP_COLOR); -} + // register color + registerInput(priority, hyperion::COMP_COLOR, origin); -void Hyperion::setColors(int priority, const std::vector& ledColors, const int timeout_ms, bool clearEffects, hyperion::Components component, const QString origin, unsigned smoothCfg) -{ - // clear effects if this call does not come from an effect - if (clearEffects) - { - _effectEngine->channelCleared(priority); - } - - if (timeout_ms > 0) - { - const uint64_t timeoutTime = QDateTime::currentMSecsSinceEpoch() + timeout_ms; - _muxer.setInput(priority, ledColors, timeoutTime, component, origin, smoothCfg); - } - else - { - _muxer.setInput(priority, ledColors, -1, component, origin, smoothCfg); - } - - if (! _sourceAutoSelectEnabled || priority == _muxer.getCurrentPriority()) - { - update(); - } -} - -void Hyperion::setImage(int priority, const Image & image, int duration_ms) -{ - if (priority == getCurrentPriority()) - { - emit emitImage(priority, image, duration_ms); - } + // write color to muxer + setInput(priority, ledColors, timeout_ms); } const QStringList & Hyperion::getAdjustmentIds() const @@ -713,38 +449,25 @@ ColorAdjustment * Hyperion::getAdjustment(const QString& id) void Hyperion::adjustmentsUpdated() { + emit adjustmentChanged(); update(); } -void Hyperion::clear(int priority) +const bool Hyperion::clear(int priority) { - if (_muxer.hasPriority(priority)) - { - _muxer.clearInput(priority); - if (!_sourceAutoSelectEnabled && _currentSourcePriority == priority ) - { - setSourceAutoSelectEnabled(true); - } - - // update leds if necessary - if (priority < _muxer.getCurrentPriority()) - { - update(); - } - } - // send clear signal to the effect engine // (outside the check so the effect gets cleared even when the effect is not sending colors) _effectEngine->channelCleared(priority); + + if(_muxer.clearInput(priority)) + return true; + + return false; } void Hyperion::clearall(bool forceClearAll) { _muxer.clearAll(forceClearAll); - setSourceAutoSelectEnabled(true); - - // update leds - update(); // send clearall signal to the effect engine _effectEngine->allChannelsCleared(); @@ -752,8 +475,7 @@ void Hyperion::clearall(bool forceClearAll) int Hyperion::getCurrentPriority() const { - - return _sourceAutoSelectEnabled || !_muxer.hasPriority(_currentSourcePriority) ? _muxer.getCurrentPriority() : _currentSourcePriority; + return _muxer.getCurrentPriority(); } bool Hyperion::isCurrentPriority(const int priority) const @@ -766,7 +488,7 @@ QList Hyperion::getActivePriorities() const return _muxer.getPriorities(); } -const Hyperion::InputInfo &Hyperion::getPriorityInfo(const int priority) const +const Hyperion::InputInfo Hyperion::getPriorityInfo(const int priority) const { return _muxer.getInputInfo(priority); } @@ -791,6 +513,11 @@ const std::list & Hyperion::getEffectSchemas() return _effectEngine->getEffectSchemas(); } +const QJsonObject& Hyperion::getQJsonConfig() +{ + return _settingsManager->getSettings(); +} + int Hyperion::setEffect(const QString &effectName, int priority, int timeout, const QString & origin) { return _effectEngine->runEffect(effectName, priority, timeout, origin); @@ -801,68 +528,96 @@ int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin); } -void Hyperion::setLedMappingType(int mappingType) +void Hyperion::setLedMappingType(const int& mappingType) { - _ledMAppingType = mappingType; - emit imageToLedsMappingChanged(mappingType); + if(mappingType != _imageProcessor->getUserLedMappingType()) + { + _imageProcessor->setLedMappingType(mappingType); + emit imageToLedsMappingChanged(mappingType); + } } -void Hyperion::setVideoMode(VideoMode mode) +const int & Hyperion::getLedMappingType() +{ + return _imageProcessor->getUserLedMappingType(); +} + +void Hyperion::setVideoMode(const VideoMode& mode) { - _videoMode = mode; emit videoMode(mode); } - -void Hyperion::hyperionStateChanged() +const VideoMode & Hyperion::getCurrentVideoMode() { - if(_fsi_blockTimer.isActive()) + return _daemon->getVideoMode(); +} + +const QString & Hyperion::getActiveDevice() +{ + return _device->getActiveDevice(); +} + +void Hyperion::updatedComponentState(const hyperion::Components comp, const bool state) +{ + if(comp == hyperion::COMP_ALL) { - _fsi_timer.start(300); - } - else - { - emit sendServerInfo(); - _fsi_blockTimer.start(250); + if(state) + { + // first muxer to update all inputs + _muxer.setEnable(state); + } + else + { + _muxer.setEnable(state); + } } } void Hyperion::update() { - // Update the muxer, cleaning obsolete priorities - _muxer.setCurrentTime(QDateTime::currentMSecsSinceEpoch()); + if(_lockUpdate) + return; + + // the ledbuffer resize for hwledcount needs to be reverted + if(_hwLedCount > _ledBuffer.size()) + _ledBuffer.resize(getLedCount()); // Obtain the current priority channel - int priority = _sourceAutoSelectEnabled || !_muxer.hasPriority(_currentSourcePriority) ? _muxer.getCurrentPriority() : _currentSourcePriority; - const PriorityMuxer::InputInfo & priorityInfo = _muxer.getInputInfo(priority); - - // copy ledcolors to local buffer - _ledBuffer.reserve(_hwLedCount); - _ledBuffer = priorityInfo.ledColors; - - // emit rawLedColors before transform - emit rawLedColors(_ledBuffer); + int priority = _muxer.getCurrentPriority(); + const PriorityMuxer::InputInfo priorityInfo = _muxer.getInputInfo(priority); + // eval comp change + bool compChanged = false; if (priorityInfo.componentId != _prevCompId) { - bool backlightEnabled = (priorityInfo.componentId != hyperion::COMP_COLOR && priorityInfo.componentId != hyperion::COMP_EFFECT); - _raw2ledAdjustment->setBacklightEnabled(backlightEnabled); + compChanged = true; _prevCompId = priorityInfo.componentId; } - _raw2ledAdjustment->applyAdjustment(_ledBuffer); - // init colororder vector, if empty - if (_ledStringColorOrder.empty()) + // copy image & process OR copy ledColors from muxer + Image image = priorityInfo.image; + if(image.size() > 3) { - for (Led& led : _ledString.leds()) + emit currentImage(image); + // disable the black border detector for effects and ledmapping to 0 + if(compChanged) { - _ledStringColorOrder.push_back(led.colorOrder); - } - for (Led& led : _ledStringClone.leds()) - { - _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); + _imageProcessor->setBlackbarDetectDisable((_prevCompId == hyperion::COMP_EFFECT || _prevCompId == hyperion::COMP_GRABBER)); + _imageProcessor->setHardLedMappingType((_prevCompId == hyperion::COMP_EFFECT) ? 0 : -1); } + _imageProcessor->process(image, _ledBuffer); } + else + { + _ledBuffer = priorityInfo.ledColors; + } + // copy rawLedColors before adjustments + _rawLedBuffer = _ledBuffer; + + // apply adjustments + if(compChanged) + _raw2ledAdjustment->setBacklightEnabled((_prevCompId != hyperion::COMP_COLOR && _prevCompId != hyperion::COMP_EFFECT)); + _raw2ledAdjustment->applyAdjustment(_ledBuffer); // insert cloned leds into buffer for (Led& led : _ledStringClone.leds()) @@ -873,8 +628,6 @@ void Hyperion::update() int i = 0; for (ColorRgb& color : _ledBuffer) { - //const ColorOrder ledColorOrder = leds.at(i).colorOrder; - // correct the color byte order switch (_ledStringColorOrder.at(i)) { @@ -902,7 +655,7 @@ void Hyperion::update() } i++; } - + // fill aditional hw leds with black if ( _hwLedCount > _ledBuffer.size() ) { _ledBuffer.resize(_hwLedCount, ColorRgb::BLACK); @@ -920,17 +673,4 @@ void Hyperion::update() if (! _deviceSmooth->enabled()) _device->setLedValues(_ledBuffer); } - - // Start the timeout-timer - if (priorityInfo.timeoutTime_ms <= 0) - { - _timer.stop(); - } - else - { - int timeout_ms = qMax(0, int(priorityInfo.timeoutTime_ms - QDateTime::currentMSecsSinceEpoch())); - // qMin() 200ms forced refresh if color is active to update priorityMuxer properly for forced serverinfo push - _timer.start(qMin(timeout_ms, 200)); - } - } diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index 1bebddc5..426328f8 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -9,27 +9,56 @@ using namespace hyperion; -ImageProcessor::ImageProcessor(const LedString& ledString, const QJsonObject & blackborderConfig) - : QObject() +// global transform method +int ImageProcessor::mappingTypeToInt(QString mappingType) +{ + if (mappingType == "unicolor_mean" ) + return 1; + + return 0; +} +// global transform method +QString ImageProcessor::mappingTypeToStr(int mappingType) +{ + if (mappingType == 1 ) + return "unicolor_mean"; + + return "multicolor_mean"; +} + +ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion) + : QObject(hyperion) , _log(Logger::getInstance("BLACKBORDER")) , _ledString(ledString) - , _borderProcessor(new BlackBorderProcessor(blackborderConfig) ) + , _borderProcessor(new BlackBorderProcessor(hyperion, this)) , _imageToLeds(nullptr) , _mappingType(0) + , _userMappingType(0) + , _hardMappingType(0) + , _hyperion(hyperion) { -// this is when we want to change the mapping for all input sources -// connect(Hyperion::getInstance(), SIGNAL(imageToLedsMappingChanged(int)), this, SLOT(setLedMappingType(int))); + // init + handleSettingsUpdate(settings::COLOR, _hyperion->getSetting(settings::COLOR)); + // listen for changes in color - ledmapping + connect(_hyperion, &Hyperion::settingsChanged, this, &ImageProcessor::handleSettingsUpdate); } ImageProcessor::~ImageProcessor() { delete _imageToLeds; - delete _borderProcessor; } -unsigned ImageProcessor::getLedCount() const +void ImageProcessor::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - return _ledString.leds().size(); + if(type == settings::COLOR) + { + const QJsonObject& obj = config.object(); + int newType = mappingTypeToInt(obj["imageToLedMappingType"].toString()); + if(_userMappingType != newType) + { + setLedMappingType(newType); + } + } } void ImageProcessor::setSize(const unsigned width, const unsigned height) @@ -47,9 +76,24 @@ void ImageProcessor::setSize(const unsigned width, const unsigned height) _imageToLeds = (width>0 && height>0) ? (new ImageToLedsMap(width, height, 0, 0, _ledString.leds())) : nullptr; } -void ImageProcessor::enableBlackBorderDetector(bool enable) +void ImageProcessor::setLedString(const LedString& ledString) { - _borderProcessor->setEnabled(enable); + _ledString = ledString; + + // get current width/height + const unsigned width = _imageToLeds->width(); + const unsigned height = _imageToLeds->height(); + + // Clean up the old buffer and mapping + delete _imageToLeds; + + // Construct a new buffer and mapping + _imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds()); +} + +void ImageProcessor::setBlackbarDetectDisable(bool enable) +{ + _borderProcessor->setHardDisable(enable); } bool ImageProcessor::blackBorderDetectorEnabled() @@ -59,29 +103,23 @@ bool ImageProcessor::blackBorderDetectorEnabled() void ImageProcessor::setLedMappingType(int mapType) { - Debug(_log, "set led mapping to type %d", mapType); - _mappingType = mapType; + // if the _hardMappingType is >-1 we aren't allowed to overwrite it + _userMappingType = mapType; + Debug(_log, "set user led mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType))); + if(_hardMappingType == -1) + { + _mappingType = mapType; + } } -int ImageProcessor::ledMappingType() +void ImageProcessor::setHardLedMappingType(int mapType) { - return _mappingType; -} - -int ImageProcessor::mappingTypeToInt(QString mappingType) -{ - if (mappingType == "unicolor_mean" ) - return 1; - - return 0; -} - -QString ImageProcessor::mappingTypeToStr(int mappingType) -{ - if (mappingType == 1 ) - return "unicolor_mean"; - - return "multicolor_mean"; + // force the maptype, if set to -1 we use the last requested _userMappingType + _hardMappingType = mapType; + if(mapType == -1) + _mappingType = _userMappingType; + else + _mappingType = mapType; } bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &hscanEnd, double &vscanBegin, double &vscanEnd) const @@ -97,4 +135,3 @@ bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &h return false; } - diff --git a/libsrc/hyperion/ImageProcessorFactory.cpp b/libsrc/hyperion/ImageProcessorFactory.cpp deleted file mode 100644 index 7b7cfbbe..00000000 --- a/libsrc/hyperion/ImageProcessorFactory.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Hyperion includes -#include -#include - -ImageProcessorFactory& ImageProcessorFactory::getInstance() -{ - static ImageProcessorFactory instance; - // Return the singleton instance - return instance; -} - -void ImageProcessorFactory::init(const LedString& ledString, const QJsonObject & blackborderConfig, int mappingType) -{ - _ledString = ledString; - _blackborderConfig = blackborderConfig; - _mappingType = mappingType; -} - -ImageProcessor* ImageProcessorFactory::newImageProcessor() const -{ - ImageProcessor* ip = new ImageProcessor(_ledString, _blackborderConfig); - ip->setLedMappingType(_mappingType); - - return ip; -} diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index b4404be9..c243de17 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -1,5 +1,6 @@ // Qt includes #include +#include #include "LinearColorSmoothing.h" #include @@ -8,38 +9,60 @@ using namespace hyperion; -// ledUpdateFrequency_hz = 0 > cause divide by zero! -LinearColorSmoothing::LinearColorSmoothing( LedDevice * ledDevice, double ledUpdateFrequency_hz, int settlingTime_ms, unsigned updateDelay, bool continuousOutput) +LinearColorSmoothing::LinearColorSmoothing( LedDevice * ledDevice, const QJsonDocument& config, Hyperion* hyperion) : LedDevice() , _ledDevice(ledDevice) - , _updateInterval(1000 / ledUpdateFrequency_hz) - , _settlingTime(settlingTime_ms) - , _timer() - , _outputDelay(updateDelay) + , _log(Logger::getInstance("SMOOTHING")) + , _hyperion(hyperion) + , _updateInterval(1000) + , _settlingTime(200) + , _timer(new QTimer(this)) + , _outputDelay(0) , _writeToLedsEnable(true) - , _continuousOutput(continuousOutput) + , _continuousOutput(false) , _pause(false) , _currentConfigId(0) { - _log = Logger::getInstance("Smoothing"); - _timer.setSingleShot(false); - _timer.setInterval(_updateInterval); + Debug(_log, "Instance created"); + + // set initial state to true, as LedDevice::enabled() is true by default + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, true); + + // init cfg 0 (default) + _cfgList.append({false, 200, 25, 0}); + handleSettingsUpdate(settings::SMOOTHING, config); - selectConfig( addConfig(_settlingTime, ledUpdateFrequency_hz, updateDelay) ); - // add pause on cfg 1 - SMOOTHING_CFG cfg = {true, 100, 50, 0}; + SMOOTHING_CFG cfg = {true}; _cfgList.append(cfg); - Info( _log, "smoothing cfg %d: pause", _cfgList.count()-1); - connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); + // listen for comp changes + connect(_hyperion, &Hyperion::componentStateChanged, this, &LinearColorSmoothing::componentStateChange); + // timer + connect(_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); } LinearColorSmoothing::~LinearColorSmoothing() { // Make sure to switch off the underlying led-device (because switchOff is no longer forwarded) _ledDevice->switchOff(); - delete _ledDevice; +} + +void LinearColorSmoothing::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::SMOOTHING) + { + QJsonObject obj = config.object(); + _continuousOutput = obj["continuousOutput"].toBool(true); + SMOOTHING_CFG cfg = {false, obj["time_ms"].toInt(200), unsigned(1000.0/obj["updateFrequency"].toDouble(25.0)), unsigned(obj["updateDelay"].toInt(0))}; + _cfgList[0] = cfg; + // if current id is 0, we need to apply the settings (forced) + if(!_currentConfigId) + selectConfig(0, true); + + if(enabled() != obj["enable"].toBool(true)) + setEnable(obj["enable"].toBool(true)); + } } int LinearColorSmoothing::write(const std::vector &ledValues) @@ -53,7 +76,7 @@ int LinearColorSmoothing::write(const std::vector &ledValues) _previousTime = QDateTime::currentMSecsSinceEpoch(); _previousValues = ledValues; - _timer.start(); + _timer->start(); } else { @@ -150,6 +173,11 @@ void LinearColorSmoothing::queueColors(const std::vector & ledColors) } } +void LinearColorSmoothing::componentStateChange(const hyperion::Components component, const bool state) +{ + if(component == hyperion::COMP_SMOOTHING) + setEnable(state); +} void LinearColorSmoothing::setEnable(bool enable) { @@ -157,9 +185,11 @@ void LinearColorSmoothing::setEnable(bool enable) if (!enable) { - _timer.stop(); + _timer->stop(); _previousValues.clear(); } + // update comp register + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_SMOOTHING, enable); } void LinearColorSmoothing::setPause(bool pause) @@ -171,14 +201,14 @@ unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFr { SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay}; _cfgList.append(cfg); - - Info( _log, "smoothing cfg %d: interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _cfgList.count()-1, cfg.updateInterval, cfg.settlingTime, cfg.outputDelay ); + + //Debug( _log, "smoothing cfg %d: interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _cfgList.count()-1, cfg.updateInterval, cfg.settlingTime, cfg.outputDelay ); return _cfgList.count() - 1; } -bool LinearColorSmoothing::selectConfig(unsigned cfg) +bool LinearColorSmoothing::selectConfig(unsigned cfg, const bool& force) { - if (_currentConfigId == cfg) + if (_currentConfigId == cfg && !force) { return true; } @@ -191,20 +221,34 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg) if (_cfgList[cfg].updateInterval != _updateInterval) { - _timer.stop(); + _timer->stop(); _updateInterval = _cfgList[cfg].updateInterval; - _timer.setInterval(_updateInterval); - _timer.start(); + _timer->setInterval(_updateInterval); + _timer->start(); } _currentConfigId = cfg; - InfoIf( enabled() && !_pause, _log, "set smoothing cfg: %d, interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _currentConfigId, _updateInterval, _settlingTime, _outputDelay ); + //DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %d, interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _currentConfigId, _updateInterval, _settlingTime, _outputDelay ); InfoIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); return true; } - + // reset to default _currentConfigId = 0; return false; } - + +void LinearColorSmoothing::startTimerDelayed() +{ + QTimer::singleShot(500, this, SLOT(delayStartTimer())); +} + +void LinearColorSmoothing::stopTimer() +{ + _timer->stop(); +} + +void LinearColorSmoothing::delayStartTimer() +{ + _timer->start(); +} diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/libsrc/hyperion/LinearColorSmoothing.h index 34aff620..97bb846c 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/libsrc/hyperion/LinearColorSmoothing.h @@ -5,13 +5,19 @@ // Qt includes -#include #include // hyperion incluse #include #include +// settings +#include + +class QTimer; +class Logger; +class Hyperion; + /// Linear Smooting class /// /// This class processes the requested led values and forwards them to the device after applying @@ -23,10 +29,10 @@ class LinearColorSmoothing : public LedDevice public: /// Constructor /// @param LedDevice the led device - /// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz) - /// @param settingTime The time after which the updated led values have been fully applied (sec) - /// @param updateDelay The number of frames to delay outgoing led updates - LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime, unsigned updateDelay, bool continuousOutput); + /// @param config The configuration document smoothing + /// @param hyperion The hyperion parent instance + /// + LinearColorSmoothing(LedDevice *ledDevice, const QJsonDocument& config, Hyperion* hyperion); /// Destructor virtual ~LinearColorSmoothing(); @@ -46,13 +52,53 @@ public: bool pause() { return _pause; } ; bool enabled() { return LedDevice::enabled() && !_pause; }; + /// + /// @brief Add a new smoothing cfg which can be used with selectConfig() + /// @param settlingTime_ms The buffer time + /// @param ledUpdateFrequency_hz The frequency of update + /// @param updateDelay The delay + /// + /// @return The index of the cfg which can be passed to selectConfig() + /// unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); - bool selectConfig(unsigned cfg); + + /// + /// @brief select a smoothing cfg given by cfg index from addConfig() + /// @param cfg The index to use + /// @param force Overwrite in any case the current values (used for cfg 0 settings udpate) + /// + /// @return On success return else false (and falls back to cfg 0) + /// + bool selectConfig(unsigned cfg, const bool& force = false); + + /// + /// @ Helper methods to start the timer with delay (see delayStartSmooth()) + /// + void startTimerDelayed(); + void stopTimer(); + +public slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); private slots: /// Timer callback which writes updated led values to the led device void updateLeds(); + /// Delay timer slot to workaround the leddevice reconstruction segfault (dangling pointer) + void delayStartTimer(); + + /// + /// @brief Handle component state changes + /// @param component The component + /// @param state The requested state + /// + void componentStateChange(const hyperion::Components component, const bool state); + private: /** * Pushes the colors into the output queue and popping the head to the led-device @@ -64,6 +110,12 @@ private: /// The led device LedDevice * _ledDevice; + /// Logger instance + Logger* _log; + + /// Hyperion instance + Hyperion* _hyperion; + /// The interval at which to update the leds (msec) int64_t _updateInterval; @@ -71,7 +123,7 @@ private: int64_t _settlingTime; /// The Qt timer object - QTimer _timer; + QTimer * _timer; /// The timestamp at which the target data should be fully applied int64_t _targetTime; @@ -92,7 +144,7 @@ private: /// Prevent sending data to device when no intput data is sent bool _writeToLedsEnable; - + /// Flag for dis/enable continuous output to led device regardless there is new data or not bool _continuousOutput; @@ -107,8 +159,8 @@ private: unsigned outputDelay; }; - /// config list + /// smooth config list QVector _cfgList; - + unsigned _currentConfigId; }; diff --git a/libsrc/hyperion/MessageForwarder.cpp b/libsrc/hyperion/MessageForwarder.cpp index 9ddb9863..abc0ed12 100644 --- a/libsrc/hyperion/MessageForwarder.cpp +++ b/libsrc/hyperion/MessageForwarder.cpp @@ -3,48 +3,110 @@ #include +#include +#include -MessageForwarder::MessageForwarder() +MessageForwarder::MessageForwarder(Hyperion* hyperion, const QJsonDocument & config) + : QObject() + , _hyperion(hyperion) + , _log(Logger::getInstance("NETFORWARDER")) { + handleSettingsUpdate(settings::NETFORWARD, config); + // get settings updates + connect(_hyperion, &Hyperion::settingsChanged, this, &MessageForwarder::handleSettingsUpdate); } MessageForwarder::~MessageForwarder() { } +void MessageForwarder::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::NETFORWARD) + { + // clear the current targets + _jsonSlaves.clear(); + _protoSlaves.clear(); + // build new one + const QJsonObject &obj = config.object(); + if ( !obj["json"].isNull() ) + { + const QJsonArray & addr = obj["json"].toArray(); + for (const auto& entry : addr) + { + addJsonSlave(entry.toString()); + } + } + + if ( !obj["proto"].isNull() ) + { + const QJsonArray & addr = obj["proto"].toArray(); + for (const auto& entry : addr) + { + addProtoSlave(entry.toString()); + } + } + InfoIf(obj["enable"].toBool(true), _log, "Forward now to json targets '%s' and proto targets '%s'", QSTRING_CSTR(_jsonSlaves.join(", ")), QSTRING_CSTR(_protoSlaves.join(", "))) + // update comp state + _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_FORWARDER, obj["enable"].toBool(true)); + } +} void MessageForwarder::addJsonSlave(QString slave) { QStringList parts = slave.split(":"); if (parts.size() != 2) - throw std::runtime_error(QString("HYPERION (forwarder) ERROR: Wrong address: unable to parse address (%1)").arg(slave).toStdString()); + { + Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); + return; + } bool ok; - quint16 port = parts[1].toUShort(&ok); + parts[1].toUShort(&ok); if (!ok) - throw std::runtime_error(QString("HYPERION (forwarder) ERROR: Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + { + Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); + return; + } - JsonSlaveAddress c; - c.addr = QHostAddress(parts[0]); - c.port = port; - _jsonSlaves << c; + // verify loop with jsonserver + const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object(); + if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) + { + Error(_log, "Loop between JsonServer and Forwarder! (%s)",QSTRING_CSTR(slave)); + return; + } + + _jsonSlaves << slave; } void MessageForwarder::addProtoSlave(QString slave) { + QStringList parts = slave.split(":"); + if (parts.size() != 2) + { + Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); + return; + } + + bool ok; + parts[1].toUShort(&ok); + if (!ok) + { + Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); + return; + } + + // verify loop with protoserver + const QJsonObject& obj = _hyperion->getSetting(settings::PROTOSERVER).object(); + if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) + { + Error(_log, "Loop between ProtoServer and Forwarder! (%s)",QSTRING_CSTR(slave)); + return; + } _protoSlaves << slave; } -QStringList MessageForwarder::getProtoSlaves() -{ - return _protoSlaves; -} - -QList MessageForwarder::getJsonSlaves() -{ - return _jsonSlaves; -} - bool MessageForwarder::protoForwardingEnabled() { return ! _protoSlaves.empty(); diff --git a/libsrc/hyperion/MultiColorAdjustment.cpp b/libsrc/hyperion/MultiColorAdjustment.cpp index 32985dba..1c5ff16c 100644 --- a/libsrc/hyperion/MultiColorAdjustment.cpp +++ b/libsrc/hyperion/MultiColorAdjustment.cpp @@ -1,10 +1,10 @@ // Hyperion includes #include -#include "MultiColorAdjustment.h" +#include MultiColorAdjustment::MultiColorAdjustment(const unsigned ledCnt) : _ledAdjustments(ledCnt, nullptr) - , _log(Logger::getInstance("ColorAdjust")) + , _log(Logger::getInstance("ADJUSTMENT")) { } @@ -23,10 +23,20 @@ void MultiColorAdjustment::addAdjustment(ColorAdjustment * adjustment) _adjustment.push_back(adjustment); } -void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned startLed, const unsigned endLed) +void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned startLed, unsigned endLed) { - Q_ASSERT(startLed <= endLed); - Q_ASSERT(endLed < _ledAdjustments.size()); + // abort + if(startLed >= endLed) + { + Error(_log,"startLed >= endLed -> %d >= %d", startLed, endLed); + return; + } + // catch wrong values + if(endLed > _ledAdjustments.size()) + { + Warning(_log,"The color calibration 'LED index' field has leds specified which aren't part of your led layout"); + endLed = _ledAdjustments.size(); + } // Get the identified adjustment (don't care if is nullptr) ColorAdjustment * adjustment = getAdjustment(id); @@ -38,17 +48,18 @@ void MultiColorAdjustment::setAdjustmentForLed(const QString& id, const unsigned bool MultiColorAdjustment::verifyAdjustments() const { + bool ok = true; for (unsigned iLed=0; iLed<_ledAdjustments.size(); ++iLed) { ColorAdjustment * adjustment = _ledAdjustments[iLed]; if (adjustment == nullptr) { - Error(_log, "No adjustment set for %d", iLed); - return false; + Warning(_log, "No calibration set for led %d", iLed); + ok = false; } } - return true; + return ok; } const QStringList & MultiColorAdjustment::getAdjustmentIds() @@ -96,7 +107,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint8_t ogreen = color.green; uint8_t oblue = color.blue; uint8_t B_RGB, B_CMY, B_W; - + adjustment->_rgbTransform.transform(ored,ogreen,oblue); adjustment->_rgbTransform.getBrightnessComponents(B_RGB, B_CMY, B_W); @@ -104,7 +115,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint32_t rng = (uint32_t) (ored) *(255-ogreen); uint32_t nrg = (uint32_t) (255-ored)*(ogreen); uint32_t rg = (uint32_t) (ored) *(ogreen); - + uint8_t black = nrng*(255-oblue)/65025; uint8_t red = rng *(255-oblue)/65025; uint8_t green = nrg *(255-oblue)/65025; @@ -113,7 +124,7 @@ void MultiColorAdjustment::applyAdjustment(std::vector& ledColors) uint8_t magenta = rng *(oblue) /65025; uint8_t yellow = rg *(255-oblue)/65025; uint8_t white = rg *(oblue) /65025; - + uint8_t OR, OG, OB, RR, RG, RB, GR, GG, GB, BR, BG, BB; uint8_t CR, CG, CB, MR, MG, MB, YR, YG, YB, WR, WG, WB; diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index d8443b92..79ed8ed1 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -1,36 +1,113 @@ // STL includes #include -#include #include +// qt incl +#include +#include + // Hyperion includes #include +// utils +#include + const int PriorityMuxer::LOWEST_PRIORITY = std::numeric_limits::max(); PriorityMuxer::PriorityMuxer(int ledCount) - : _currentPriority(PriorityMuxer::LOWEST_PRIORITY) + : QObject() + , _log(Logger::getInstance("HYPERION")) + , _currentPriority(PriorityMuxer::LOWEST_PRIORITY) + , _manualSelectedPriority(256) , _activeInputs() , _lowestPriorityInfo() + , _sourceAutoSelectEnabled(true) + , _updateTimer(new QTimer(this)) + , _timer(new QTimer(this)) + , _blockTimer(new QTimer(this)) { + // init lowest priority info _lowestPriorityInfo.priority = PriorityMuxer::LOWEST_PRIORITY; - _lowestPriorityInfo.timeoutTime_ms = 0; + _lowestPriorityInfo.timeoutTime_ms = -1; _lowestPriorityInfo.ledColors = std::vector(ledCount, {0, 0, 0}); _lowestPriorityInfo.componentId = hyperion::COMP_COLOR; _lowestPriorityInfo.origin = "System"; + _lowestPriorityInfo.owner = ""; - _activeInputs[_currentPriority] = _lowestPriorityInfo; + _activeInputs[PriorityMuxer::LOWEST_PRIORITY] = _lowestPriorityInfo; - // do a reuqest after blocking timer runs out - connect(&_timer, SIGNAL(timeout()), this, SLOT(emitReq())); - _timer.setSingleShot(true); - _blockTimer.setSingleShot(true); + // adapt to 1s interval for COLOR and EFFECT timeouts > -1 + connect(_timer, &QTimer::timeout, this, &PriorityMuxer::timeTrigger); + _timer->setSingleShot(true); + _blockTimer->setSingleShot(true); + // forward timeRunner signal to prioritiesChanged signal & threading workaround + connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged); + connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger); + + // start muxer timer + connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime); + _updateTimer->setInterval(250); + _updateTimer->start(); + InputInfo ninfo; } PriorityMuxer::~PriorityMuxer() { } +void PriorityMuxer::setEnable(const bool& enable) +{ + enable ? _updateTimer->start() : _updateTimer->stop(); +} + +bool PriorityMuxer::setSourceAutoSelectEnabled(const bool& enable, const bool& update) +{ + if(_sourceAutoSelectEnabled != enable) + { + // on disable we need to make sure the last priority call to setPriority is still valid + if(!enable && !_activeInputs.contains(_manualSelectedPriority)) + { + Warning(_log, "Can't disable auto selection, as the last manual selected priority (%d) is no longer available", _manualSelectedPriority); + return false; + } + + _sourceAutoSelectEnabled = enable; + Debug(_log, "Source auto select is now %s", enable ? "enabled" : "disabled"); + + // update _currentPriority if called from external + if(update) + setCurrentTime(); + + emit autoSelectChanged(enable); + return true; + } + return false; +} + +bool PriorityMuxer::setPriority(const uint8_t priority) +{ + if(_activeInputs.contains(priority)) + { + _manualSelectedPriority = priority; + // update auto select state -> update _currentPriority + setSourceAutoSelectEnabled(false); + return true; + } + return false; +} + +void PriorityMuxer::updateLedColorsLength(const int& ledCount) +{ + for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();) + { + if (infoIt->ledColors.size() >= 1) + { + infoIt->ledColors.resize(ledCount, infoIt->ledColors.at(0)); + } + ++infoIt; + } +} + int PriorityMuxer::getCurrentPriority() const { return _currentPriority; @@ -46,7 +123,7 @@ bool PriorityMuxer::hasPriority(const int priority) const return (priority == PriorityMuxer::LOWEST_PRIORITY) ? true : _activeInputs.contains(priority); } -const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority) const +const PriorityMuxer::InputInfo PriorityMuxer::getInputInfo(const int priority) const { auto elemIt = _activeInputs.find(priority); if (elemIt == _activeInputs.end()) @@ -54,35 +131,127 @@ const PriorityMuxer::InputInfo& PriorityMuxer::getInputInfo(const int priority) elemIt = _activeInputs.find(PriorityMuxer::LOWEST_PRIORITY); if (elemIt == _activeInputs.end()) { - throw std::runtime_error("HYPERION (prioritymuxer) ERROR: no such priority"); + // fallback + return _lowestPriorityInfo; } } return elemIt.value(); } -void PriorityMuxer::setInput(const int priority, const std::vector& ledColors, const int64_t timeoutTime_ms, hyperion::Components component, const QString origin, unsigned smooth_cfg) +void PriorityMuxer::registerInput(const int priority, const hyperion::Components& component, const QString& origin, const QString& owner, unsigned smooth_cfg) { + // detect new registers + bool newInput = false; + if(!_activeInputs.contains(priority)) + newInput = true; + InputInfo& input = _activeInputs[priority]; input.priority = priority; - input.timeoutTime_ms = timeoutTime_ms; - input.ledColors = ledColors; + input.timeoutTime_ms = newInput ? -100 : input.timeoutTime_ms; input.componentId = component; input.origin = origin; input.smooth_cfg = smooth_cfg; - _currentPriority = qMin(_currentPriority, priority); + input.owner = owner; + + if(newInput) + { + Debug(_log,"Register new input '%s/%s' with priority %d as inactive", QSTRING_CSTR(origin), hyperion::componentToIdString(component), priority); + emit priorityChanged(priority, true); + emit prioritiesChanged(); + return; + } } -void PriorityMuxer::clearInput(const int priority) +const bool PriorityMuxer::setInput(const int priority, const std::vector& ledColors, int64_t timeout_ms) { - if (priority < PriorityMuxer::LOWEST_PRIORITY) + if(!_activeInputs.contains(priority)) { - _activeInputs.remove(priority); - if (_currentPriority == priority) - { - QList keys = _activeInputs.keys(); - _currentPriority = *std::min_element(keys.begin(), keys.end()); - } + Error(_log,"setInput() used without registerInput() for priority '%d', probably the priority reached timeout",priority); + return false; } + + // calc final timeout + if(timeout_ms > 0) + timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms; + + InputInfo& input = _activeInputs[priority]; + // detect active <-> inactive changes + bool activeChange = false; + bool active = true; + if(input.timeoutTime_ms == -100 && timeout_ms != -100) + { + activeChange = true; + } + else if(timeout_ms == -100 && input.timeoutTime_ms != -100) + { + active = false; + activeChange = true; + } + // update input + input.timeoutTime_ms = timeout_ms; + input.ledColors = ledColors; + + // emit active change + if(activeChange) + { + Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); + emit activeStateChanged(priority, active); + setCurrentTime(); + } + return true; +} + +const bool PriorityMuxer::setInputImage(const int priority, const Image& image, int64_t timeout_ms) +{ + if(!_activeInputs.contains(priority)) + { + Error(_log,"setInputImage() used without registerInput() for priority '%d', probably the priority reached timeout",priority); + return false; + } + + // calc final timeout + if(timeout_ms > 0) + timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms; + + InputInfo& input = _activeInputs[priority]; + // detect active <-> inactive changes + bool activeChange = false; + bool active = true; + if(input.timeoutTime_ms == -100 && timeout_ms != -100) + { + activeChange = true; + } + else if(timeout_ms == -100 && input.timeoutTime_ms != -100) + { + active = false; + activeChange = true; + } + // update input + input.timeoutTime_ms = timeout_ms; + input.image = image; + + // emit active change + if(activeChange) + { + Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); + emit activeStateChanged(priority, active); + setCurrentTime(); + } + return true; +} + +const bool PriorityMuxer::clearInput(const uint8_t priority) +{ + if (priority < PriorityMuxer::LOWEST_PRIORITY && _activeInputs.remove(priority)) + { + Debug(_log,"Removed source priority %d",priority); + // on clear success update _currentPriority + setCurrentTime(); + emit priorityChanged(priority, false); + emit prioritiesChanged(); + return true; + } + return false; } void PriorityMuxer::clearAll(bool forceClearAll) @@ -97,47 +266,82 @@ void PriorityMuxer::clearAll(bool forceClearAll) { for(auto key : _activeInputs.keys()) { - if (key < PriorityMuxer::LOWEST_PRIORITY-1) + const InputInfo info = getInputInfo(key); + if ((info.componentId == hyperion::COMP_COLOR || info.componentId == hyperion::COMP_EFFECT) && key < PriorityMuxer::LOWEST_PRIORITY-1) { - _activeInputs.remove(key); + clearInput(key); } } } } -void PriorityMuxer::setCurrentTime(const int64_t& now) +void PriorityMuxer::setCurrentTime(void) { - _currentPriority = PriorityMuxer::LOWEST_PRIORITY; + const int64_t now = QDateTime::currentMSecsSinceEpoch(); + int newPriority = PriorityMuxer::LOWEST_PRIORITY; for (auto infoIt = _activeInputs.begin(); infoIt != _activeInputs.end();) { if (infoIt->timeoutTime_ms > 0 && infoIt->timeoutTime_ms <= now) { + quint8 tPrio = infoIt->priority; infoIt = _activeInputs.erase(infoIt); + Debug(_log,"Timeout clear for priority %d",tPrio); + emit priorityChanged(tPrio, false); + emit prioritiesChanged(); } else { - _currentPriority = qMin(_currentPriority, infoIt->priority); - - // call emitReq when effect or color is running with timeout > -1, blacklist prio 255 + // timeoutTime of -100 is awaiting data (inactive); skip + if(infoIt->timeoutTime_ms >= -1) + newPriority = qMin(newPriority, infoIt->priority); + + // call timeTrigger when effect or color is running with timeout > -1, blacklist prio 255 if(infoIt->priority < 254 && infoIt->timeoutTime_ms > -1 && (infoIt->componentId == hyperion::COMP_EFFECT || infoIt->componentId == hyperion::COMP_COLOR)) { - emitReq(); + emit signalTimeTrigger(); // as signal to prevent Threading issues } ++infoIt; } } + // eval if manual selected prio is still available + if(!_sourceAutoSelectEnabled) + { + if(_activeInputs.contains(_manualSelectedPriority)) + { + newPriority = _manualSelectedPriority; + } + else + { + Debug(_log, "The manual selected priority '%d' is no longer available, switching to auto selection", _manualSelectedPriority); + // update state, but no _currentPriority re-eval + setSourceAutoSelectEnabled(true, false); + } + } + // apply & emit on change (after apply!) + bool changed = false; + if(_currentPriority != newPriority) + changed = true; + + _currentPriority = newPriority; + + if(changed) + { + Debug(_log, "Set visible priority to %d", newPriority); + emit visiblePriorityChanged(newPriority); + emit prioritiesChanged(); + } } -void PriorityMuxer::emitReq() +void PriorityMuxer::timeTrigger() { - if(_blockTimer.isActive()) + if(_blockTimer->isActive()) { - _timer.start(500); + _timer->start(500); } else { - emit timerunner(); - _blockTimer.start(1000); + emit timeRunner(); + _blockTimer->start(1000); } } diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp new file mode 100644 index 00000000..3eeec8d3 --- /dev/null +++ b/libsrc/hyperion/SettingsManager.cpp @@ -0,0 +1,175 @@ +// proj +#include + +// util +#include + +// json schema process +#include +#include + +// write config to filesystem +#include + +// hyperion +#include + +QJsonObject SettingsManager::schemaJson; + +SettingsManager::SettingsManager(Hyperion* hyperion, const quint8& instance, const QString& configFile) + : _hyperion(hyperion) + , _log(Logger::getInstance("SettingsManager")) +{ + Q_INIT_RESOURCE(resource); + connect(this, &SettingsManager::settingsChanged, _hyperion, &Hyperion::settingsChanged); + // get schema + if(schemaJson.isEmpty()) + { + try + { + schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); + } + catch(const std::runtime_error& error) + { + throw std::runtime_error(error.what()); + } + } + // get default config + QJsonObject defaultConfig; + if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + throw std::runtime_error("Failed to read default config"); + + Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); + QJsonSchemaChecker schemaCheckerT; + + if(!JsonUtils::readFile(configFile, _qconfig, _log)) + throw std::runtime_error("Failed to load config!"); + + // validate config with schema and correct it if required + QPair validate = schemaCheckerT.validate(_qconfig); + + // errors in schema syntax, abort + if (!validate.second) + { + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); + + throw std::runtime_error("ERROR: Hyperion schema has syntax errors!"); + } + // errors in configuration, correct it! + if (!validate.first) + { + Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); + _qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig); + + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + + if (!JsonUtils::write(configFile, _qconfig, _log)) + throw std::runtime_error("ERROR: Can't save configuration file, aborting"); + } + + Debug(_log,"Settings database initialized") +} + +SettingsManager::SettingsManager(const quint8& instance, const QString& configFile) + : _hyperion(nullptr) + , _log(Logger::getInstance("SettingsManager")) +{ + Q_INIT_RESOURCE(resource); + // get schema + if(schemaJson.isEmpty()) + { + try + { + schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); + } + catch(const std::runtime_error& error) + { + throw std::runtime_error(error.what()); + } + } + // get default config + QJsonObject defaultConfig; + if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + throw std::runtime_error("Failed to read default config"); + + Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); + QJsonSchemaChecker schemaCheckerT; + + if(!JsonUtils::readFile(configFile, _qconfig, _log)) + throw std::runtime_error("Failed to load config!"); + + // validate config with schema and correct it if required + QPair validate = schemaCheckerT.validate(_qconfig); + + // errors in schema syntax, abort + if (!validate.second) + { + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); + + throw std::runtime_error("ERROR: Hyperion schema has syntax errors!"); + } + // errors in configuration, correct it! + if (!validate.first) + { + Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); + _qconfig = schemaCheckerT.getAutoCorrectedConfig(_qconfig); + + foreach (auto & schemaError, schemaCheckerT.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + + if (!JsonUtils::write(configFile, _qconfig, _log)) + throw std::runtime_error("ERROR: Can't save configuration file, aborting"); + } + + Debug(_log,"Settings database initialized") +} + +SettingsManager::~SettingsManager() +{ +} + +const QJsonDocument SettingsManager::getSetting(const settings::type& type) +{ + //return _sTable->getSettingsRecord(settings::typeToString(type)); + + QString key = settings::typeToString(type); + if(_qconfig[key].isObject()) + return QJsonDocument(_qconfig[key].toObject()); + else + return QJsonDocument(_qconfig[key].toArray()); +} + +const bool SettingsManager::saveSettings(QJsonObject config, const bool& correct) +{ + // we need to validate data against schema + QJsonSchemaChecker schemaChecker; + schemaChecker.setSchema(schemaJson); + if (!schemaChecker.validate(config).first) + { + if(!correct) + { + Error(_log,"Failed to save configuration, errors during validation"); + return false; + } + Warning(_log,"Fixing json data!"); + config = schemaChecker.getAutoCorrectedConfig(config); + + foreach (auto & schemaError, schemaChecker.getMessages()) + Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + } + + // save data to file + if(_hyperion != nullptr) + { + if(!JsonUtils::write(_hyperion->getConfigFilePath(), config, _log)) + return false; + } + + // store the current state + _qconfig = config; + + return true; +} diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index 501335f3..b49d2f44 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -71,6 +71,10 @@ { "$ref": "schema-effects.json" }, + "instCapture": + { + "$ref": "schema-instCapture.json" + }, "ledConfig": { "$ref": "schema-ledConfig.json" diff --git a/libsrc/hyperion/resource.qrc b/libsrc/hyperion/resource.qrc index 8616fa79..717d548a 100644 --- a/libsrc/hyperion/resource.qrc +++ b/libsrc/hyperion/resource.qrc @@ -22,5 +22,6 @@ schema/schema-effects.json schema/schema-ledConfig.json schema/schema-leds.json + schema/schema-instCapture.json diff --git a/libsrc/hyperion/schema/schema-color.json b/libsrc/hyperion/schema/schema-color.json index fe8181d5..1bcb1705 100644 --- a/libsrc/hyperion/schema/schema-color.json +++ b/libsrc/hyperion/schema/schema-color.json @@ -46,22 +46,6 @@ "default" : "*", "propertyOrder" : 2 }, - "black" : - { - "type" : "array", - "title" : "edt_conf_color_black_title", - "format" : "colorpicker", - "required" : true, - "default": [0,0,0], - "items" : { - "type" : "integer", - "minimum" : 0, - "maximum" : 255 - }, - "minItems" : 3, - "maxItems" : 3, - "propertyOrder" : 3 - }, "white" : { "type" : "array", diff --git a/libsrc/hyperion/schema/schema-framegrabber.json b/libsrc/hyperion/schema/schema-framegrabber.json index 366d05b6..e1f4ea98 100644 --- a/libsrc/hyperion/schema/schema-framegrabber.json +++ b/libsrc/hyperion/schema/schema-framegrabber.json @@ -3,13 +3,6 @@ "title" : "edt_conf_fg_heading_title", "properties" : { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "default" : true, - "propertyOrder" : 1 - }, "type" : { "type" : "string", @@ -45,15 +38,6 @@ "append" : "edt_append_hz", "propertyOrder" : 4 }, - "priority" : - { - "type" : "integer", - "title" : "edt_conf_general_priority_title", - "minimum" : 100, - "maximum" : 254, - "default" : 250, - "propertyOrder" : 5 - }, "cropLeft" : { "type" : "integer", @@ -90,42 +74,28 @@ "append" : "edt_append_pixel", "propertyOrder" : 9 }, - "useXGetImage" : + "pixelDecimation" : { - "type" : "boolean", - "title" : "edt_conf_fg_useXGetImage_title", - "default" : false, + "type" : "integer", + "title" : "edt_conf_fg_pixelDecimation_title", + "minimum" : 1, + "maximum" : 30, + "default" : 8, "propertyOrder" : 10 }, - "horizontalPixelDecimation" : - { - "type" : "integer", - "title" : "edt_conf_fg_horizontalPixelDecimation_title", - "minimum" : 0, - "default" : 8, - "propertyOrder" : 11 - }, - "verticalPixelDecimation" : - { - "type" : "integer", - "title" : "edt_conf_fg_verticalPixelDecimation_title", - "minimum" : 0, - "default" : 8, - "propertyOrder" : 12 - }, "device" : { "type" : "string", "title" : "edt_conf_fg_device_title", "default" : "/dev/fb0", - "propertyOrder" : 13 + "propertyOrder" : 11 }, "display" : { "type" : "integer", "title" : "edt_conf_fg_display_title", "minimum" : 0, - "propertyOrder" : 14 + "propertyOrder" : 12 } }, "additionalProperties" : false diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index e7356006..bd928614 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -11,14 +11,6 @@ "title" : "edt_conf_v4l2_heading_title", "properties" : { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "default" : false, - "required" : true, - "propertyOrder" : 1 - }, "device" : { "type" : "string", @@ -40,62 +32,24 @@ { "type" : "string", "title" : "edt_conf_v4l2_standard_title", - "enum" : ["PAL","NTSC","SECAM"], - "default" : "PAL", + "enum" : ["PAL","NTSC","SECAM","NO_CHANGE"], + "default" : "NO_CHANGE", "options" : { - "enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM"] + "enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM", "edt_conf_enum_NO_CHANGE"] }, "required" : true, "propertyOrder" : 4 }, - "width" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_width_title", - "minimum" : 0, - "default" : 0, - "append" : "edt_append_pixel", - "required" : true, - "propertyOrder" : 5 - }, - "height" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_height_title", - "minimum" : 0, - "default" : 0, - "append" : "edt_append_pixel", - "required" : true, - "propertyOrder" : 6 - }, - "frameDecimation" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_frameDecimation_title", - "minimum" : 0, - "default" : 2, - "required" : true, - "propertyOrder" : 7 - }, "sizeDecimation" : { "type" : "integer", - "title" : "Size decimation", - "minimum" : 0, + "title" : "edt_conf_v4l2_sizeDecimation_title", + "minimum" : 1, + "maximum" : 30, "default" : 6, "required" : true, "propertyOrder" : 8 }, - "priority" : - { - "type" : "integer", - "minimum" : 100, - "maximum" : 253, - "title" : "edt_conf_general_priority_title", - "default" : 240, - "required" : true, - "propertyOrder" : 9 - }, "cropLeft" : { "type" : "integer", diff --git a/libsrc/hyperion/schema/schema-instCapture.json b/libsrc/hyperion/schema/schema-instCapture.json new file mode 100644 index 00000000..2e59df10 --- /dev/null +++ b/libsrc/hyperion/schema/schema-instCapture.json @@ -0,0 +1,45 @@ +{ + "type" : "object", + "required" : true, + "title" : "edt_conf_instC_heading_title", + "properties" : + { + "systemEnable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_instC_systemEnable", + "default" : true, + "propertyOrder" : 1 + }, + "systemPriority" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_priority_title", + "minimum" : 100, + "maximum" : 253, + "default" : 250, + "propertyOrder" : 2 + }, + "v4lEnable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_instC_v4lEnable", + "default" : false, + "propertyOrder" : 3 + }, + "v4lPriority" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_priority_title", + "minimum" : 100, + "maximum" : 253, + "default" : 240, + "propertyOrder" : 4 + } + }, + "additionalProperties" : false +} diff --git a/libsrc/jsonserver/CMakeLists.txt b/libsrc/jsonserver/CMakeLists.txt index dfddffa5..1f0bc310 100644 --- a/libsrc/jsonserver/CMakeLists.txt +++ b/libsrc/jsonserver/CMakeLists.txt @@ -8,6 +8,7 @@ FILE ( GLOB JsonServer_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DI add_library(jsonserver ${JsonServer_SOURCES} ) target_link_libraries(jsonserver + hyperion-api hyperion Qt5::Network Qt5::Gui diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 554361b2..8fd2b630 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -1,7 +1,10 @@ // project includes #include "JsonClientConnection.h" -#include +#include + +// qt inc #include +#include JsonClientConnection::JsonClientConnection(QTcpSocket *socket) : QObject() @@ -11,10 +14,10 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket) { connect(_socket, &QTcpSocket::disconnected, this, &JsonClientConnection::disconnected); connect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest); - // create a new instance of JsonProcessor - _jsonProcessor = new JsonProcessor(socket->peerAddress().toString(), _log, this); - // get the callback messages from JsonProcessor and send it to the client - connect(_jsonProcessor,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject))); + // create a new instance of JsonAPI + _jsonAPI = new JsonAPI(socket->peerAddress().toString(), _log, this); + // get the callback messages from JsonAPI and send it to the client + connect(_jsonAPI,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject))); } void JsonClientConnection::readRequest() @@ -31,7 +34,7 @@ void JsonClientConnection::readRequest() _receiveBuffer = _receiveBuffer.mid(bytes); // handle message - _jsonProcessor->handleMessage(message); + _jsonAPI->handleMessage(message); // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 1195e7c5..57944275 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -3,11 +3,12 @@ // Qt includes #include #include +#include // util includes #include -class JsonProcessor; +class JsonAPI; class QTcpSocket; /// @@ -40,8 +41,8 @@ private slots: private: QTcpSocket* _socket; - /// new instance of JsonProcessor - JsonProcessor * _jsonProcessor; + /// new instance of JsonAPI + JsonAPI * _jsonAPI; /// The buffer used for reading data from the socket QByteArray _receiveBuffer; diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 3c8e9e73..42deda01 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -6,41 +6,41 @@ #include "JsonClientConnection.h" // hyperion include +#include #include +#include +#include // qt includes +#include #include #include #include -JsonServer::JsonServer(uint16_t port) +JsonServer::JsonServer(const QJsonDocument& config) : QObject() - , _server() + , _server(new QTcpServer(this)) , _hyperion(Hyperion::getInstance()) , _openConnections() , _log(Logger::getInstance("JSONSERVER")) + , _componentRegister( & _hyperion->getComponentRegister()) { - if (!_server.listen(QHostAddress::Any, port)) - { - throw std::runtime_error("JSONSERVER ERROR: could not bind to port"); - } - - QList list = Hyperion::getInstance()->getForwarder()->getJsonSlaves(); - for ( int i=0; iisComponentEnabled(hyperion::COMP_FORWARDER)); } JsonServer::~JsonServer() @@ -50,16 +50,58 @@ JsonServer::~JsonServer() } } +void JsonServer::start() +{ + if(_server->isListening()) + return; + + if (!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Could not bind to port '%d', please use an available port", _port); + return; + } + Info(_log, "Started on port %d", _port); + + if(_serviceRegister == nullptr) + { + _serviceRegister = new BonjourServiceRegister(); + _serviceRegister->registerService("_hyperiond-json._tcp", _port); + } +} + +void JsonServer::stop() +{ + if(!_server->isListening()) + return; + + _server->close(); + Info(_log, "Stopped"); +} + +void JsonServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::JSONSERVER) + { + QJsonObject obj = config.object(); + if(_port != obj["port"].toInt()) + { + _port = obj["port"].toInt(); + stop(); + start(); + } + } +} + uint16_t JsonServer::getPort() const { - return _server.serverPort(); + return _port; } void JsonServer::newConnection() { - while(_server.hasPendingConnections()) + while(_server->hasPendingConnections()) { - if (QTcpSocket * socket = _server.nextPendingConnection()) + if (QTcpSocket * socket = _server->nextPendingConnection()) { Debug(_log, "New connection from: %s ",socket->localAddress().toString().toStdString().c_str()); JsonClientConnection * connection = new JsonClientConnection(socket); @@ -83,11 +125,9 @@ void JsonServer::closedConnection(void) void JsonServer::componentStateChanged(const hyperion::Components component, bool enable) { - if (component == hyperion::COMP_FORWARDER && _forwarder_enabled != enable) + if (component == hyperion::COMP_FORWARDER) { - _forwarder_enabled = enable; - Info(_log, "forwarder change state to %s", (enable ? "enabled" : "disabled") ); - if(_forwarder_enabled) + if(enable) { connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); } @@ -101,11 +141,12 @@ void JsonServer::componentStateChanged(const hyperion::Components component, boo void JsonServer::forwardJsonMessage(const QJsonObject &message) { QTcpSocket client; - QList list = _hyperion->getForwarder()->getJsonSlaves(); + QStringList list = _hyperion->getForwarder()->getJsonSlaves(); - for ( int i=0; i LedDeviceRegistry LedDevice::_ledDeviceMap = LedDeviceRegistry(); -QString LedDevice::_activeDevice = ""; -int LedDevice::_ledCount = 0; -int LedDevice::_ledRGBCount = 0; -int LedDevice::_ledRGBWCount= 0; LedDevice::LedDevice() : QObject() - , _log(Logger::getInstance("LedDevice")) + , _log(Logger::getInstance("LEDDEVICE")) , _ledBuffer(0) , _deviceReady(true) , _refresh_timer() @@ -77,6 +73,10 @@ void LedDevice::setActiveDevice(QString dev) bool LedDevice::init(const QJsonObject &deviceConfig) { + _colorOrder = deviceConfig["colorOrder"].toString("RGB"); + _activeDevice = deviceConfig["type"].toString("file").toLower(); + setLedCount(deviceConfig["currentLedCount"].toInt(1)); // property injected to reflect real led count + _latchTime_ms = deviceConfig["latchTime"].toInt(_latchTime_ms); _refresh_timer.setInterval( deviceConfig["rewriteTime"].toInt( _refresh_timer_interval) ); if (_refresh_timer.interval() <= (signed)_latchTime_ms ) diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index 85505451..8deaaf96 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -13,20 +13,17 @@ // following file is auto generated by cmake! it contains all available leddevice headers #include "LedDevice_headers.h" -LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const int ledCount) +LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig) { - Logger * log = Logger::getInstance("LedDevice"); + Logger * log = Logger::getInstance("LEDDEVICE"); QJsonDocument config(deviceConfig); QString ss(config.toJson(QJsonDocument::Indented)); QString type = deviceConfig["type"].toString("UNSPECIFIED").toLower(); - // set amount of led to leddevice - LedDevice::setLedCount(ledCount); - #define REGISTER(className) LedDevice::addToDeviceMap(QString(#className).toLower(), LedDevice##className::construct); - // the REGISTER() calls are autogenerated by cmake. + // the REGISTER() calls are autogenerated by cmake. #include "LedDevice_register.cpp" #undef REGISTER @@ -40,12 +37,11 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const if (dev.first == type) { device = dev.second(deviceConfig); - LedDevice::setActiveDevice(dev.first); Info(log,"LedDevice '%s' configured.", QSTRING_CSTR(dev.first)); break; } } - + if (device == nullptr) { Error(log, "Dummy device used, because configured device '%s' is unknown", QSTRING_CSTR(type) ); @@ -54,13 +50,13 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const } catch(std::exception& e) { - + Error(log, "Dummy device used, because configured device '%s' throws error '%s'", QSTRING_CSTR(type), e.what()); const QJsonObject dummyDeviceConfig; device = LedDeviceFile::construct(QJsonObject()); } device->open(); - + return device; } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index b49d098a..d40ffa48 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -21,7 +21,7 @@ ProviderUdp::ProviderUdp() , _defaultHost("127.0.0.1") { _latchTime_ms = 1; - _udpSocket = new QUdpSocket(); + _udpSocket = new QUdpSocket(this); } ProviderUdp::~ProviderUdp() @@ -34,7 +34,7 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig) LedDevice::init(deviceConfig); QString host = deviceConfig["host"].toString(_defaultHost); - + if (_address.setAddress(host) ) { Debug( _log, "Successfully parsed %s as an ip address.", deviceConfig["host"].toString().toStdString().c_str()); @@ -57,9 +57,9 @@ bool ProviderUdp::init(const QJsonObject &deviceConfig) { throw std::runtime_error("invalid target port"); } - + Debug( _log, "UDP using %s:%d", _address.toString().toStdString().c_str() , _port ); - + return true; } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.h b/libsrc/leddevice/dev_net/ProviderUdp.h index ef34220d..316902bd 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.h +++ b/libsrc/leddevice/dev_net/ProviderUdp.h @@ -1,11 +1,14 @@ #pragma once -#include - // Hyperion includes #include #include +// qt +#include + +class QUdpSocket; + /// /// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets. /// diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index 737292f2..f492d380 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -7,24 +7,42 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIRS} ) -FILE ( GLOB ProtoServer_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) set(ProtoServer_PROTOS ${CURRENT_SOURCE_DIR}/message.proto ) protobuf_generate_cpp(ProtoServer_PROTO_SRCS ProtoServer_PROTO_HDRS ${ProtoServer_PROTOS} ) -add_library(protoserver - ${ProtoServer_SOURCES} - ${ProtoServer_PROTOS} +### Split protoclient from protoserver as protoserver relates to HyperionDaemon and standalone capture binarys can't link to it + +add_library(protoclient + ${CURRENT_HEADER_DIR}/ProtoConnection.h + ${CURRENT_SOURCE_DIR}/ProtoConnection.cpp + ${CURRENT_HEADER_DIR}/ProtoConnectionWrapper.h + ${CURRENT_SOURCE_DIR}/ProtoConnectionWrapper.cpp + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.h + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp ${ProtoServer_PROTO_SRCS} ${ProtoServer_PROTO_HDRS} ) + +add_library(protoserver + ${CURRENT_HEADER_DIR}/ProtoServer.h + ${CURRENT_SOURCE_DIR}/ProtoServer.cpp +) + # disable warnings for auto generatet proto files, we can't change the files .... SET_SOURCE_FILES_PROPERTIES ( ${ProtoServer_PROTO_SRCS} ${ProtoServer_PROTO_HDRS} ${ProtoServer_PROTOS} PROPERTIES COMPILE_FLAGS -w ) -target_link_libraries(protoserver +target_link_libraries(protoclient hyperion hyperion-utils protobuf Qt5::Gui ) + +target_link_libraries(protoserver + hyperion + hyperion-utils + protoclient + Qt5::Gui +) diff --git a/libsrc/protoserver/ProtoClientConnection.cpp b/libsrc/protoserver/ProtoClientConnection.cpp index 1e2e24e2..2a3b6b22 100644 --- a/libsrc/protoserver/ProtoClientConnection.cpp +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -14,8 +14,6 @@ #include // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" // project includes @@ -24,17 +22,14 @@ ProtoClientConnection::ProtoClientConnection(QTcpSocket *socket) : QObject() , _socket(socket) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _priority(-1) - , _priorityChannelName("Proto-Server") , _clientAddress(QHostInfo::fromName(socket->peerAddress().toString()).hostName()) { // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); connect(_socket, SIGNAL(readyRead()), this, SLOT(readData())); - connect(_hyperion, SIGNAL(imageToLedsMappingChanged(int)), _imageProcessor, SLOT(setLedMappingType(int))); } ProtoClientConnection::~ProtoClientConnection() @@ -81,7 +76,7 @@ void ProtoClientConnection::readData() void ProtoClientConnection::socketClosed() { - _hyperion->unRegisterPriority(_priorityChannelName); + _hyperion->clear(_priority); emit connectionClosed(this); } @@ -103,7 +98,6 @@ void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message // forward messages emit newMessage(&message); - int prevPriority = _priority; switch (message.command()) { case proto::HyperionRequest::COLOR: @@ -136,24 +130,26 @@ void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message default: handleNotImplemented(); } - - if (prevPriority != _priority) - { - _hyperion->registerPriority(_priorityChannelName, _priority); - prevPriority = _priority; - } } void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &message) { // extract parameters - _priority = message.priority(); + int priority = message.priority(); int duration = message.has_duration() ? message.duration() : -1; ColorRgb color; color.red = qRed(message.rgbcolor()); color.green = qGreen(message.rgbcolor()); color.blue = qBlue(message.rgbcolor()); + // make sure the prio is registered before setColor() + if(priority != _priority) + { + _hyperion->clear(_priority); + _hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "proto@"+_clientAddress); + _priority = priority; + } + // set output _hyperion->setColor(_priority, color, duration); @@ -164,12 +160,20 @@ void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &messag void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &message) { // extract parameters - _priority = message.priority(); + int priority = message.priority(); int duration = message.has_duration() ? message.duration() : -1; int width = message.imagewidth(); int height = message.imageheight(); const std::string & imageData = message.imagedata(); + // make sure the prio is registered before setInput() + if(priority != _priority) + { + _hyperion->clear(_priority); + _hyperion->registerInput(priority, hyperion::COMP_PROTOSERVER, "proto@"+_clientAddress); + _priority = priority; + } + // check consistency of the size of the received data if ((int) imageData.size() != width*height*3) { @@ -177,17 +181,11 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag return; } - // set width and height of the image processor - _imageProcessor->setSize(width, height); - // create ImageRgb Image image(width, height); memcpy(image.memptr(), imageData.c_str(), imageData.size()); - // process the image - std::vector ledColors = _imageProcessor->process(image); - _hyperion->setColors(_priority, ledColors, duration, true, hyperion::COMP_PROTOSERVER , "proto@"+_clientAddress); - _hyperion->setImage(_priority, image, duration); + _hyperion->setInputImage(_priority, image, duration); // send reply sendSuccessReply(); @@ -197,11 +195,10 @@ void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &messag void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &message) { // extract parameters - _priority = message.priority(); + int priority = message.priority(); // clear priority - _hyperion->clear(_priority); - _hyperion->unRegisterPriority(_priorityChannelName); + _hyperion->clear(priority); // send reply sendSuccessReply(); } diff --git a/libsrc/protoserver/ProtoClientConnection.h b/libsrc/protoserver/ProtoClientConnection.h index ad139094..f61cfed1 100644 --- a/libsrc/protoserver/ProtoClientConnection.h +++ b/libsrc/protoserver/ProtoClientConnection.h @@ -19,8 +19,6 @@ #include "message.pb.h" #include "protoserver/ProtoConnection.h" -class ImageProcessor; - /// /// The Connection object created by a ProtoServer when a new connection is establshed /// @@ -128,9 +126,6 @@ private: /// The TCP-Socket that is connected tot the Proto-client QTcpSocket * _socket; - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - /// Link to Hyperion for writing led-values to a priority channel Hyperion * _hyperion; @@ -139,8 +134,6 @@ private: int _priority; - QString _priorityChannelName; - /// address of client QString _clientAddress; }; diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp index a925bf74..4504dda0 100644 --- a/libsrc/protoserver/ProtoServer.cpp +++ b/libsrc/protoserver/ProtoServer.cpp @@ -1,43 +1,44 @@ // system includes #include +// qt incl +#include + // project includes +#include #include #include #include "protoserver/ProtoConnection.h" #include "ProtoClientConnection.h" +#include +#include -ProtoServer::ProtoServer(uint16_t port) +ProtoServer::ProtoServer(const QJsonDocument& config) : QObject() , _hyperion(Hyperion::getInstance()) - , _server() + , _server(new QTcpServer(this)) , _openConnections() , _log(Logger::getInstance("PROTOSERVER")) - , _forwarder_enabled(true) + , _componentRegister( & _hyperion->getComponentRegister()) { + Debug(_log,"Instance created"); + connect( _server, SIGNAL(newConnection()), this, SLOT(newConnection())); + handleSettingsUpdate(settings::PROTOSERVER, config); - MessageForwarder * forwarder = _hyperion->getForwarder(); - QStringList slaves = forwarder->getProtoSlaves(); + QStringList slaves = _hyperion->getForwarder()->getProtoSlaves(); - for (int i = 0; i < slaves.size(); ++i) { - if ( QString("127.0.0.1:%1").arg(port) == slaves.at(i) ) { - throw std::runtime_error("PROTOSERVER ERROR: Loop between proto server and forwarder detected. Fix your config!"); - } - - ProtoConnection* p = new ProtoConnection(slaves.at(i).toLocal8Bit().constData()); + for (const auto& entry : slaves) + { + ProtoConnection* p = new ProtoConnection(entry.toLocal8Bit().constData()); p->setSkipReply(true); _proxy_connections << p; } - if (!_server.listen(QHostAddress::Any, port)) - { - throw std::runtime_error("PROTOSERVER ERROR: Could not bind to port"); - } - - // Set trigger for incoming connections - connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); - connect( _hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); + // listen for component changes + connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &ProtoServer::componentStateChanged); + // get inital forwarder state + componentStateChanged(hyperion::COMP_FORWARDER, _componentRegister->isComponentEnabled(hyperion::COMP_FORWARDER)); } ProtoServer::~ProtoServer() @@ -50,27 +51,70 @@ ProtoServer::~ProtoServer() delete _proxy_connections.takeFirst(); } +void ProtoServer::start() +{ + if(_server->isListening()) + return; + + if (!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Could not bind to port '%d', please use an available port",_port); + return; + } + Info(_log, "Started on port %d", _port); + + if(_serviceRegister == nullptr) + { + _serviceRegister = new BonjourServiceRegister(); + _serviceRegister->registerService("_hyperiond-proto._tcp", _port); + } +} + +void ProtoServer::stop() +{ + if(!_server->isListening()) + return; + + _server->close(); + Info(_log, "Stopped"); +} + +void ProtoServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::PROTOSERVER) + { + QJsonObject obj = config.object(); + if(obj["port"].toInt() != _port) + { + _port = obj["port"].toInt(); + stop(); + start(); + } + } +} + uint16_t ProtoServer::getPort() const { - return _server.serverPort(); + return _port; } void ProtoServer::newConnection() { - QTcpSocket * socket = _server.nextPendingConnection(); - - if (socket != nullptr) + while(_server->hasPendingConnections()) { - Debug(_log, "New connection"); - ProtoClientConnection * connection = new ProtoClientConnection(socket); - _openConnections.insert(connection); + if(QTcpSocket * socket = _server->nextPendingConnection()) + { + Debug(_log, "New connection"); + ProtoClientConnection * connection = new ProtoClientConnection(socket); + _openConnections.insert(connection); - // register slot for cleaning up after the connection closed - connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); - connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); + // register slot for cleaning up after the connection closed + connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); + connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); - // register forward signal for video mode - connect(this, SIGNAL(videoMode(VideoMode)), connection, SLOT(setVideoMode(VideoMode))); + // register forward signal for video mode + connect(this, SIGNAL(videoMode(VideoMode)), connection, SLOT(setVideoMode(VideoMode))); + } } } @@ -93,12 +137,7 @@ void ProtoServer::componentStateChanged(const hyperion::Components component, bo { if (component == hyperion::COMP_FORWARDER) { - if (_forwarder_enabled != enable) - { - _forwarder_enabled = enable; - Info(_log, "forwarder change state to %s", (_forwarder_enabled ? "enabled" : "disabled") ); - } - _hyperion->getComponentRegister().componentStateChanged(component, _forwarder_enabled); + _forwarder_enabled = enable; } } diff --git a/libsrc/python/CMakeLists.txt b/libsrc/python/CMakeLists.txt new file mode 100644 index 00000000..2f363d88 --- /dev/null +++ b/libsrc/python/CMakeLists.txt @@ -0,0 +1,21 @@ +find_package(PythonLibs 3.4 REQUIRED) + +# Include the python directory. Also include the parent (which is for example /usr/include) +# which may be required when it is not includes by the (cross-) compiler by default. +include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/python) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/python) + +FILE ( GLOB PYTHON_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +add_library(python + ${PYTHON_SOURCES} +) + +target_link_libraries(python + effectengine + hyperion-utils + ${PYTHON_LIBRARIES} +) diff --git a/libsrc/python/PythonInit.cpp b/libsrc/python/PythonInit.cpp new file mode 100644 index 00000000..f93f920f --- /dev/null +++ b/libsrc/python/PythonInit.cpp @@ -0,0 +1,31 @@ +#undef slots +#include +#define slots + +// utils +#include + +#include +#include + +// modules to init +#include + +PythonInit::PythonInit() +{ + // register modules + EffectModule::registerHyperionExtensionModule(); + + // init Python + Debug(Logger::getInstance("DAEMON"), "Initializing Python interpreter"); + Py_InitializeEx(0); + PyEval_InitThreads(); // Create the GIL + mainThreadState = PyEval_SaveThread(); +} + +PythonInit::~PythonInit() +{ + Debug(Logger::getInstance("DAEMON"), "Cleaning up Python interpreter"); + PyEval_RestoreThread(mainThreadState); + Py_Finalize(); +} diff --git a/libsrc/udplistener/UDPListener.cpp b/libsrc/udplistener/UDPListener.cpp index cb53958c..3674587b 100644 --- a/libsrc/udplistener/UDPListener.cpp +++ b/libsrc/udplistener/UDPListener.cpp @@ -1,33 +1,38 @@ // project includes #include +// hyperion includes +#include +#include + // hyperion util includes -#include "hyperion/ImageProcessorFactory.h" -#include "hyperion/ImageProcessor.h" #include "utils/ColorRgb.h" #include "HyperionConfig.h" +// qt includes +#include + using namespace hyperion; -UDPListener::UDPListener(const int priority, const int timeout, const QString& address, quint16 listenPort, bool shared) : +UDPListener::UDPListener(const QJsonDocument& config) : QObject(), _hyperion(Hyperion::getInstance()), - _server(), + _server(new QUdpSocket(this)), _openConnections(), - _priority(priority), - _timeout(timeout), + _priority(0), + _timeout(0), _log(Logger::getInstance("UDPLISTENER")), _isActive(false), - _listenPort(listenPort), - _bondage(shared ? QAbstractSocket::ShareAddress : QAbstractSocket::DefaultForPlatform) + _listenPort(0) { - _server = new QUdpSocket(this); - _listenAddress = address.isEmpty()? QHostAddress::AnyIPv4 : QHostAddress(address); - + Debug(_log, "Instance created"); + // listen for comp changes + connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); // Set trigger for incoming connections connect(_server, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - _hyperion->registerPriority("UDPLISTENER", _priority); + // init + handleSettingsUpdate(settings::UDPLISTENER, config); } UDPListener::~UDPListener() @@ -51,7 +56,7 @@ void UDPListener::start() if (!_server->bind(_listenAddress, _listenPort, _bondage)) { - Warning(_log, "Could not bind to %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort); + Error(_log, "Could not bind to %s:%d", _listenAddress.toString().toStdString().c_str(), _listenPort); } else { @@ -62,7 +67,13 @@ void UDPListener::start() WarningIf( ! joinGroupOK, _log, "Multicast failed"); } _isActive = true; - emit statusChanged(_isActive); + _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); + + if(_bonjourService == nullptr) + { + _bonjourService = new BonjourServiceRegister(); + _bonjourService->registerService("_hyperiond-udp._udp", _listenPort); + } } } @@ -73,7 +84,9 @@ void UDPListener::stop() _server->close(); _isActive = false; - emit statusChanged(_isActive); + Info(_log, "Stopped"); + _hyperion->clear(_priority); + _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); } void UDPListener::componentStateChanged(const hyperion::Components component, bool enable) @@ -84,9 +97,7 @@ void UDPListener::componentStateChanged(const hyperion::Components component, bo { if (enable) start(); else stop(); - Info(_log, "change state to %s", (enable ? "enabled" : "disabled") ); } - _hyperion->getComponentRegister().componentStateChanged(component, enable); } } @@ -105,9 +116,7 @@ void UDPListener::readPendingDatagrams() quint16 senderPort; _server->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); - processTheDatagram(&datagram, &sender); - } } @@ -126,9 +135,26 @@ void UDPListener::processTheDatagram(const QByteArray * datagram, const QHostAdd rgb.green = datagram->at(ledIndex*3+1); rgb.blue = datagram->at(ledIndex*3+2); } - - _hyperion->setColors(_priority, _ledColors, _timeout, -1, hyperion::COMP_UDPLISTENER, sender->toString()); + // TODO provide a setInput with origin arg to overwrite senders smarter + _hyperion->registerInput(_priority, hyperion::COMP_UDPLISTENER, QString("UDPListener@%1").arg(sender->toString())); + _hyperion->setInput(_priority, _ledColors, _timeout); } +void UDPListener::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::UDPLISTENER) + { + QJsonObject obj = config.object(); + // if we change the prio we need to make sure the old one is cleared before we apply the new one! + stop(); - + QString addr = obj["address"].toString(""); + _priority = obj["priority"].toInt(); + _listenPort = obj["port"].toInt(); + _listenAddress = addr.isEmpty()? QHostAddress::AnyIPv4 : QHostAddress(addr); + _bondage = (obj["shared"].toBool(false)) ? QAbstractSocket::ShareAddress : QAbstractSocket::DefaultForPlatform; + _timeout = obj["timeout"].toInt(10000); + if(obj["enable"].toBool()) + start(); + } +} diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index a1483ec6..6b3865ac 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -9,11 +9,8 @@ if ( NOT ENABLE_PROFILER ) LIST ( REMOVE_ITEM Utils_SOURCES ${CURRENT_HEADER_DIR}/Profiler.h ${CURRENT_SOURCE_DIR}/Profiler.cpp ) endif() -set(Utils_RESOURCES ${CURRENT_SOURCE_DIR}/JSONRPC_schemas.qrc ) - add_library(hyperion-utils ${Utils_SOURCES} - ${Utils_RESOURCES} ) target_link_libraries(hyperion-utils diff --git a/libsrc/utils/FileUtils.cpp b/libsrc/utils/FileUtils.cpp index c7c6d208..31c70cd5 100644 --- a/libsrc/utils/FileUtils.cpp +++ b/libsrc/utils/FileUtils.cpp @@ -1,6 +1,7 @@ #include // qt incl +#include #include #include @@ -21,6 +22,17 @@ namespace FileUtils { return fi.path(); } + bool removeDir(const QString& path, Logger* log) + { + //QDir dir(path); + if(!QDir(path).removeRecursively()) + { + Error(log, "Failed to remove directory: %s", QSTRING_CSTR(path)); + return false; + } + return true; + } + bool fileExists(const QString& path, Logger* log, bool ignError) { QFile file(path); @@ -71,17 +83,18 @@ namespace FileUtils { return true; } - bool removeFile(const QString& path, Logger* log) + bool removeFile(const QString& path, Logger* log, bool ignError) { QFile file(path); if(!file.remove()) { - resolveFileError(file,log); + if(!ignError) + resolveFileError(file,log); return false; } return true; } - + QString convertPath(const QString path) { QString p = path; diff --git a/libsrc/utils/JSONRPC_schema/schema-clearall.json b/libsrc/utils/JSONRPC_schema/schema-clearall.json deleted file mode 100644 index 8c88cc6c..00000000 --- a/libsrc/utils/JSONRPC_schema/schema-clearall.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type":"object", - "required":true, - "properties":{ - "command": { - "type" : "string", - "required" : true, - "enum" : ["clearall"] - }, - "tan" : { - "type" : "integer" - } - }, - "additionalProperties": false -} diff --git a/libsrc/utils/JsonProcessor.cpp b/libsrc/utils/JsonProcessor.cpp deleted file mode 100644 index 5e114f6c..00000000 --- a/libsrc/utils/JsonProcessor.cpp +++ /dev/null @@ -1,1196 +0,0 @@ -// project includes -#include - -// stl includes -#include -#include -#include -#include - -// Qt includes -#include -#include -#include -#include -#include -#include - #include - #include - #include -#include - -// hyperion includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace hyperion; - -std::map JsonProcessor::_componentsPrevState; - -JsonProcessor::JsonProcessor(QString peerAddress, Logger* log, QObject* parent, bool noListener) - : QObject(parent) - , _peerAddress(peerAddress) - , _log(log) - , _hyperion(Hyperion::getInstance()) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) - , _streaming_logging_activated(false) - , _image_stream_timeout(0) - , _led_stream_timeout(0) -{ - // notify hyperion about a jsonMessageForward - connect(this, &JsonProcessor::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); - // notify hyperion about a push emit TODO: Remove! Make sure that the target of the commands trigger this (less error margin) instead this instance - connect(this, &JsonProcessor::pushReq, _hyperion, &Hyperion::hyperionStateChanged); - - if(!noListener) - { - // listen for sendServerInfo pushes from hyperion - connect(_hyperion, &Hyperion::sendServerInfo, this, &JsonProcessor::forceServerInfo); - } - - _image_stream_mutex.unlock(); - _led_stream_mutex.unlock(); -} - -void JsonProcessor::handleMessage(const QString& messageString) -{ - const QString ident = "JsonRpc@"+_peerAddress; - Q_INIT_RESOURCE(JSONRPC_schemas); - QJsonObject message; - // parse the message - if(!JsonUtils::parse(ident, messageString, message, _log)) - { - sendErrorReply("Errors during message parsing, please consult the Hyperion Log. Data:"+messageString); - return; - } - - // check basic message - if(!JsonUtils::validate(ident, message, ":schema", _log)) - { - sendErrorReply("Errors during message validation, please consult the Hyperion Log."); - return; - } - - // check specific message - const QString command = message["command"].toString(); - if(!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) - { - sendErrorReply("Errors during specific message validation, please consult the Hyperion Log"); - return; - } - - int tan = message["tan"].toInt(); - // switch over all possible commands and handle them - if (command == "color") handleColorCommand (message, command, tan); - else if (command == "image") handleImageCommand (message, command, tan); - else if (command == "effect") handleEffectCommand (message, command, tan); - else if (command == "create-effect") handleCreateEffectCommand (message, command, tan); - else if (command == "delete-effect") handleDeleteEffectCommand (message, command, tan); - else if (command == "sysinfo") handleSysInfoCommand (message, command, tan); - else if (command == "serverinfo") handleServerInfoCommand (message, command, tan); - else if (command == "clear") handleClearCommand (message, command, tan); - else if (command == "clearall") handleClearallCommand (message, command, tan); - else if (command == "adjustment") handleAdjustmentCommand (message, command, tan); - else if (command == "sourceselect") handleSourceSelectCommand (message, command, tan); - else if (command == "config") handleConfigCommand (message, command, tan); - else if (command == "componentstate") handleComponentStateCommand(message, command, tan); - else if (command == "ledcolors") handleLedColorsCommand (message, command, tan); - else if (command == "logging") handleLoggingCommand (message, command, tan); - else if (command == "processing") handleProcessingCommand (message, command, tan); - else if (command == "videomode") handleVideoModeCommand (message, command, tan); - else handleNotImplemented (); -} - -void JsonProcessor::handleColorCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - QString origin = message["origin"].toString() + "@"+_peerAddress; - - std::vector colorData(_hyperion->getLedCount()); - const QJsonArray & jsonColor = message["color"].toArray(); - unsigned int i = 0; - for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i) - { - colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt()); - colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt()); - colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt()); - } - - // copy full blocks of led colors - unsigned size = i; - while (i + size < _hyperion->getLedCount()) - { - memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb)); - i += size; - } - - // copy remaining block of led colors - if (i < _hyperion->getLedCount()) - { - memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb)); - } - - // set output - _hyperion->setColors(priority, colorData, duration, true, hyperion::COMP_COLOR, origin); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleImageCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - int width = message["imagewidth"].toInt(); - int height = message["imageheight"].toInt(); - QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); - - // check consistency of the size of the received data - if (data.size() != width*height*3) - { - sendErrorReply("Size of image data does not match with the width and height", command, tan); - return; - } - - // set width and height of the image processor - _imageProcessor->setSize(width, height); - - // create ImageRgb - Image image(width, height); - memcpy(image.memptr(), data.data(), data.size()); - - // process the image - std::vector ledColors = _imageProcessor->process(image); - _hyperion->setColors(priority, ledColors, duration); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - QString pythonScript = message["pythonScript"].toString(); - QString origin = message["origin"].toString() + "@"+_peerAddress; - const QJsonObject & effect = message["effect"].toObject(); - const QString & effectName = effect["name"].toString(); - - // set output - if (effect.contains("args")) - { - _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); - } - else - { - _hyperion->setEffect(effectName, priority, duration, origin); - } - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) -{ - if (!message["args"].toObject().isEmpty()) - { - QString scriptName; - (message["script"].toString().mid(0, 1) == ":" ) - ? scriptName = ":/effects//" + message["script"].toString().mid(1) - : scriptName = message["script"].toString(); - - std::list effectsSchemas = _hyperion->getEffectSchemas(); - std::list::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName)); - - if (it != effectsSchemas.end()) - { - if(!JsonUtils::validate("JsonRpc@"+_peerAddress, message["args"].toObject(), it->schemaFile, _log)) - { - sendErrorReply("Error during arg validation against schema, please consult the Hyperion Log", command, tan); - return; - } - - QJsonObject effectJson; - QJsonArray effectArray; - effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray(); - - if (effectArray.size() > 0) - { - if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) - { - sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan); - return; - } - - effectJson["name"] = message["name"].toString(); - effectJson["script"] = message["script"].toString(); - effectJson["args"] = message["args"].toObject(); - - std::list availableEffects = _hyperion->getEffects(); - std::list::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString())); - - QFileInfo newFileName; - if (iter != availableEffects.end()) - { - newFileName.setFile(iter->file); - if (newFileName.absoluteFilePath().mid(0, 1) == ":") - { - sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan); - return; - } - } else - { - QString f = FileUtils::convertPath(effectArray[0].toString() + "/" + message["name"].toString().replace(QString(" "), QString("")) + QString(".json")); - newFileName.setFile(f); - } - - if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log)) - { - sendErrorReply("Error while saving effect, please check the Hyperion Log", command, tan); - return; - } - - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - { - sendErrorReply("Can't save new effect. Effect path empty", command, tan); - return; - } - } else - sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan); - } else - sendErrorReply("Missing or empty Object 'args'", command, tan); -} - -void JsonProcessor::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - QString effectName = message["name"].toString(); - std::list effectsDefinition = _hyperion->getEffects(); - std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); - - if (it != effectsDefinition.end()) - { - QFileInfo effectConfigurationFile(it->file); - if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" ) - { - if (effectConfigurationFile.exists()) - { - bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); - if (result) - { - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan); - } else - sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan); - } else - sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan); - } else - sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan); -} - -void JsonProcessor::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan) -{ - // create result - QJsonObject result; - QJsonObject info; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - SysInfo::HyperionSysInfo data = SysInfo::get(); - QJsonObject system; - system["kernelType" ] = data.kernelType; - system["kernelVersion" ] = data.kernelVersion; - system["architecture" ] = data.architecture; - system["wordSize" ] = data.wordSize; - system["productType" ] = data.productType; - system["productVersion"] = data.productVersion; - system["prettyName" ] = data.prettyName; - system["hostName" ] = data.hostName; - system["domainName" ] = data.domainName; - info["system"] = system; - - QJsonObject hyperion; - hyperion["jsonrpc_version" ] = QString(HYPERION_JSON_VERSION); - hyperion["version" ] = QString(HYPERION_VERSION); - hyperion["build" ] = QString(HYPERION_BUILD_ID); - hyperion["time" ] = QString(__DATE__ " " __TIME__); - hyperion["id" ] = _hyperion->id; - info["hyperion"] = hyperion; - - // send the result - result["info" ] = info; - emit callbackMessage(result); -} - -void JsonProcessor::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - QJsonObject info; - - // collect priority information - QJsonArray priorities; - uint64_t now = QDateTime::currentMSecsSinceEpoch(); - QList activePriorities = _hyperion->getActivePriorities(); - Hyperion::PriorityRegister priorityRegister = _hyperion->getPriorityRegister(); - int currentPriority = _hyperion->getCurrentPriority(); - - foreach (int priority, activePriorities) { - const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); - QJsonObject item; - item["priority"] = priority; - if (priorityInfo.timeoutTime_ms != -1 ) - { - item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); - } - - item["owner"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["origin"] = priorityInfo.origin; - item["active"] = true; - item["visible"] = (priority == currentPriority); - - // remove item from prio register, because we have more valuable information via active priority - QList prios = priorityRegister.keys(priority); - if (! prios.empty()) - { - item["owner"] = prios[0]; - priorityRegister.remove(prios[0]); - } - - if(priorityInfo.componentId == hyperion::COMP_COLOR) - { - QJsonObject LEDcolor; - - // add RGB Value to Array - QJsonArray RGBValue; - RGBValue.append(priorityInfo.ledColors.begin()->red); - RGBValue.append(priorityInfo.ledColors.begin()->green); - RGBValue.append(priorityInfo.ledColors.begin()->blue); - LEDcolor.insert("RGB", RGBValue); - - uint16_t Hue; - float Saturation, Luminace; - - // add HSL Value to Array - QJsonArray HSLValue; - ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, - priorityInfo.ledColors.begin()->green, - priorityInfo.ledColors.begin()->blue, - Hue, Saturation, Luminace); - - HSLValue.append(Hue); - HSLValue.append(Saturation); - HSLValue.append(Luminace); - LEDcolor.insert("HSL", HSLValue); - - // add HEX Value to Array ["HEX Value"] - QJsonArray HEXValue; - std::stringstream hex; - hex << "0x" - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->red) - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->green) - << std::uppercase << std::setw(2) << std::setfill('0') - << std::hex << unsigned(priorityInfo.ledColors.begin()->blue); - - HEXValue.append(QString::fromStdString(hex.str())); - LEDcolor.insert("HEX", HEXValue); - - item["value"] = LEDcolor; - } - // priorities[priorities.size()] = item; - priorities.append(item); - } - - // append left over priorities - for(auto key : priorityRegister.keys()) - { - QJsonObject item; - item["priority"] = priorityRegister[key]; - item["active"] = false; - item["visible"] = false; - item["owner"] = key; - priorities.append(item); - } - - info["priorities"] = priorities; - info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); - - // collect adjustment information - QJsonArray adjustmentArray; - for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) - { - const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); - continue; - } - - QJsonObject adjustment; - adjustment["id"] = adjustmentId; - - QJsonArray blackAdjust; - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentR()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentG()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentB()); - adjustment.insert("black", blackAdjust); - - QJsonArray whiteAdjust; - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); - whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); - adjustment.insert("white", whiteAdjust); - - QJsonArray redAdjust; - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); - redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); - adjustment.insert("red", redAdjust); - - QJsonArray greenAdjust; - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); - greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); - adjustment.insert("green", greenAdjust); - - QJsonArray blueAdjust; - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); - blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); - adjustment.insert("blue", blueAdjust); - - QJsonArray cyanAdjust; - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); - cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); - adjustment.insert("cyan", cyanAdjust); - - QJsonArray magentaAdjust; - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); - magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); - adjustment.insert("magenta", magentaAdjust); - - QJsonArray yellowAdjust; - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); - yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); - adjustment.insert("yellow", yellowAdjust); - - adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); - adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); - adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); - adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); - adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); - adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); - adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); - - adjustmentArray.append(adjustment); - } - - info["adjustment"] = adjustmentArray; - - // collect effect info - QJsonArray effects; - const std::list & effectsDefinitions = _hyperion->getEffects(); - for (const EffectDefinition & effectDefinition : effectsDefinitions) - { - QJsonObject effect; - effect["name"] = effectDefinition.name; - effect["file"] = effectDefinition.file; - effect["script"] = effectDefinition.script; - effect["args"] = effectDefinition.args; - effects.append(effect); - } - - info["effects"] = effects; - - // get available led devices - QJsonObject ledDevices; - ledDevices["active"] = LedDevice::activeDevice(); - QJsonArray availableLedDevices; - for (auto dev: LedDevice::getDeviceMap()) - { - availableLedDevices.append(dev.first); - } - - ledDevices["available"] = availableLedDevices; - info["ledDevices"] = ledDevices; - - QJsonObject grabbers; - QJsonArray availableGrabbers; -#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) - // get available grabbers - //grabbers["active"] = ????; - for (auto grabber: GrabberWrapper::availableGrabbers()) - { - availableGrabbers.append(grabber); - } -#endif - grabbers["available"] = availableGrabbers; - grabbers["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); - info["grabbers"] = grabbers; - - // get available components - QJsonArray component; - std::map components = _hyperion->getComponentRegister().getRegister(); - for(auto comp : components) - { - QJsonObject item; - item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); - item["enabled"] = comp.second; - - component.append(item); - } - - info["components"] = component; - info["ledMAppingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); - - // Add Hyperion - QJsonObject hyperion; - hyperion["config_modified" ] = _hyperion->configModified(); - hyperion["config_writeable"] = _hyperion->configWriteable(); - hyperion["off"] = hyperionIsActive()? false : true; - - // sessions - QJsonArray sessions; - for (auto session: _hyperion->getHyperionSessions()) - { - if (session.port<0) continue; - QJsonObject item; - item["name"] = session.serviceName; - item["type"] = session.registeredType; - item["domain"] = session.replyDomain; - item["host"] = session.hostName; - item["address"]= session.address; - item["port"] = session.port; - sessions.append(item); - } - hyperion["sessions"] = sessions; - - info["hyperion"] = hyperion; - - // send the result - result["info"] = info; - emit callbackMessage(result); -} - -void JsonProcessor::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - - // clear priority - _hyperion->clear(priority); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) -{ - emit forwardJsonMessage(message); - - // clear priority - _hyperion->clearall(); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan) -{ - const QJsonObject & adjustment = message["adjustment"].toObject(); - - const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first()); - ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); - if (colorAdjustment == nullptr) - { - Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str()); - return; - } - - if (adjustment.contains("red")) - { - const QJsonArray & values = adjustment["red"].toArray(); - colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("green")) - { - const QJsonArray & values = adjustment["green"].toArray(); - colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("blue")) - { - const QJsonArray & values = adjustment["blue"].toArray(); - colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("cyan")) - { - const QJsonArray & values = adjustment["cyan"].toArray(); - colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("magenta")) - { - const QJsonArray & values = adjustment["magenta"].toArray(); - colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("yellow")) - { - const QJsonArray & values = adjustment["yellow"].toArray(); - colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("black")) - { - const QJsonArray & values = adjustment["black"].toArray(); - colorAdjustment->_rgbBlackAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - if (adjustment.contains("white")) - { - const QJsonArray & values = adjustment["white"].toArray(); - colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); - } - - if (adjustment.contains("gammaRed")) - { - colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaGreen")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB()); - } - if (adjustment.contains("gammaBlue")) - { - colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble()); - } - - if (adjustment.contains("backlightThreshold")) - { - colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble()); - } - if (adjustment.contains("backlightColored")) - { - colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool()); - } - if (adjustment.contains("brightness")) - { - colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt()); - } - if (adjustment.contains("brightnessCompensation")) - { - colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt()); - } - - // commit the changes - _hyperion->adjustmentsUpdated(); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleSourceSelectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - bool success = false; - if (message["auto"].toBool(false)) - { - _hyperion->setSourceAutoSelectEnabled(true); - success = true; - } - else if (message.contains("priority")) - { - success = _hyperion->setCurrentSourcePriority(message["priority"].toInt()); - } - - if (success) - { - sendSuccessReply(command, tan); - } - else - { - sendErrorReply("setting current priority failed", command, tan); - } -} - -void JsonProcessor::handleConfigCommand(const QJsonObject& message, const QString& command, const int tan) -{ - QString subcommand = message["subcommand"].toString(""); - QString full_command = command + "-" + subcommand; - - if (subcommand == "getschema") - { - handleSchemaGetCommand(message, full_command, tan); - } - else if (subcommand == "setconfig") - { - handleConfigSetCommand(message, full_command, tan); - } - else if (subcommand == "getconfig") - { - handleConfigGetCommand(message, full_command, tan); - } - else if (subcommand == "reload") - { - _hyperion->freeObjects(true); - Process::restartHyperion(); - sendErrorReply("failed to restart hyperion", full_command, tan); - } - else - { - sendErrorReply("unknown or missing subcommand", full_command, tan); - } -} - -void JsonProcessor::handleConfigSetCommand(const QJsonObject& message, const QString &command, const int tan) -{ - if(message.size() > 0) - { - if (message.contains("config")) - { - QJsonObject hyperionConfigJsonObj = message["config"].toObject(); - try - { - Q_INIT_RESOURCE(resource); - - QJsonObject schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); - - QJsonSchemaChecker schemaChecker; - schemaChecker.setSchema(schemaJson); - - QPair validate = schemaChecker.validate(hyperionConfigJsonObj); - - if (validate.first && validate.second) - { - QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj); - } - else if (!validate.first && validate.second) - { - Warning(_log,"Errors have been found in the configuration file. Automatic correction is applied"); - - QStringList schemaErrors = schemaChecker.getMessages(); - for (auto & schemaError : schemaErrors) - Info(_log, QSTRING_CSTR(schemaError)); - - hyperionConfigJsonObj = schemaChecker.getAutoCorrectedConfig(hyperionConfigJsonObj); - - if (!QJsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfigJsonObj)) - throw std::runtime_error("ERROR: can not save configuration file, aborting"); - } - else //Error in Schema - { - QString errorMsg = "ERROR: Json validation failed: \n"; - QStringList schemaErrors = schemaChecker.getMessages(); - for (auto & schemaError: schemaErrors) - { - Error(_log, "config write validation: %s", QSTRING_CSTR(schemaError)); - errorMsg += schemaError + "\n"; - } - - throw std::runtime_error(errorMsg.toStdString()); - } - sendSuccessReply(command, tan); - } - catch(const std::runtime_error& validate_error) - { - sendErrorReply("Error while validating json: " + QString(validate_error.what()), command, tan); - } - } - } - else - { - sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); - } -} - -void JsonProcessor::handleConfigGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - result["result"] = _hyperion->getQJsonConfig(); - - // send the result - emit callbackMessage(result); -} - -void JsonProcessor::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result, schemaJson, alldevices, properties; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(resource); - - // read the hyperion json schema from the resource - QString schemaFile = ":/hyperion-schema"; - - try - { - schemaJson = QJsonFactory::readSchema(schemaFile); - } - catch(const std::runtime_error& error) - { - throw std::runtime_error(error.what()); - } - - // collect all LED Devices - properties = schemaJson["properties"].toObject(); - alldevices = LedDevice::getLedDeviceSchemas(); - properties.insert("alldevices", alldevices); - - // collect all available effect schemas - QJsonObject pyEffectSchemas, pyEffectSchema; - QJsonArray in, ex; - const std::list & effectsSchemas = _hyperion->getEffectSchemas(); - for (const EffectSchema & effectSchema : effectsSchemas) - { - if (effectSchema.pyFile.mid(0, 1) == ":") - { - QJsonObject internal; - internal.insert("script", effectSchema.pyFile); - internal.insert("schemaLocation", effectSchema.schemaFile); - internal.insert("schemaContent", effectSchema.pySchema); - in.append(internal); - } - else - { - QJsonObject external; - external.insert("script", effectSchema.pyFile); - external.insert("schemaLocation", effectSchema.schemaFile); - external.insert("schemaContent", effectSchema.pySchema); - ex.append(external); - } - } - - if (!in.empty()) - pyEffectSchema.insert("internal", in); - if (!ex.empty()) - pyEffectSchema.insert("external", ex); - - pyEffectSchemas = pyEffectSchema; - properties.insert("effectSchemas", pyEffectSchemas); - - schemaJson.insert("properties", properties); - - result["result"] = schemaJson; - - // send the result - emit callbackMessage(result); -} - -void JsonProcessor::handleComponentStateCommand(const QJsonObject& message, const QString &command, const int tan) -{ - const QJsonObject & componentState = message["componentstate"].toObject(); - - QString compStr = componentState["component"].toString("invalid"); - bool compState = componentState["state"].toBool(true); - - if (compStr == "ALL" ) - { - if (hyperionIsActive() != compState) - { - std::map components = _hyperion->getComponentRegister().getRegister(); - - if (!compState) - { - JsonProcessor::_componentsPrevState = components; - } - - for(auto comp : components) - { - _hyperion->setComponentState(comp.first, compState ? JsonProcessor::_componentsPrevState[comp.first] : false); - } - - if (compState) - { - JsonProcessor::_componentsPrevState.clear(); - } - } - - sendSuccessReply(command, tan); - return; - - } - else - { - Components component = stringToComponent(compStr); - - if (hyperionIsActive()) - { - if (component != COMP_INVALID) - { - _hyperion->setComponentState(component, compState); - sendSuccessReply(command, tan); - return; - } - sendErrorReply("invalid component name", command, tan); - return; - } - sendErrorReply("can't change component state when hyperion is off", command, tan); - } -} - -void JsonProcessor::handleLedColorsCommand(const QJsonObject& message, const QString &command, const int tan) -{ - // create result - QString subcommand = message["subcommand"].toString(""); - - if (subcommand == "ledstream-start") - { - _streaming_leds_reply["success"] = true; - _streaming_leds_reply["command"] = command+"-ledstream-update"; - _streaming_leds_reply["tan"] = tan; - connect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate, Qt::UniqueConnection); - _ledcolorsLedsActive = true; - } - else if (subcommand == "ledstream-stop") - { - disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonProcessor::streamLedcolorsUpdate); - _ledcolorsLedsActive = false; - } - else if (subcommand == "imagestream-start") - { - _streaming_image_reply["success"] = true; - _streaming_image_reply["command"] = command+"-imagestream-update"; - _streaming_image_reply["tan"] = tan; - connect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, SLOT(setImage(int, const Image&, const int)) ); - } - else if (subcommand == "imagestream-stop") - { - disconnect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, 0 ); - } - else - { - sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonProcessor::handleLoggingCommand(const QJsonObject& message, const QString &command, const int tan) -{ - // create result - QString subcommand = message["subcommand"].toString(""); - _streaming_logging_reply["success"] = true; - _streaming_logging_reply["command"] = command; - _streaming_logging_reply["tan"] = tan; - - if (subcommand == "start") - { - if (!_streaming_logging_activated) - { - _streaming_logging_reply["command"] = command+"-update"; - connect(LoggerManager::getInstance(),SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE))); - Debug(_log, "log streaming activated for client %s",_peerAddress.toStdString().c_str()); // needed to trigger log sending - } - } - else if (subcommand == "stop") - { - if (_streaming_logging_activated) - { - disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0); - _streaming_logging_activated = false; - Debug(_log, "log streaming deactivated for client %s",_peerAddress.toStdString().c_str()); - - } - } - else - { - sendErrorReply("unknown subcommand",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonProcessor::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) -{ - _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleVideoModeCommand(const QJsonObject& message, const QString &command, const int tan) -{ - _hyperion->setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); - - sendSuccessReply(command, tan); -} - -void JsonProcessor::handleNotImplemented() -{ - sendErrorReply("Command not implemented"); -} - -void JsonProcessor::sendSuccessReply(const QString &command, const int tan) -{ - // create reply - QJsonObject reply; - reply["success"] = true; - reply["command"] = command; - reply["tan"] = tan; - - // send reply - emit callbackMessage(reply); - - // blacklisted commands for emitter - QVector vector; - vector << "ledcolors-imagestream-stop" << "ledcolors-imagestream-start" << "ledcolors-ledstream-stop" << "ledcolors-ledstream-start" << "logging-start" << "logging-stop"; - if(vector.indexOf(command) == -1) - { - emit pushReq(); - } -} - -void JsonProcessor::sendErrorReply(const QString &error, const QString &command, const int tan) -{ - // create reply - QJsonObject reply; - reply["success"] = false; - reply["error"] = error; - reply["command"] = command; - reply["tan"] = tan; - - // send reply - emit callbackMessage(reply); -} - - -void JsonProcessor::streamLedcolorsUpdate(const std::vector& ledColors) -{ - if ( (_led_stream_timeout+50) < QDateTime::currentMSecsSinceEpoch() && _led_stream_mutex.tryLock(0) ) - { - _led_stream_timeout = QDateTime::currentMSecsSinceEpoch(); - QJsonObject result; - QJsonArray leds; - - for(auto color = ledColors.begin(); color != ledColors.end(); ++color) - { - QJsonObject item; - item["index"] = int(color - ledColors.begin()); - item["red"] = color->red; - item["green"] = color->green; - item["blue"] = color->blue; - leds.append(item); - } - - result["leds"] = leds; - _streaming_leds_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_leds_reply); - - _led_stream_mutex.unlock(); - } -} - -void JsonProcessor::setImage(int priority, const Image & image, int duration_ms) -{ - if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) ) - { - _image_stream_timeout = QDateTime::currentMSecsSinceEpoch(); - - QImage jpgImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); - QByteArray ba; - QBuffer buffer(&ba); - buffer.open(QIODevice::WriteOnly); - jpgImage.save(&buffer, "jpg"); - - QJsonObject result; - result["image"] = "data:image/jpg;base64,"+QString(ba.toBase64()); - _streaming_image_reply["result"] = result; - emit callbackMessage(_streaming_image_reply); - - _image_stream_mutex.unlock(); - } -} - -void JsonProcessor::incommingLogMessage(Logger::T_LOG_MESSAGE msg) -{ - QJsonObject result, message; - QJsonArray messageArray; - - if (!_streaming_logging_activated) - { - _streaming_logging_activated = true; - QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); - for(int i=0; ilength(); i++) - { - message["appName"] = logBuffer->at(i).appName; - message["loggerName"] = logBuffer->at(i).loggerName; - message["function"] = logBuffer->at(i).function; - message["line"] = QString::number(logBuffer->at(i).line); - message["fileName"] = logBuffer->at(i).fileName; - message["message"] = logBuffer->at(i).message; - message["levelString"] = logBuffer->at(i).levelString; - - messageArray.append(message); - } - } - else - { - message["appName"] = msg.appName; - message["loggerName"] = msg.loggerName; - message["function"] = msg.function; - message["line"] = QString::number(msg.line); - message["fileName"] = msg.fileName; - message["message"] = msg.message; - message["levelString"] = msg.levelString; - - messageArray.append(message); - } - - result.insert("messages", messageArray); - _streaming_logging_reply["result"] = result; - - // send the result - emit callbackMessage(_streaming_logging_reply); -} - -void JsonProcessor::forceServerInfo() -{ - const QString command("serverinfo"); - const int tan = 1; - const QJsonObject obj; - handleServerInfoCommand(obj,command,tan); -} diff --git a/libsrc/utils/JsonUtils.cpp b/libsrc/utils/JsonUtils.cpp index d103978c..0040f241 100644 --- a/libsrc/utils/JsonUtils.cpp +++ b/libsrc/utils/JsonUtils.cpp @@ -9,8 +9,6 @@ #include #include -#include - namespace JsonUtils { bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError) @@ -38,13 +36,33 @@ namespace JsonUtils { } bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log) + { + QJsonDocument doc; + if(!parse(path, data, doc, log)) + return false; + + obj = doc.object(); + return true; + } + + bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log) + { + QJsonDocument doc; + if(!parse(path, data, doc, log)) + return false; + + arr = doc.array(); + return true; + } + + bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log) { //remove Comments in data QString cleanData = data; - cleanData.remove(QRegularExpression("([^:]?\\/\\/.*)")); + //cleanData .remove(QRegularExpression("([^:]?\\/\\/.*)")); QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error); + doc = QJsonDocument::fromJson(cleanData.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { @@ -63,7 +81,6 @@ namespace JsonUtils { Error(log,"Failed to parse json data from %s: Error: %s at Line: %i, Column: %i", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn); return false; } - obj = doc.object(); return true; } @@ -74,6 +91,14 @@ namespace JsonUtils { if(!readFile(schemaPath, schema, log)) return false; + if(!validate(file, json, schema, log)) + return false; + return true; + + } + + bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log) + { QJsonSchemaChecker schemaChecker; schemaChecker.setSchema(schema); if (!schemaChecker.validate(json).first) @@ -120,7 +145,7 @@ namespace JsonUtils { obj.insert(attribute, resolveRefs(attributeValue.toObject(), obj, log)); else { - qDebug() <<"ADD ATTR:VALUE"< namespace Process { - + void restartHyperion(bool asNewProcess) { Logger* log = Logger::getInstance("Process"); @@ -19,8 +19,8 @@ void restartHyperion(bool asNewProcess) << " *******************************************" << std::endl << " * hyperion will restart now *" << std::endl << " *******************************************" << std::endl << std::endl; - - + + QStringList qargs = QCoreApplication::arguments(); int size = qargs.size(); char *args[size+1]; @@ -43,7 +43,7 @@ QByteArray command_exec(QString cmd, QByteArray data) QString result = ""; std::shared_ptr pipe(popen(cmd.toLocal8Bit().constData(), "r"), pclose); - if (pipe) + if (pipe) { while (!feof(pipe.get())) { @@ -54,4 +54,4 @@ QByteArray command_exec(QString cmd, QByteArray data) return QSTRING_CSTR(result); } -}; \ No newline at end of file +}; diff --git a/libsrc/utils/RgbChannelAdjustment.cpp b/libsrc/utils/RgbChannelAdjustment.cpp index dc763ff8..84d6d059 100644 --- a/libsrc/utils/RgbChannelAdjustment.cpp +++ b/libsrc/utils/RgbChannelAdjustment.cpp @@ -21,7 +21,7 @@ RgbChannelAdjustment::~RgbChannelAdjustment() void RgbChannelAdjustment::resetInitialized() { - Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]); + //Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]); memset(_initialized, false, sizeof(_initialized)); } diff --git a/libsrc/utils/RgbTransform.cpp b/libsrc/utils/RgbTransform.cpp index 7c891dfc..2b3fd730 100644 --- a/libsrc/utils/RgbTransform.cpp +++ b/libsrc/utils/RgbTransform.cpp @@ -144,8 +144,8 @@ void RgbTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue) { // apply gamma red = _mappingR[red]; - green = _mappingG[green]; - blue = _mappingB[blue]; + green = _mappingR[green]; + blue = _mappingR[blue]; // apply brightnesss int rgbSum = red+green+blue; diff --git a/libsrc/utils/Stats.cpp b/libsrc/utils/Stats.cpp index b78d6fc2..039e8361 100644 --- a/libsrc/utils/Stats.cpp +++ b/libsrc/utils/Stats.cpp @@ -23,7 +23,7 @@ Stats::Stats() { if (!(interface.flags() & QNetworkInterface::IsLoopBack)) { - _hyperion->id = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFileName().toLocal8Bit()),QCryptographicHash::Sha1).toHex()); + _hyperion->setId(QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFileName().toLocal8Bit()),QCryptographicHash::Sha1).toHex())); _hash = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); break; } @@ -34,12 +34,12 @@ Stats::Stats() { Warning(_log, "No interface found, abort"); // fallback id - _hyperion->id = QString(QCryptographicHash::hash(_hyperion->getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); + _hyperion->setId(QString(QCryptographicHash::hash(_hyperion->getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex())); return; } // prepare content - QJsonObject config = _hyperion->getConfig(); + QJsonObject config = _hyperion->getQJsonConfig(); SysInfo::HyperionSysInfo data = SysInfo::get(); QJsonObject system; @@ -49,8 +49,8 @@ Stats::Stats() system["pVersion" ] = data.productVersion; system["pName" ] = data.prettyName; system["version" ] = QString(HYPERION_VERSION); - system["device" ] = LedDevice::activeDevice(); - system["id" ] = _hyperion->id; + system["device" ] = Hyperion::getInstance()->getActiveDevice(); + system["id" ] = _hyperion->getId(); system["hw_id" ] = _hash; system["ledCount" ] = QString::number(Hyperion::getInstance()->getLedCount()); system["comp_sm" ] = config["smoothing"].toObject().take("enable"); @@ -100,7 +100,7 @@ void Stats::sendHTTP() void Stats::sendHTTPp() { - _req.setUrl(QUrl("https://api.hyperion-project.org/api/stats/"+_hyperion->id)); + _req.setUrl(QUrl("https://api.hyperion-project.org/api/stats/"+_hyperion->getId())); _mgr.put(_req,_ba); } @@ -122,7 +122,7 @@ bool Stats::trigger(bool set) { QString path = _hyperion->getRootPath()+"/misc/"; QDir dir; - QFile file(path + _hyperion->id); + QFile file(path + _hyperion->getId()); if(set && file.open(QIODevice::ReadWrite) ) { diff --git a/libsrc/webconfig/WebConfig.cpp b/libsrc/webconfig/WebConfig.cpp deleted file mode 100644 index 36c9cf71..00000000 --- a/libsrc/webconfig/WebConfig.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "webconfig/WebConfig.h" -#include "StaticFileServing.h" - -#include - -WebConfig::WebConfig(QObject * parent) - : QObject(parent) - , _hyperion(Hyperion::getInstance()) - , _server(nullptr) -{ - Logger* log = Logger::getInstance("WEBSERVER"); - _port = WEBCONFIG_DEFAULT_PORT; - _baseUrl = WEBCONFIG_DEFAULT_PATH; - const QJsonObject config = _hyperion->getQJsonConfig(); - - bool webconfigEnable = true; - - if (config.contains("webConfig")) - { - const QJsonObject webconfigConfig = config["webConfig"].toObject(); - webconfigEnable = webconfigConfig["enable"].toBool(true); - _port = webconfigConfig["port"].toInt(_port); - _baseUrl = webconfigConfig["document_root"].toString(_baseUrl); - } - - if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) - { - QFileInfo info(_baseUrl); - if (!info.exists() || !info.isDir()) - { - Error(log, "document_root '%s' is invalid, set to default '%s'", _baseUrl.toUtf8().constData(), WEBCONFIG_DEFAULT_PATH.toUtf8().constData()); - _baseUrl = WEBCONFIG_DEFAULT_PATH; - } - } - else - _baseUrl = WEBCONFIG_DEFAULT_PATH; - - Debug(log, "WebUI initialized, document root: %s", _baseUrl.toUtf8().constData()); - if ( webconfigEnable ) - { - start(); - } -} - - -WebConfig::~WebConfig() -{ - stop(); -} - - -void WebConfig::start() -{ - if ( _server == nullptr ) - _server = new StaticFileServing (_hyperion, _baseUrl, _port, this); -} - -void WebConfig::stop() -{ - if ( _server != nullptr ) - { - delete _server; - _server = nullptr; - } -} - - diff --git a/libsrc/webconfig/CMakeLists.txt b/libsrc/webserver/CMakeLists.txt similarity index 78% rename from libsrc/webconfig/CMakeLists.txt rename to libsrc/webserver/CMakeLists.txt index 51e99367..175df60e 100644 --- a/libsrc/webconfig/CMakeLists.txt +++ b/libsrc/webserver/CMakeLists.txt @@ -1,7 +1,7 @@ # Define the current source locations -set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webconfig) -set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webconfig) +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webserver) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webserver) FILE ( GLOB WebConfig_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) FILE ( GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig/* ) @@ -13,13 +13,14 @@ ENDFOREACH() CONFIGURE_FILE(${CURRENT_SOURCE_DIR}/WebConfig.qrc.in ${CMAKE_BINARY_DIR}/WebConfig.qrc ) SET(WebConfig_RESOURCES ${CMAKE_BINARY_DIR}/WebConfig.qrc) -add_library(webconfig +add_library(webserver ${WebConfig_SOURCES} ${WebConfig_RESOURCES} ) -target_link_libraries(webconfig +target_link_libraries(webserver hyperion hyperion-utils + hyperion-api Qt5::Network ) diff --git a/libsrc/webconfig/CgiHandler.cpp b/libsrc/webserver/CgiHandler.cpp similarity index 94% rename from libsrc/webconfig/CgiHandler.cpp rename to libsrc/webserver/CgiHandler.cpp index e4e5d404..a9ddc107 100644 --- a/libsrc/webconfig/CgiHandler.cpp +++ b/libsrc/webserver/CgiHandler.cpp @@ -13,12 +13,12 @@ #include #include -CgiHandler::CgiHandler (Hyperion * hyperion, QString baseUrl, QObject * parent) +CgiHandler::CgiHandler (Hyperion * hyperion, QObject * parent) : QObject(parent) , _hyperion(hyperion) , _args(QStringList()) , _hyperionConfig(_hyperion->getQJsonConfig()) - , _baseUrl(baseUrl) + , _baseUrl() , _log(Logger::getInstance("WEBSERVER")) { } @@ -27,6 +27,11 @@ CgiHandler::~CgiHandler() { } +void CgiHandler::setBaseUrl(const QString& url) +{ + _baseUrl = url; +} + void CgiHandler::exec(const QStringList & args, QtHttpRequest * request, QtHttpReply * reply) { try diff --git a/libsrc/webconfig/CgiHandler.h b/libsrc/webserver/CgiHandler.h similarity index 83% rename from libsrc/webconfig/CgiHandler.h rename to libsrc/webserver/CgiHandler.h index 02c5aa6a..0be79812 100644 --- a/libsrc/webconfig/CgiHandler.h +++ b/libsrc/webserver/CgiHandler.h @@ -15,25 +15,24 @@ class CgiHandler : public QObject { Q_OBJECT public: - CgiHandler (Hyperion * hyperion, QString baseUrl, QObject * parent = NULL); + CgiHandler (Hyperion * hyperion, QObject * parent = NULL); virtual ~CgiHandler (void); + void setBaseUrl(const QString& url); void exec(const QStringList & args,QtHttpRequest * request, QtHttpReply * reply); - + // cgi commands void cmd_cfg_jsonserver(); void cmd_runscript (); - + private: Hyperion* _hyperion; QtHttpReply * _reply; QtHttpRequest * _request; QStringList _args; const QJsonObject & _hyperionConfig; - const QString _baseUrl; + QString _baseUrl; Logger * _log; }; #endif // CGIHANDLER_H - - diff --git a/libsrc/webconfig/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp similarity index 99% rename from libsrc/webconfig/QtHttpClientWrapper.cpp rename to libsrc/webserver/QtHttpClientWrapper.cpp index 0720abe5..9f3c5474 100644 --- a/libsrc/webconfig/QtHttpClientWrapper.cpp +++ b/libsrc/webserver/QtHttpClientWrapper.cpp @@ -159,9 +159,8 @@ void QtHttpClientWrapper::onClientDataReceived (void) { this, &QtHttpClientWrapper::onReplySendHeadersRequested); connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested); - emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request - m_parsingStatus = sendReplyToClient (&reply); + m_parsingStatus = sendReplyToClient (&reply); break; } case ParsingError: { // there was an error durin one of parsing steps diff --git a/libsrc/webconfig/QtHttpClientWrapper.h b/libsrc/webserver/QtHttpClientWrapper.h similarity index 100% rename from libsrc/webconfig/QtHttpClientWrapper.h rename to libsrc/webserver/QtHttpClientWrapper.h diff --git a/libsrc/webconfig/QtHttpHeader.cpp b/libsrc/webserver/QtHttpHeader.cpp similarity index 100% rename from libsrc/webconfig/QtHttpHeader.cpp rename to libsrc/webserver/QtHttpHeader.cpp diff --git a/libsrc/webconfig/QtHttpHeader.h b/libsrc/webserver/QtHttpHeader.h similarity index 100% rename from libsrc/webconfig/QtHttpHeader.h rename to libsrc/webserver/QtHttpHeader.h diff --git a/libsrc/webconfig/QtHttpReply.cpp b/libsrc/webserver/QtHttpReply.cpp similarity index 100% rename from libsrc/webconfig/QtHttpReply.cpp rename to libsrc/webserver/QtHttpReply.cpp diff --git a/libsrc/webconfig/QtHttpReply.h b/libsrc/webserver/QtHttpReply.h similarity index 100% rename from libsrc/webconfig/QtHttpReply.h rename to libsrc/webserver/QtHttpReply.h diff --git a/libsrc/webconfig/QtHttpRequest.cpp b/libsrc/webserver/QtHttpRequest.cpp similarity index 100% rename from libsrc/webconfig/QtHttpRequest.cpp rename to libsrc/webserver/QtHttpRequest.cpp diff --git a/libsrc/webconfig/QtHttpRequest.h b/libsrc/webserver/QtHttpRequest.h similarity index 100% rename from libsrc/webconfig/QtHttpRequest.h rename to libsrc/webserver/QtHttpRequest.h diff --git a/libsrc/webconfig/QtHttpServer.cpp b/libsrc/webserver/QtHttpServer.cpp similarity index 66% rename from libsrc/webconfig/QtHttpServer.cpp rename to libsrc/webserver/QtHttpServer.cpp index d58b6aed..bccdb07b 100644 --- a/libsrc/webconfig/QtHttpServer.cpp +++ b/libsrc/webserver/QtHttpServer.cpp @@ -53,23 +53,20 @@ QString QtHttpServer::getErrorString (void) const { } void QtHttpServer::start (quint16 port) { - if (m_sockServer->listen (QHostAddress::Any, port)) { - emit started (m_sockServer->serverPort ()); - } - else { - emit error (m_sockServer->errorString ()); - } + if(!m_sockServer->isListening()) + { + if (m_sockServer->listen (QHostAddress::Any, port)) { + emit started (m_sockServer->serverPort ()); + } + else { + emit error (m_sockServer->errorString ()); + } + } } void QtHttpServer::stop (void) { if (m_sockServer->isListening ()) { m_sockServer->close (); - // disconnect clients - const QList socks = m_socksClientsHash.keys(); - for(auto sock : socks) - { - sock->close(); - } emit stopped (); } } @@ -94,22 +91,22 @@ void QtHttpServer::setCertificates (const QList & certs) { void QtHttpServer::onClientConnected (void) { while (m_sockServer->hasPendingConnections ()) { if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) { - connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected); - if (m_useSsl) { - if (QSslSocket * ssl = qobject_cast (sock)) { - connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors); - connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted); - connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError); - connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged); - ssl->setLocalCertificateChain (m_sslCerts); - ssl->setPrivateKey (m_sslKey); - ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer); - ssl->startServerEncryption (); - } - } - QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this); - m_socksClientsHash.insert (sock, wrapper); - emit clientConnected (wrapper->getGuid ()); + connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected); + if (m_useSsl) { + if (QSslSocket * ssl = qobject_cast (sock)) { + connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors); + connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted); + connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError); + connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged); + ssl->setLocalCertificateChain (m_sslCerts); + ssl->setPrivateKey (m_sslKey); + ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer); + ssl->startServerEncryption (); + } + } + QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this); + m_socksClientsHash.insert (sock, wrapper); + emit clientConnected (wrapper->getGuid ()); } } } diff --git a/libsrc/webconfig/QtHttpServer.h b/libsrc/webserver/QtHttpServer.h similarity index 100% rename from libsrc/webconfig/QtHttpServer.h rename to libsrc/webserver/QtHttpServer.h diff --git a/libsrc/webconfig/StaticFileServing.cpp b/libsrc/webserver/StaticFileServing.cpp similarity index 63% rename from libsrc/webconfig/StaticFileServing.cpp rename to libsrc/webserver/StaticFileServing.cpp index 80e8e901..85004470 100644 --- a/libsrc/webconfig/StaticFileServing.cpp +++ b/libsrc/webserver/StaticFileServing.cpp @@ -8,66 +8,29 @@ #include #include #include -#include -#include -#include -#include #include -StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent) +StaticFileServing::StaticFileServing (Hyperion *hyperion, QObject * parent) : QObject (parent) , _hyperion(hyperion) - , _baseUrl (baseUrl) - , _cgi(hyperion, baseUrl, this) + , _baseUrl () + , _cgi(hyperion, this) , _log(Logger::getInstance("WEBSERVER")) { Q_INIT_RESOURCE(WebConfig); _mimeDb = new QMimeDatabase; - - _server = new QtHttpServer (this); - _server->setServerName (QStringLiteral ("Hyperion WebConfig")); - - connect (_server, &QtHttpServer::started, this, &StaticFileServing::onServerStarted); - connect (_server, &QtHttpServer::stopped, this, &StaticFileServing::onServerStopped); - connect (_server, &QtHttpServer::error, this, &StaticFileServing::onServerError); - connect (_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply); - - _server->start (port); } StaticFileServing::~StaticFileServing () { - _server->stop (); + } -void StaticFileServing::onServerStarted (quint16 port) +void StaticFileServing::setBaseUrl(const QString& url) { - Info(_log, "started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); - const QJsonObject & generalConfig = _hyperion->getQJsonConfig()["general"].toObject(); - const QString mDNSDescr = generalConfig["name"].toString("") + "@" + QHostInfo::localHostName() + ":" + QString::number(port); - - // txt record for zeroconf - QString id = _hyperion->id; - std::string version = HYPERION_VERSION; - std::vector > txtRecord = {{"id",id.toStdString()},{"version",version}}; - - BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); - bonjourRegister_http->registerService( - BonjourRecord(mDNSDescr, "_hyperiond-http._tcp", QString()), - port, - txtRecord - ); - Debug(_log, "Web Config mDNS responder started"); -} - -void StaticFileServing::onServerStopped () { - Info(_log, "stopped %s", _server->getServerName().toStdString().c_str()); -} - -void StaticFileServing::onServerError (QString msg) -{ - Error(_log, "%s", msg.toStdString().c_str()); + _baseUrl = url; + _cgi.setBaseUrl(url); } void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage) @@ -133,7 +96,6 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl } return; } - Q_INIT_RESOURCE(WebConfig); QFileInfo info(_baseUrl % "/" % path); if ( path == "/" || path.isEmpty() ) diff --git a/libsrc/webconfig/StaticFileServing.h b/libsrc/webserver/StaticFileServing.h similarity index 69% rename from libsrc/webconfig/StaticFileServing.h rename to libsrc/webserver/StaticFileServing.h index 42e03950..875bad4d 100644 --- a/libsrc/webconfig/StaticFileServing.h +++ b/libsrc/webserver/StaticFileServing.h @@ -1,10 +1,9 @@ #ifndef STATICFILESERVING_H #define STATICFILESERVING_H -#include #include -#include "QtHttpServer.h" +//#include "QtHttpServer.h" #include "QtHttpRequest.h" #include "QtHttpReply.h" #include "QtHttpHeader.h" @@ -17,19 +16,17 @@ class StaticFileServing : public QObject { Q_OBJECT public: - explicit StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent = nullptr); + explicit StaticFileServing (Hyperion *hyperion, QObject * parent = nullptr); virtual ~StaticFileServing (void); + void setBaseUrl(const QString& url); + public slots: - void onServerStopped (void); - void onServerStarted (quint16 port); - void onServerError (QString msg); void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); private: Hyperion * _hyperion; QString _baseUrl; - QtHttpServer * _server; QMimeDatabase * _mimeDb; CgiHandler _cgi; Logger * _log; diff --git a/libsrc/webconfig/WebConfig.qrc.in b/libsrc/webserver/WebConfig.qrc.in similarity index 100% rename from libsrc/webconfig/WebConfig.qrc.in rename to libsrc/webserver/WebConfig.qrc.in diff --git a/libsrc/webconfig/WebJsonRpc.cpp b/libsrc/webserver/WebJsonRpc.cpp similarity index 74% rename from libsrc/webconfig/WebJsonRpc.cpp rename to libsrc/webserver/WebJsonRpc.cpp index 16f4a52b..85e16fa0 100644 --- a/libsrc/webconfig/WebJsonRpc.cpp +++ b/libsrc/webserver/WebJsonRpc.cpp @@ -4,7 +4,7 @@ #include "QtHttpServer.h" #include "QtHttpClientWrapper.h" -#include +#include WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent) : QObject(parent) @@ -13,20 +13,20 @@ WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClien , _log(Logger::getInstance("HTTPJSONRPC")) { const QString client = request->getClientInfo().clientAddress.toString(); - _jsonProcessor = new JsonProcessor(client, _log, this, true); - connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebJsonRpc::handleCallback); + _jsonAPI = new JsonAPI(client, _log, this, true); + connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback); } void WebJsonRpc::handleMessage(QtHttpRequest* request) { QByteArray data = request->getRawData(); _unlocked = true; - _jsonProcessor->handleMessage(data); + _jsonAPI->handleMessage(data); } void WebJsonRpc::handleCallback(QJsonObject obj) { - // guard against wrong callbacks; TODO: Remove when JsonProcessor is more solid + // guard against wrong callbacks; TODO: Remove when JSONAPI is more solid if(!_unlocked) return; _unlocked = false; // construct reply with headers timestamp and server name diff --git a/libsrc/webconfig/WebJsonRpc.h b/libsrc/webserver/WebJsonRpc.h similarity index 89% rename from libsrc/webconfig/WebJsonRpc.h rename to libsrc/webserver/WebJsonRpc.h index 5aabfa71..d074dd2f 100644 --- a/libsrc/webconfig/WebJsonRpc.h +++ b/libsrc/webserver/WebJsonRpc.h @@ -5,7 +5,7 @@ class QtHttpServer; class QtHttpRequest; class QtHttpClientWrapper; -class JsonProcessor; +class JsonAPI; class WebJsonRpc : public QObject { Q_OBJECT @@ -18,7 +18,7 @@ private: QtHttpServer* _server; QtHttpClientWrapper* _wrapper; Logger* _log; - JsonProcessor* _jsonProcessor; + JsonAPI* _jsonAPI; bool _unlocked = false; diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp new file mode 100644 index 00000000..2298d25d --- /dev/null +++ b/libsrc/webserver/WebServer.cpp @@ -0,0 +1,99 @@ +#include "webserver/WebServer.h" +#include "StaticFileServing.h" +#include "QtHttpServer.h" + +// bonjour +#include +#include + +#include + +WebServer::WebServer(const QJsonDocument& config, QObject * parent) + : QObject(parent) + , _log(Logger::getInstance("WEBSERVER")) + , _hyperion(Hyperion::getInstance()) + , _server(new QtHttpServer (this)) +{ + _server->setServerName (QStringLiteral ("Hyperion Webserver")); + + connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted); + connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped); + connect (_server, &QtHttpServer::error, this, &WebServer::onServerError); + + // create StaticFileServing + _staticFileServing = new StaticFileServing (_hyperion, this); + connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply); + + Debug(_log, "Instance created"); + // init + handleSettingsUpdate(settings::WEBSERVER, config); +} + +WebServer::~WebServer() +{ + stop(); +} + +void WebServer::onServerStarted (quint16 port) +{ + Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); + + BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); + bonjourRegister_http->registerService("_hyperiond-http._tcp", port); +} + +void WebServer::onServerStopped () { + Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str()); +} + +void WebServer::onServerError (QString msg) +{ + Error(_log, "%s", msg.toStdString().c_str()); +} + +void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + if(type == settings::WEBSERVER) + { + const QJsonObject& obj = config.object(); + + bool webconfigEnable = obj["enable"].toBool(true); + _baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH); + + + if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) + { + QFileInfo info(_baseUrl); + if (!info.exists() || !info.isDir()) + { + Error(_log, "document_root '%s' is invalid", _baseUrl.toUtf8().constData()); + _baseUrl = WEBSERVER_DEFAULT_PATH; + } + } + else + _baseUrl = WEBSERVER_DEFAULT_PATH; + + Debug(_log, "Set document root to: %s", _baseUrl.toUtf8().constData()); + _staticFileServing->setBaseUrl(_baseUrl); + + if(_port != obj["port"].toInt(WEBSERVER_DEFAULT_PORT)) + { + _port = obj["port"].toInt(WEBSERVER_DEFAULT_PORT); + stop(); + } + if ( webconfigEnable ) + { + start(); + } + } +} + +void WebServer::start() +{ + _server->start(_port); +} + +void WebServer::stop() +{ + _server->stop(); +} diff --git a/libsrc/webconfig/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp similarity index 96% rename from libsrc/webconfig/WebSocketClient.cpp rename to libsrc/webserver/WebSocketClient.cpp index c24a796d..7ee64530 100644 --- a/libsrc/webconfig/WebSocketClient.cpp +++ b/libsrc/webserver/WebSocketClient.cpp @@ -3,7 +3,7 @@ #include "QtHttpHeader.h" #include -#include +#include #include #include @@ -23,8 +23,8 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObje const QString client = request->getClientInfo().clientAddress.toString(); // Json processor - _jsonProcessor = new JsonProcessor(client, _log, this); - connect(_jsonProcessor, &JsonProcessor::callbackMessage, this, &WebSocketClient::sendMessage); + _jsonAPI = new JsonAPI(client, _log, this); + connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage); Debug(_log, "New connection from %s", QSTRING_CSTR(client)); @@ -112,7 +112,7 @@ void WebSocketClient::handleWebSocketFrame(void) _onContinuation = false; if (_wsh.opCode == OPCODE::TEXT) { - _jsonProcessor->handleMessage(QString(_wsReceiveBuffer)); + _jsonAPI->handleMessage(QString(_wsReceiveBuffer)); } else { @@ -238,7 +238,8 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data) image.resize(width, height); memcpy(image.memptr(), data.data()+4, imgSize); - _hyperion->setImage(priority, image, duration_s*1000); + //_hyperion->registerInput(); + _hyperion->setInputImage(priority, image, duration_s*1000); } qint64 WebSocketClient::sendMessage(QJsonObject obj) diff --git a/libsrc/webconfig/WebSocketClient.h b/libsrc/webserver/WebSocketClient.h similarity index 97% rename from libsrc/webconfig/WebSocketClient.h rename to libsrc/webserver/WebSocketClient.h index 7aaf3ce3..05bfb1c7 100644 --- a/libsrc/webconfig/WebSocketClient.h +++ b/libsrc/webserver/WebSocketClient.h @@ -7,7 +7,7 @@ class QTcpSocket; class QtHttpRequest; class Hyperion; -class JsonProcessor; +class JsonAPI; class WebSocketClient : public QObject { Q_OBJECT @@ -27,7 +27,7 @@ private: QTcpSocket* _socket; Logger* _log; Hyperion* _hyperion; - JsonProcessor* _jsonProcessor; + JsonAPI* _jsonAPI; void getWsFrameHeader(WebSocketHeader* header); void sendClose(int status, QString reason = ""); diff --git a/libsrc/webconfig/WebSocketUtils.h b/libsrc/webserver/WebSocketUtils.h similarity index 100% rename from libsrc/webconfig/WebSocketUtils.h rename to libsrc/webserver/WebSocketUtils.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89a91802..428a45e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,4 +25,3 @@ endif() if(ENABLE_OSX) add_subdirectory(hyperion-osx) endif() - diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt index 48516408..feddffd1 100644 --- a/src/hyperion-aml/CMakeLists.txt +++ b/src/hyperion-aml/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries(${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient amlogic-grabber framebuffer-grabber Qt5::Core diff --git a/src/hyperion-dispmanx/CMakeLists.txt b/src/hyperion-dispmanx/CMakeLists.txt index a35dfd1c..4d4e2425 100644 --- a/src/hyperion-dispmanx/CMakeLists.txt +++ b/src/hyperion-dispmanx/CMakeLists.txt @@ -34,7 +34,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient dispmanx-grabber ${Dispmanx_LIBRARIES} Qt5::Core diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt index 452604d6..83f69f72 100644 --- a/src/hyperion-framebuffer/CMakeLists.txt +++ b/src/hyperion-framebuffer/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient framebuffer-grabber Qt5::Core Qt5::Gui diff --git a/src/hyperion-osx/CMakeLists.txt b/src/hyperion-osx/CMakeLists.txt index 69b78ff4..8f234ebe 100644 --- a/src/hyperion-osx/CMakeLists.txt +++ b/src/hyperion-osx/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries( ${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient osx-grabber Qt5::Core Qt5::Gui diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 9bf50daa..d9e81d24 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -275,7 +275,8 @@ void JsonConnection::clearAll() // create command QJsonObject command; - command["command"] = QString("clearall"); + command["command"] = QString("clear"); + command["priority"] = -1; // send command message QJsonObject reply = sendMessage(command); diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index f768dd0c..6ff41a81 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -28,7 +28,7 @@ target_link_libraries(${PROJECT_NAME} commandline blackborder hyperion-utils - protoserver + protoclient Qt5::Core Qt5::Gui Qt5::Network diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 5eb4adc3..e343a340 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -60,8 +60,6 @@ int main(int argc, char** argv) SwitchOption & argVideoStandard= parser.add>('v', "video-standard", "The used video standard. Valid values are PAL, NTSC, SECAM or no-change. [default: %1]", "no-change"); SwitchOption & argPixelFormat = parser.add> (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, RGB32 or no-change. [default: %1]", "no-change"); IntOption & argInput = parser.add (0x0, "input", "Input channel (optional)", "-1"); - IntOption & argWidth = parser.add (0x0, "width", "Try to set the width of the video input [default: %1]", "-1"); - IntOption & argHeight = parser.add (0x0, "height", "Try to set the height of the video input [default: %1]", "-1"); IntOption & argCropWidth = parser.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0"); IntOption & argCropHeight = parser.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0"); IntOption & argCropLeft = parser.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); @@ -69,7 +67,6 @@ int main(int argc, char** argv) IntOption & argCropTop = parser.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); IntOption & argCropBottom = parser.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); IntOption & argSizeDecimation = parser.add ('s', "size-decimator", "Decimation factor for the output size [default=%1]", "1"); - IntOption & argFrameDecimation = parser.add ('f', "frame-decimator", "Decimation factor for the video frames [default=%1]", "1"); BooleanOption & argScreenshot = parser.add(0x0, "screenshot", "Take a single screenshot, save it to file and quit"); BooleanOption & argSignalDetection = parser.add('s', "signal-detection-disabled", "disable signal detection"); @@ -115,10 +112,6 @@ int main(int argc, char** argv) argInput.getInt(parser), argVideoStandard.switchValue(parser), argPixelFormat.switchValue(parser), - argWidth.getInt(parser), - argHeight.getInt(parser), - std::max(1, argFrameDecimation.getInt(parser)), - std::max(1, argSizeDecimation.getInt(parser)), std::max(1, argSizeDecimation.getInt(parser))); // set signal detection diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index a38bdfda..7e773598 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -29,7 +29,7 @@ target_link_libraries(${PROJECT_NAME} blackborder commandline hyperion-utils - protoserver + protoclient x11-grabber ${X11_LIBRARIES} ${X11_Xrender_LIB} diff --git a/src/hyperion-x11/X11Wrapper.cpp b/src/hyperion-x11/X11Wrapper.cpp index 83c39961..f78335c8 100644 --- a/src/hyperion-x11/X11Wrapper.cpp +++ b/src/hyperion-x11/X11Wrapper.cpp @@ -2,9 +2,9 @@ // Hyperion-X11 includes #include "X11Wrapper.h" -X11Wrapper::X11Wrapper(int grabInterval, bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) : +X11Wrapper::X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) : _timer(this), - _grabber(useXGetImage, cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation) + _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) { _timer.setSingleShot(false); _timer.setInterval(grabInterval); @@ -36,7 +36,7 @@ bool X11Wrapper::displayInit() void X11Wrapper::capture() { - _grabber.grabFrame(_screenshot, false); + _grabber.grabFrame(_screenshot, true); emit sig_screenshot(_screenshot); } diff --git a/src/hyperion-x11/X11Wrapper.h b/src/hyperion-x11/X11Wrapper.h index 9ec59c92..8f014f47 100644 --- a/src/hyperion-x11/X11Wrapper.h +++ b/src/hyperion-x11/X11Wrapper.h @@ -12,7 +12,7 @@ class X11Wrapper : public QObject { Q_OBJECT public: - X11Wrapper(int grabInterval, bool useXGetImage, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); const Image & getScreenshot(); diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp index 2d0ac3f1..15f94afb 100644 --- a/src/hyperion-x11/hyperion-x11.cpp +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -33,7 +33,6 @@ int main(int argc, char ** argv) Parser parser("X11 capture application for Hyperion"); IntOption & argFps = parser.add ('f', "framerate", "Capture frame rate [default: %1]", "10"); - BooleanOption & argXGetImage = parser.add('x', "xgetimage", "Use XGetImage instead of XRender"); IntOption & argCropWidth = parser.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0"); IntOption & argCropHeight = parser.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0"); IntOption & argCropLeft = parser.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); @@ -59,13 +58,11 @@ int main(int argc, char ** argv) // Create the X11 grabbing stuff X11Wrapper x11Wrapper( 1000 / argFps.getInt(parser), - parser.isSet(argXGetImage), parser.isSet(argCropLeft) ? argCropLeft.getInt(parser) : argCropWidth.getInt(parser), parser.isSet(argCropRight) ? argCropRight.getInt(parser) : argCropWidth.getInt(parser), parser.isSet(argCropTop) ? argCropTop.getInt(parser) : argCropHeight.getInt(parser), parser.isSet(argCropBottom) ? argCropBottom.getInt(parser) : argCropHeight.getInt(parser), - argSizeDecimation.getInt(parser), // horizontal decimation - argSizeDecimation.getInt(parser)); // vertical decimation + argSizeDecimation.getInt(parser)); // pixel decimation if (!x11Wrapper.displayInit()) return -1; diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index d6fb90f9..3a78377a 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -1,4 +1,7 @@ +find_package(PythonLibs 3.4 REQUIRED) +include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + add_executable(hyperiond hyperiond.h systray.h @@ -15,8 +18,10 @@ target_link_libraries(hyperiond boblightserver udplistener protoserver - webconfig + webserver bonjour + python + ${PYTHON_LIBRARIES} ) if (ENABLE_DISPMANX) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 0eb9e90d..d68220cc 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -16,80 +15,136 @@ #include #include -#include "HyperionConfig.h" - -#include #include #include #include -#include -#include -#include -#include #include #include #include #include - +#include +#include +#include // Required to determine the cmake options #include "hyperiond.h" -#include +// bonjour browser +#include + +// settings +#include + +// Init Python +#include + +HyperionDaemon* HyperionDaemon::daemon = nullptr; HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObject *parent) : QObject(parent) - , _log(Logger::getInstance("MAIN")) + , _log(Logger::getInstance("DAEMON")) + , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) + , _pyInit(new PythonInit()) + , _webserver(nullptr) , _jsonServer(nullptr) , _protoServer(nullptr) , _boblightServer(nullptr) , _udpListener(nullptr) , _v4l2Grabbers() , _dispmanx(nullptr) -#ifdef ENABLE_X11 , _x11Grabber(nullptr) -#endif , _amlGrabber(nullptr) , _fbGrabber(nullptr) , _osxGrabber(nullptr) , _hyperion(nullptr) , _stats(nullptr) + , _currVideoMode(VIDEO_2D) { - loadConfig(configFile); + HyperionDaemon::daemon = this; + // init settings + _settingsManager = new SettingsManager(0,configFile); + + const QJsonObject& logConfig = _settingsManager->getSetting(settings::LOGGER).object(); if (Logger::getLogLevel() == Logger::WARNING) { - if (_qconfig.contains("logger")) - { - const QJsonObject & logConfig = _qconfig["logger"].toObject(); - std::string level = logConfig["level"].toString("warn").toStdString(); // silent warn verbose debug - if (level == "silent") Logger::setLogLevel(Logger::OFF); - else if (level == "warn") Logger::setLogLevel(Logger::WARNING); - else if (level == "verbose") Logger::setLogLevel(Logger::INFO); - else if (level == "debug") Logger::setLogLevel(Logger::DEBUG); - else Error(Logger::getInstance("LOGGER"), "log level '%s' used in config is unknown. valid: silent warn verbose debug", level.c_str()); - - } + std::string level = logConfig["level"].toString("warn").toStdString(); // silent warn verbose debug + if (level == "silent") Logger::setLogLevel(Logger::OFF); + else if (level == "warn") Logger::setLogLevel(Logger::WARNING); + else if (level == "verbose") Logger::setLogLevel(Logger::INFO); + else if (level == "debug") Logger::setLogLevel(Logger::DEBUG); } else { - WarningIf(_qconfig.contains("logger"), Logger::getInstance("LOGGER"), "Logger settings overridden by command line argument"); + Warning(Logger::getInstance("LOGGER"), "Logger settings overridden by command line argument"); } - _hyperion = Hyperion::initInstance(_qconfig, configFile, rootPath); + _hyperion = Hyperion::initInstance(this, 0, configFile, rootPath); Info(_log, "Hyperion initialized"); + + connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects())); + // listen for setting changes + connect(_hyperion, &Hyperion::settingsChanged, this, &HyperionDaemon::settingsChanged); + // listen for setting changes of framegrabber and v4l2 + connect(this, &HyperionDaemon::settingsChanged, this, &HyperionDaemon::handleSettingsUpdate); + // forward system and v4l images to Hyperion + connect(this, &HyperionDaemon::systemImage, _hyperion, &Hyperion::systemImage); + connect(this, &HyperionDaemon::v4lImage, _hyperion, &Hyperion::v4lImage); + // forward videoModes from Hyperion to Daemon evaluation + connect(_hyperion, &Hyperion::videoMode, this, &HyperionDaemon::setVideoMode); + // forward videoMode changes from Daemon to Hyperion + connect(this, &HyperionDaemon::videoMode, _hyperion, &Hyperion::newVideoMode); + + // ---- grabber ----- + #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) + Warning(_log, "No platform capture can be instantiated, because all grabbers have been left out from the build"); + #endif + // init system capture (framegrabber) + handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + // init v4l2 capture + handleSettingsUpdate(settings::V4L2, getSetting(settings::V4L2)); + // ---- network services ----- + startNetworkServices(); } HyperionDaemon::~HyperionDaemon() { freeObjects(); delete _hyperion; + delete _settingsManager; + delete _pyInit; +} + +quint16 HyperionDaemon::getWebServerPort() +{ + return _webserver->getPort(); +} + +void HyperionDaemon::setVideoMode(const VideoMode& mode) +{ + if(_currVideoMode != mode) + { + _currVideoMode = mode; + emit videoMode(mode); + } +} + +const QJsonDocument HyperionDaemon::getSetting(const settings::type &type) +{ + return _settingsManager->getSetting(type); } void HyperionDaemon::freeObjects() { _hyperion->clearall(true); - Debug(_log, "destroy grabbers and network stuff"); + // destroy network first as a client might want to access pointers + delete _webserver; + delete _jsonServer; + delete _protoServer; + delete _boblightServer; + delete _udpListener; + + delete _bonjourBrowserWrapper; delete _amlGrabber; delete _dispmanx; delete _fbGrabber; @@ -98,17 +153,15 @@ void HyperionDaemon::freeObjects() { delete grabber; } - delete _jsonServer; - delete _protoServer; - delete _boblightServer; - delete _udpListener; delete _stats; _v4l2Grabbers.clear(); + _bonjourBrowserWrapper = nullptr; _amlGrabber = nullptr; _dispmanx = nullptr; _fbGrabber = nullptr; _osxGrabber = nullptr; + _webserver = nullptr; _jsonServer = nullptr; _protoServer = nullptr; _boblightServer = nullptr; @@ -116,332 +169,159 @@ void HyperionDaemon::freeObjects() _stats = nullptr; } -void HyperionDaemon::run() -{ - // ---- network services ----- - startNetworkServices(); - - // ---- grabber ----- - createGrabberV4L2(); - createSystemFrameGrabber(); - - #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) - WarningIf(_qconfig.contains("framegrabber"), _log, "No grabber can be instantiated, because all grabbers have been left out from the build"); - #endif - Info(_log, "Hyperion started"); - - connect(_hyperion,SIGNAL(closing()),this,SLOT(freeObjects())); - - startInitialEffect(); -} - -void HyperionDaemon::loadConfig(const QString & configFile) -{ - Info(_log, "Selected configuration file: %s", QSTRING_CSTR(configFile)); - - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(resource); - - // read the json schema from the resource - QString schemaFile = ":/hyperion-schema"; - QJsonObject schemaJson; - try - { - //QJsonObject obj; - //JsonUtils::readSchema(schemaFile, obj, _log); - schemaJson = QJsonFactory::readSchema(schemaFile); - } - catch(const std::runtime_error& error) - { - throw std::runtime_error(error.what()); - } - - QJsonSchemaChecker schemaChecker; - schemaChecker.setSchema(schemaJson); - - if(!JsonUtils::readFile(configFile, _qconfig, _log)) - throw std::runtime_error("Failed to load config!"); - - // validate config with schema and correct it if required - QPair validate = schemaChecker.validate(_qconfig); - - // errors in schema syntax, abort - if (!validate.second) - { - foreach (auto & schemaError, schemaChecker.getMessages()) - Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); - - throw std::runtime_error("ERROR: Hyperion schema has errors!"); - } - // errors in configuration, correct it! - if (!validate.first) - { - Warning(_log,"Errors have been found in the configuration file. Automatic correction has been applied"); - _qconfig = schemaChecker.getAutoCorrectedConfig(_qconfig); - - foreach (auto & schemaError, schemaChecker.getMessages()) - Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); - - if (!JsonUtils::write(configFile, _qconfig, _log)) - throw std::runtime_error("ERROR: Can't save configuration file, aborting"); - } -} - - -void HyperionDaemon::startInitialEffect() -{ - #define FGCONFIG_ARRAY fgColorConfig.toArray() - #define BGCONFIG_ARRAY bgColorConfig.toArray() - - Hyperion *hyperion = Hyperion::getInstance(); - - // create boot sequence - const QJsonObject & FGEffectConfig = _qconfig["foregroundEffect"].toObject(); - const QJsonObject & BGEffectConfig = _qconfig["backgroundEffect"].toObject(); - const int FG_PRIORITY = 0; - const int DURATION_INFINITY = 0; - const int BG_PRIORITY = PriorityMuxer::LOWEST_PRIORITY-1; - - // clear the leds - hyperion->setColor(FG_PRIORITY, ColorRgb::BLACK, 100, false); - - // initial foreground effect/color - if (FGEffectConfig["enable"].toBool(true)) - { - const QString fgTypeConfig = FGEffectConfig["type"].toString("effect"); - const QString fgEffectConfig = FGEffectConfig["effect"].toString("Rainbow swirl fast"); - const QJsonValue fgColorConfig = FGEffectConfig["color"]; - int default_fg_duration_ms = 3000; - int fg_duration_ms = FGEffectConfig["duration_ms"].toInt(default_fg_duration_ms); - if (fg_duration_ms == DURATION_INFINITY) - { - fg_duration_ms = default_fg_duration_ms; - Warning(_log, "foreground effect duration 'infinity' is forbidden, set to default value %d ms",default_fg_duration_ms); - } - if ( fgTypeConfig.contains("color") ) - { - ColorRgb fg_color = { - (uint8_t)FGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(2).toInt(0) - }; - hyperion->setColor(FG_PRIORITY, fg_color, fg_duration_ms, false); - Info(_log,"Inital foreground color set (%d %d %d)",fg_color.red,fg_color.green,fg_color.blue); - } - else - { - int result = hyperion->setEffect(fgEffectConfig, FG_PRIORITY, fg_duration_ms); - Info(_log,"Inital foreground effect '%s' %s", QSTRING_CSTR(fgEffectConfig), ((result == 0) ? "started" : "failed")); - } - } - // initial background effect/color - if (BGEffectConfig["enable"].toBool(true)) - { - const QString bgTypeConfig = BGEffectConfig["type"].toString("effect"); - const QString bgEffectConfig = BGEffectConfig["effect"].toString("Warm mood blobs"); - const QJsonValue bgColorConfig = BGEffectConfig["color"]; - if (bgTypeConfig.contains("color")) - { - ColorRgb bg_color = { - (uint8_t)BGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(2).toInt(0) - }; - hyperion->setColor(BG_PRIORITY, bg_color, DURATION_INFINITY, false); - Info(_log,"Inital background color set (%d %d %d)",bg_color.red,bg_color.green,bg_color.blue); - } - else - { - int result = hyperion->setEffect(bgEffectConfig, BG_PRIORITY, DURATION_INFINITY); - Info(_log,"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); - } - } - - #undef FGCONFIG_ARRAY - #undef BGCONFIG_ARRAY -} - void HyperionDaemon::startNetworkServices() { - // Create Stats + // Create Stats before network services _stats = new Stats(); - // Create Json server if configuration is present - unsigned int jsonPort = 19444; - if (_qconfig.contains("jsonServer")) - { - const QJsonObject & jsonServerConfig = _qconfig["jsonServer"].toObject(); - //jsonEnable = jsonServerConfig.get("enable", true).asBool(); - jsonPort = jsonServerConfig["port"].toInt(jsonPort); - } + // Create Json server + _jsonServer = new JsonServer(getSetting(settings::JSONSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _jsonServer, &JsonServer::handleSettingsUpdate); - _jsonServer = new JsonServer(jsonPort); - Info(_log, "Json server created and started on port %d", _jsonServer->getPort()); + // Create Proto server + _protoServer = new ProtoServer(getSetting(settings::PROTOSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate); + //QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SLOT(setVideoMode(VideoMode))); - // Create Proto server if configuration is present - unsigned int protoPort = 19445; - if (_qconfig.contains("protoServer")) - { - const QJsonObject & protoServerConfig = _qconfig["protoServer"].toObject(); - //protoEnable = protoServerConfig.get("enable", true).asBool(); - protoPort = protoServerConfig["port"].toInt(protoPort); - } + // boblight server + _boblightServer = new BoblightServer(getSetting(settings::BOBLSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate); - _protoServer = new ProtoServer(protoPort); - QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _protoServer, SIGNAL(videoMode(VideoMode))); - Info(_log, "Proto server created and started on port %d", _protoServer->getPort()); + // Create UDP listener + _udpListener = new UDPListener(getSetting(settings::UDPLISTENER)); + connect(this, &HyperionDaemon::settingsChanged, _udpListener, &UDPListener::handleSettingsUpdate); - // Create Boblight server if configuration is present - bool boblightConfigured = _qconfig.contains("boblightServer"); - - const QJsonObject & boblightServerConfig = _qconfig["boblightServer"].toObject(); - _boblightServer = new BoblightServer( - boblightServerConfig["priority"].toInt(710), - boblightServerConfig["port"].toInt(19333) ); - Debug(_log, "Boblight server created"); - - if ( boblightConfigured && boblightServerConfig["enable"].toBool(true)) - { - _boblightServer->start(); - } - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_BOBLIGHTSERVER, _boblightServer->componentState()); - connect( Hyperion::getInstance(), SIGNAL(componentStateChanged(hyperion::Components,bool)), _boblightServer, SLOT(componentStateChanged(hyperion::Components,bool))); - - // Create UDP listener if configuration is present - bool udpListenerConfigured = _qconfig.contains("udpListener"); - const QJsonObject & udpListenerConfig = _qconfig["udpListener"].toObject(); - _udpListener = new UDPListener( - udpListenerConfig["priority"].toInt(700), - udpListenerConfig["timeout"].toInt(10000), - udpListenerConfig["address"].toString(""), - udpListenerConfig["port"].toInt(2801), - udpListenerConfig["shared"].toBool(false)); - - Debug(_log, "UDP listener created"); - - if ( udpListenerConfigured && udpListenerConfig["enable"].toBool(true)) - { - _udpListener->start(); - } - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_UDPLISTENER, _udpListener->componentState()); - connect( Hyperion::getInstance(), SIGNAL(componentStateChanged(hyperion::Components,bool)), _udpListener, SLOT(componentStateChanged(hyperion::Components,bool))); - - // zeroconf description - $leddevicename@$hostname - const QJsonObject & generalConfig = _qconfig["general"].toObject(); - const QString mDNSDescr = generalConfig["name"].toString("") + "@" + QHostInfo::localHostName(); - // txt record for zeroconf - QString id = _hyperion->id; - std::string version = HYPERION_VERSION; - std::vector > txtRecord = {{"id",id.toStdString()},{"version",version}}; - - // zeroconf udp listener - if (_udpListener != nullptr) - { - BonjourServiceRegister *bonjourRegister_udp = new BonjourServiceRegister(); - bonjourRegister_udp->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_udpListener->getPort()), "_hyperiond-udp._udp", QString()), _udpListener->getPort(), txtRecord); - Debug(_log, "UDP LIstener mDNS responder started"); - } - - // zeroconf json - BonjourServiceRegister *bonjourRegister_json = new BonjourServiceRegister(); - bonjourRegister_json->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_jsonServer->getPort()), "_hyperiond-json._tcp", QString()), _jsonServer->getPort(), txtRecord); - Debug(_log, "Json mDNS responder started"); - - // zeroconf proto - BonjourServiceRegister *bonjourRegister_proto = new BonjourServiceRegister(); - bonjourRegister_proto->registerService( - BonjourRecord(mDNSDescr + ":" + QString::number(_jsonServer->getPort()), "_hyperiond-proto._tcp", QString()), _protoServer->getPort(), txtRecord); - Debug(_log, "Proto mDNS responder started"); + // Create Webserver + _webserver = new WebServer(getSetting(settings::WEBSERVER)); + connect(this, &HyperionDaemon::settingsChanged, _webserver, &WebServer::handleSettingsUpdate); } - -void HyperionDaemon::createSystemFrameGrabber() +void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - if (_qconfig.contains("framegrabber")) + if(type == settings::SYSTEMCAPTURE) { - const QJsonObject & grabberConfig = _qconfig["framegrabber"].toObject(); -// if (grabberConfig["enable"].toBool(true)) + const QJsonObject & grabberConfig = config.object(); + + _grabber_width = grabberConfig["width"].toInt(96); + _grabber_height = grabberConfig["height"].toInt(96); + _grabber_frequency = grabberConfig["frequency_Hz"].toInt(10); + + _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); + _grabber_cropRight = grabberConfig["cropRight"].toInt(0); + _grabber_cropTop = grabberConfig["cropTop"].toInt(0); + _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); + + #ifdef ENABLE_OSX + QString type = "osx"; + #else + QString type = grabberConfig["type"].toString("auto"); + #endif + + // auto eval of type + if ( type == "auto" ) { - _grabber_width = grabberConfig["width"].toInt(96); - _grabber_height = grabberConfig["height"].toInt(96); - _grabber_frequency = grabberConfig["frequency_Hz"].toInt(10); - _grabber_priority = grabberConfig["priority"].toInt(900); - - _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); - _grabber_cropRight = grabberConfig["cropRight"].toInt(0); - _grabber_cropTop = grabberConfig["cropTop"].toInt(0); - _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); - - #ifdef ENABLE_OSX - QString type = "osx"; - #else - QString type = grabberConfig["type"].toString("auto"); - #endif - - // auto eval of type - if ( type == "auto" ) + // dispmanx -> on raspi + if (QFile::exists("/dev/vchiq")) { - // dispmanx -> on raspi - // TODO currently a compile option - #ifdef ENABLE_DISPMANX - if (true) - #else - if (false) - #endif - { - type = "dispmanx"; - } - // amlogic -> /dev/amvideo exists - else if ( QFile::exists("/dev/amvideo") && ( QFile::exists("/dev/amvideocap0") || QFile::exists("/dev/ge2d") ) ) - { - type = "amlogic"; - } - // x11 -> if DISPLAY is set - else if (getenv("DISPLAY") != NULL ) - { - type = "x11"; - } - // framebuffer -> if nothing other applies - else - { - type = "framebuffer"; - } - Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type)); + type = "dispmanx"; } - - bool grabberCompState = grabberConfig["enable"].toBool(true); - if (type == "") { Info( _log, "screen capture device disabled"); grabberCompState = false; } - else if (type == "framebuffer") createGrabberFramebuffer(grabberConfig); - else if (type == "dispmanx") createGrabberDispmanx(); - else if (type == "amlogic") createGrabberAmlogic(); - else if (type == "osx") createGrabberOsx(grabberConfig); - else if (type == "x11") createGrabberX11(grabberConfig); - else { Warning( _log, "unknown framegrabber type '%s'", QSTRING_CSTR(type)); grabberCompState = false; } - -// _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_GRABBER, grabberCompState); - _hyperion->setComponentState(hyperion::COMP_GRABBER, grabberCompState ); + // amlogic -> /dev/amvideo exists + else if ( QFile::exists("/dev/amvideo") && ( QFile::exists("/dev/amvideocap0") || QFile::exists("/dev/ge2d") ) ) + { + type = "amlogic"; + } + // x11 -> if DISPLAY is set + else if (getenv("DISPLAY") != NULL ) + { + type = "x11"; + } + // framebuffer -> if nothing other applies + else + { + type = "framebuffer"; + } + Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type)); } + + if (type == "") { Info( _log, "screen capture device disabled"); } + else if (type == "framebuffer" && _fbGrabber == nullptr) createGrabberFramebuffer(grabberConfig); + else if (type == "dispmanx" && _dispmanx == nullptr) createGrabberDispmanx(); + else if (type == "amlogic" && _amlGrabber == nullptr) createGrabberAmlogic(); + else if (type == "osx" && _osxGrabber == nullptr) createGrabberOsx(grabberConfig); + else if (type == "x11" && _x11Grabber == nullptr) createGrabberX11(grabberConfig); + else { Warning( _log, "unknown framegrabber type '%s'", QSTRING_CSTR(type)); } + } + else if(type == settings::V4L2) + { + // stop + if(_v4l2Grabbers.size()>0) + return; + + unsigned v4lEnableCount = 0; + + const QJsonArray & v4lArray = config.array(); + for ( signed idx=0; idxsetSignalThreshold( + grabberConfig["redSignalThreshold"].toDouble(0.0)/100.0, + grabberConfig["greenSignalThreshold"].toDouble(0.0)/100.0, + grabberConfig["blueSignalThreshold"].toDouble(0.0)/100.0); + grabber->setCropping( + grabberConfig["cropLeft"].toInt(0), + grabberConfig["cropRight"].toInt(0), + grabberConfig["cropTop"].toInt(0), + grabberConfig["cropBottom"].toInt(0)); + grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); + grabber->setSignalDetectionOffset( + grabberConfig["sDHOffsetMin"].toDouble(0.25), + grabberConfig["sDVOffsetMin"].toDouble(0.25), + grabberConfig["sDHOffsetMax"].toDouble(0.75), + grabberConfig["sDVOffsetMax"].toDouble(0.75)); + Debug(_log, "V4L2 grabber created"); + + // connect to HyperionDaemon signal + connect(grabber, &V4L2Wrapper::systemImage, this, &HyperionDaemon::v4lImage); + connect(this, &HyperionDaemon::videoMode, grabber, &V4L2Wrapper::setVideoMode); + connect(this, &HyperionDaemon::settingsChanged, grabber, &V4L2Wrapper::handleSettingsUpdate); + + if (grabber->start()) + { + Info(_log, "V4L2 grabber started"); + } + _v4l2Grabbers.push_back(grabber); + #endif + } + + ErrorIf( (v4lEnableCount>0 && _v4l2Grabbers.size()==0), _log, "The v4l2 grabber can not be instantiated, because it has been left out from the build"); } } - void HyperionDaemon::createGrabberDispmanx() { #ifdef ENABLE_DISPMANX - _dispmanx = new DispmanxWrapper(_grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _dispmanx = new DispmanxWrapper(_grabber_width, _grabber_height, _grabber_frequency); _dispmanx->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_hyperion, SIGNAL(videoMode(VideoMode)), _dispmanx, SLOT(setVideoMode(VideoMode))); - QObject::connect(_dispmanx, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); - QObject::connect(_dispmanx, SIGNAL(emitImage(int, const Image&, const int)), _hyperion, SLOT(setImage(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _dispmanx, &DispmanxWrapper::setVideoMode); + connect(_dispmanx, &DispmanxWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _dispmanx, &DispmanxWrapper::handleSettingsUpdate); _dispmanx->start(); Info(_log, "DISPMANX frame grabber created and started"); #else - ErrorIf(_qconfig.contains("framegrabber"), _log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build"); + Error( _log, "The dispmanx framegrabber can not be instantiated, because it has been left out from the build"); #endif } @@ -449,10 +329,13 @@ void HyperionDaemon::createGrabberDispmanx() void HyperionDaemon::createGrabberAmlogic() { #ifdef ENABLE_AMLOGIC - _amlGrabber = new AmlogicWrapper(_grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _amlGrabber = new AmlogicWrapper(_grabber_width, _grabber_height, _grabber_frequency); _amlGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_amlGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _amlGrabber, &AmlogicWrapper::setVideoMode); + connect(_amlGrabber, &AmlogicWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _amlGrabber, &AmlogicWrapper::handleSettingsUpdate); _amlGrabber->start(); Info(_log, "AMLOGIC grabber created and started"); @@ -465,14 +348,15 @@ void HyperionDaemon::createGrabberX11(const QJsonObject & grabberConfig) { #ifdef ENABLE_X11 _x11Grabber = new X11Wrapper( - grabberConfig["useXGetImage"].toBool(false), _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom, - grabberConfig["horizontalPixelDecimation"].toInt(8), - grabberConfig["verticalPixelDecimation"].toInt(8), - _grabber_frequency, _grabber_priority ); + grabberConfig["pixelDecimation"].toInt(8), + _grabber_frequency ); _x11Grabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_x11Grabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _x11Grabber, &X11Wrapper::setVideoMode); + connect(_x11Grabber, &X11Wrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _x11Grabber, &X11Wrapper::handleSettingsUpdate); _x11Grabber->start(); Info(_log, "X11 grabber created and started"); @@ -488,9 +372,12 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject & grabberConfig) // Construct and start the framebuffer grabber if the configuration is present _fbGrabber = new FramebufferWrapper( grabberConfig["device"].toString("/dev/fb0"), - _grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _grabber_width, _grabber_height, _grabber_frequency); _fbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); - QObject::connect(_fbGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _fbGrabber, &FramebufferWrapper::setVideoMode); + connect(_fbGrabber, &FramebufferWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _fbGrabber, &FramebufferWrapper::handleSettingsUpdate); _fbGrabber->start(); Info(_log, "Framebuffer grabber created and started"); @@ -506,9 +393,12 @@ void HyperionDaemon::createGrabberOsx(const QJsonObject & grabberConfig) // Construct and start the osx grabber if the configuration is present _osxGrabber = new OsxWrapper( grabberConfig["display"].toInt(0), - _grabber_width, _grabber_height, _grabber_frequency, _grabber_priority); + _grabber_width, _grabber_height, _grabber_frequency); - QObject::connect(_osxGrabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + // connect to HyperionDaemon signal + connect(this, &HyperionDaemon::videoMode, _osxGrabber, &OsxWrapper::setVideoMode); + connect(_osxGrabber, &OsxWrapper::systemImage, this, &HyperionDaemon::systemImage); + connect(this, &HyperionDaemon::settingsChanged, _osxGrabber, &OsxWrapper::handleSettingsUpdate); _osxGrabber->start(); Info(_log, "OSX grabber created and started"); @@ -516,65 +406,3 @@ void HyperionDaemon::createGrabberOsx(const QJsonObject & grabberConfig) Error(_log, "The osx grabber can not be instantiated, because it has been left out from the build"); #endif } - - -void HyperionDaemon::createGrabberV4L2() -{ - // construct and start the v4l2 grabber if the configuration is present - bool v4lConfigured = _qconfig.contains("grabberV4L2"); - bool v4lStarted = false; - unsigned v4lEnableCount = 0; - - if (_qconfig["grabberV4L2"].isArray()) - { - const QJsonArray & v4lArray = _qconfig["grabberV4L2"].toArray(); - for ( signed idx=0; idxsetCropping( - grabberConfig["cropLeft"].toInt(0), - grabberConfig["cropRight"].toInt(0), - grabberConfig["cropTop"].toInt(0), - grabberConfig["cropBottom"].toInt(0)); - grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); - grabber->setSignalDetectionOffset( - grabberConfig["sDHOffsetMin"].toDouble(0.25), - grabberConfig["sDVOffsetMin"].toDouble(0.25), - grabberConfig["sDHOffsetMax"].toDouble(0.75), - grabberConfig["sDVOffsetMax"].toDouble(0.75)); - Debug(_log, "V4L2 grabber created"); - - QObject::connect(grabber, SIGNAL(emitImage(int, const Image&, const int)), _protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int))); - - if (enableV4l && grabber->start()) - { - v4lStarted = true; - Info(_log, "V4L2 grabber started"); - } - _v4l2Grabbers.push_back(grabber); - #endif - } - } - - ErrorIf( (v4lEnableCount>0 && _v4l2Grabbers.size()==0), _log, "The v4l2 grabber can not be instantiated, because it has been left out from the build"); - _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, (_v4l2Grabbers.size()>0 && v4lEnableCount>0 && v4lStarted) ); -} diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index 9f4f68dd..8640fb4d 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -40,15 +40,23 @@ #endif #include +#include +#include -#include -#include -#include -#include -#include - +// settings management +#include +class Hyperion; class SysTray; +class JsonServer; +class ProtoServer; +class BoblightServer; +class UDPListener; +class Stats; +class BonjourBrowserWrapper; +class WebServer; +class SettingsManager; +class PythonInit; class HyperionDaemon : public QObject { @@ -60,19 +68,60 @@ public: HyperionDaemon(QString configFile, QString rootPath, QObject *parent=nullptr); ~HyperionDaemon(); - void loadConfig(const QString & configFile); - void run(); + quint16 getWebServerPort(); + /// + /// @brief Get the current videoMode + /// + const VideoMode & getVideoMode() { return _currVideoMode; }; + + /// + /// @brief get the settings + /// + const QJsonDocument getSetting(const settings::type& type); - void startInitialEffect(); void startNetworkServices(); - // grabber creators - void createGrabberV4L2(); - void createSystemFrameGrabber(); + static HyperionDaemon* getInstance() { return daemon; }; + static HyperionDaemon* daemon; public slots: void freeObjects(); +signals: + /// + /// @brief PIPE settings events from Hyperion class to HyperionDaemon components + /// + void settingsChanged(const settings::type& type, const QJsonDocument& data); + + /// + /// @brief PIPE SystemCapture images from SystemCapture over HyperionDaemon to Hyperion class + /// + void systemImage(const Image& image); + + /// + /// @brief PIPE v4lCapture images from v4lCapture over HyperionDaemon to Hyperion class + /// + void v4lImage(const Image & image); + + /// + /// @brief After eval of setVideoMode this signal emits with a new one on change + /// + void videoMode(const VideoMode& mode); + +private slots: + /// + /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor + /// @param type settingyType from enum + /// @param config configuration object + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + /// + /// @brief Listen for videoMode changes and emit videoMode in case of a change, update _currVideoMode + /// @param mode The requested video mode + /// + void setVideoMode(const VideoMode& mode); + private: void createGrabberDispmanx(); void createGrabberAmlogic(); @@ -80,29 +129,31 @@ private: void createGrabberOsx(const QJsonObject & grabberConfig); void createGrabberX11(const QJsonObject & grabberConfig); - Logger* _log; - QJsonObject _qconfig; - JsonServer* _jsonServer; - ProtoServer* _protoServer; - BoblightServer* _boblightServer; - UDPListener* _udpListener; + Logger* _log; + BonjourBrowserWrapper* _bonjourBrowserWrapper; + PythonInit* _pyInit; + WebServer* _webserver; + JsonServer* _jsonServer; + ProtoServer* _protoServer; + BoblightServer* _boblightServer; + UDPListener* _udpListener; std::vector _v4l2Grabbers; - DispmanxWrapper* _dispmanx; -#ifdef ENABLE_X11 - X11Wrapper* _x11Grabber; -#endif - AmlogicWrapper* _amlGrabber; - FramebufferWrapper* _fbGrabber; - OsxWrapper* _osxGrabber; - Hyperion* _hyperion; - Stats* _stats; + DispmanxWrapper* _dispmanx; + X11Wrapper* _x11Grabber; + AmlogicWrapper* _amlGrabber; + FramebufferWrapper* _fbGrabber; + OsxWrapper* _osxGrabber; + Hyperion* _hyperion; + Stats* _stats; unsigned _grabber_width; unsigned _grabber_height; unsigned _grabber_frequency; - int _grabber_priority; unsigned _grabber_cropLeft; unsigned _grabber_cropRight; unsigned _grabber_cropTop; unsigned _grabber_cropBottom; + + VideoMode _currVideoMode; + SettingsManager* _settingsManager; }; diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index a1ade791..7d5e58d4 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -319,7 +318,6 @@ int main(int argc, char** argv) try { hyperiond = new HyperionDaemon(configFiles[0], rootPath, qApp); - hyperiond->run(); } catch (std::exception& e) { @@ -327,16 +325,14 @@ int main(int argc, char** argv) } int rc = 1; - WebConfig* webConfig = nullptr; try { - webConfig = new WebConfig(qApp); // run the application if (isGuiApp) { Info(log, "start systray"); QApplication::setQuitOnLastWindowClosed(false); - SysTray tray(hyperiond, webConfig->getPort()); + SysTray tray(hyperiond, hyperiond->getWebServerPort()); tray.hide(); rc = (qobject_cast(app.data()))->exec(); } @@ -352,7 +348,6 @@ int main(int argc, char** argv) } // delete components - delete webConfig; delete hyperiond; Logger::deleteInstance(); From 3700566d10acbe93907b9294d8714226f397d2aa Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Fri, 28 Dec 2018 17:55:49 +0100 Subject: [PATCH 28/46] add flatbuffer dependencies --- .gitmodules | 3 + dependencies/external/flatbuffers | 1 + include/flatbufserver/FlatBufferConnection.h | 127 ++++++++++ include/flatbufserver/FlatBufferServer.h | 67 ++++++ libsrc/flatbufserver/CMakeLists.txt | 42 ++++ libsrc/flatbufserver/FlatBufferClient.cpp | 195 +++++++++++++++ libsrc/flatbufserver/FlatBufferClient.h | 122 ++++++++++ libsrc/flatbufserver/FlatBufferConnection.cpp | 223 ++++++++++++++++++ libsrc/flatbufserver/FlatBufferServer.cpp | 113 +++++++++ libsrc/flatbufserver/hyperion_reply.fbs | 15 ++ libsrc/flatbufserver/hyperion_request.fbs | 35 +++ .../hyperion/schema/schema-flatbufServer.json | 37 +++ 12 files changed, 980 insertions(+) create mode 160000 dependencies/external/flatbuffers create mode 100644 include/flatbufserver/FlatBufferConnection.h create mode 100644 include/flatbufserver/FlatBufferServer.h create mode 100644 libsrc/flatbufserver/CMakeLists.txt create mode 100644 libsrc/flatbufserver/FlatBufferClient.cpp create mode 100644 libsrc/flatbufserver/FlatBufferClient.h create mode 100644 libsrc/flatbufserver/FlatBufferConnection.cpp create mode 100644 libsrc/flatbufserver/FlatBufferServer.cpp create mode 100644 libsrc/flatbufserver/hyperion_reply.fbs create mode 100644 libsrc/flatbufserver/hyperion_request.fbs create mode 100644 libsrc/hyperion/schema/schema-flatbufServer.json diff --git a/.gitmodules b/.gitmodules index a9b2e1ba..68102e8d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ path = dependencies/external/rpi_ws281x url = https://github.com/hyperion-project/rpi_ws281x.git branch = master +[submodule "dependencies/external/flatbuffers"] + path = dependencies/external/flatbuffers + url = git://github.com/google/flatbuffers.git diff --git a/dependencies/external/flatbuffers b/dependencies/external/flatbuffers new file mode 160000 index 00000000..0eb7b3be --- /dev/null +++ b/dependencies/external/flatbuffers @@ -0,0 +1 @@ +Subproject commit 0eb7b3beb037748bf5b469e4df9db862c4833e35 diff --git a/include/flatbufserver/FlatBufferConnection.h b/include/flatbufserver/FlatBufferConnection.h new file mode 100644 index 00000000..a4c06a59 --- /dev/null +++ b/include/flatbufserver/FlatBufferConnection.h @@ -0,0 +1,127 @@ +#pragma once + +// Qt includes +#include +#include +#include +#include +#include +#include + +// hyperion util +#include +#include +#include +#include + +// flatbuffer FBS +#include "hyperion_reply_generated.h" +#include "hyperion_request_generated.h" + +/// +/// Connection class to setup an connection to the hyperion server and execute commands. Used from standalone capture binaries (x11/dispamnx/...) +/// +class FlatBufferConnection : public QObject +{ + + Q_OBJECT + +public: + /// + /// Constructor + /// + /// @param address The address of the Hyperion server (for example "192.168.0.32:19444) + /// + FlatBufferConnection(const QString & address); + + /// + /// Destructor + /// + ~FlatBufferConnection(); + + /// Do not read reply messages from Hyperion if set to true + void setSkipReply(bool skip); + + /// + /// Set all leds to the specified color + /// + /// @param color The color + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setColor(const ColorRgb & color, int priority, int duration = 1); + + /// + /// Set the leds according to the given image (assume the image is stretched to the display size) + /// + /// @param image The image + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setImage(const Image & image, int priority, int duration = -1); + + /// + /// Clear the given priority channel + /// + /// @param priority The priority + /// + void clear(int priority); + + /// + /// Clear all priority channels + /// + void clearAll(); + + /// + /// Send a command message and receive its reply + /// + /// @param message The message to send + /// + void sendMessage(const uint8_t* buffer, uint32_t size); + +private slots: + /// Try to connect to the Hyperion host + void connectToHost(); + + /// + /// Slot called when new data has arrived + /// + void readData(); + +signals: + + /// + /// emits when a new videoMode was requested from flatbuf client + /// + void setVideoMode(const VideoMode videoMode); + +private: + + /// + /// Parse a reply message + /// + /// @param reply The received reply + /// + /// @return true if the reply indicates success + /// + bool parseReply(const flatbuf::HyperionReply * reply); + +private: + /// The TCP-Socket with the connection to the server + QTcpSocket _socket; + + /// Host address + QString _host; + + /// Host port + uint16_t _port; + + /// Skip receiving reply messages from Hyperion if set + bool _skipReply; + + QTimer _timer; + QAbstractSocket::SocketState _prevSocketState; + + Logger * _log; + flatbuffers::FlatBufferBuilder _builder; +}; diff --git a/include/flatbufserver/FlatBufferServer.h b/include/flatbufserver/FlatBufferServer.h new file mode 100644 index 00000000..799e3d92 --- /dev/null +++ b/include/flatbufserver/FlatBufferServer.h @@ -0,0 +1,67 @@ +#pragma once + +// util +#include +#include + +// qt +#include + +class QTcpServer; +class FlatBufferClient; +class NetOrigin; + +/// +/// @brief A TcpServer to receive images of different formats with Google Flatbuffer +/// Images will be forwarded to all Hyperion instances +/// +class FlatBufferServer : public QObject +{ + Q_OBJECT +public: + FlatBufferServer(const QJsonDocument& config, QObject* parent = nullptr); + ~FlatBufferServer(); + +public slots: + /// + /// @brief Handle settings update + /// @param type The type from enum + /// @param config The configuration + /// + void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); + + void initServer(); + +private slots: + /// + /// @brief Is called whenever a new socket wants to connect + /// + void newConnection(); + + /// + /// @brief is called whenever a client disconnected + /// + void clientDisconnected(); + +private: + /// + /// @brief Start the server with current _port + /// + void startServer(); + + /// + /// @brief Stop server + /// + void stopServer(); + + +private: + QTcpServer* _server; + NetOrigin* _netOrigin; + Logger* _log; + int _timeout; + quint16 _port; + const QJsonDocument _config; + + QVector _openConnections; +}; diff --git a/libsrc/flatbufserver/CMakeLists.txt b/libsrc/flatbufserver/CMakeLists.txt new file mode 100644 index 00000000..48a31dd8 --- /dev/null +++ b/libsrc/flatbufserver/CMakeLists.txt @@ -0,0 +1,42 @@ + +# Define the current source locations +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/flatbufserver) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/flatbufserver) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${FLATBUFFERS_INCLUDE_DIRS} +) + +FILE ( GLOB FLATBUFSERVER_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +set(Flatbuffer_GENERATED_FBS + hyperion_reply_generated.h + hyperion_request_generated.h +) + +set(Flatbuffer_FBS + ${CURRENT_SOURCE_DIR}/hyperion_reply.fbs + ${CURRENT_SOURCE_DIR}/hyperion_request.fbs +) + +FOREACH(FBS_FILE ${Flatbuffer_FBS}) + compile_flattbuffer_schema(${FBS_FILE} ${CMAKE_CURRENT_BINARY_DIR}) + ENDFOREACH(FBS_FILE) + +# let cmake know about new generated source files +set_source_files_properties( + ${Flatbuffer_GENERATED_FBS} PROPERTIES GENERATED TRUE +) + +add_library(flatbufserver + ${FLATBUFSERVER_SOURCES} + ${Flatbuffer_GENERATED_FBS} +) + +target_link_libraries(flatbufserver + hyperion-utils + flatbuffers + Qt5::Network + Qt5::Core +) diff --git a/libsrc/flatbufserver/FlatBufferClient.cpp b/libsrc/flatbufserver/FlatBufferClient.cpp new file mode 100644 index 00000000..335bf829 --- /dev/null +++ b/libsrc/flatbufserver/FlatBufferClient.cpp @@ -0,0 +1,195 @@ +#include "FlatBufferClient.h" + +// qt +#include +#include +#include + +#include +#include +FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent) + : QObject(parent) + , _log(Logger::getInstance("FLATBUFSERVER")) + , _socket(socket) + , _clientAddress(socket->peerAddress().toString()) + , _timeoutTimer(new QTimer(this)) + , _timeout(timeout * 1000) + , _priority() + , _hyperion(Hyperion::getInstance()) +{ + // timer setup + _timeoutTimer->setSingleShot(true); + _timeoutTimer->setInterval(_timeout); + connect(_timeoutTimer, &QTimer::timeout, this, &FlatBufferClient::forceClose); + + // connect socket signals + connect(_socket, &QTcpSocket::readyRead, this, &FlatBufferClient::readyRead); + connect(_socket, &QTcpSocket::disconnected, this, &FlatBufferClient::disconnected); +} + +void FlatBufferClient::readyRead() +{ + qDebug()<<"readyRead"; + _timeoutTimer->start(); + + _receiveBuffer += _socket->readAll(); + + // check if we can read a header + while(_receiveBuffer.size() >= 4) + { + + uint32_t messageSize = + ((_receiveBuffer[0]<<24) & 0xFF000000) | + ((_receiveBuffer[1]<<16) & 0x00FF0000) | + ((_receiveBuffer[2]<< 8) & 0x0000FF00) | + ((_receiveBuffer[3] ) & 0x000000FF); + + // check if we can read a complete message + if((uint32_t) _receiveBuffer.size() < messageSize + 4) return; + + // remove header + msg from buffer + const QByteArray& msg = _receiveBuffer.remove(0, messageSize + 4); + + const uint8_t* msgData = reinterpret_cast(msg.mid(3, messageSize).constData()); + flatbuffers::Verifier verifier(msgData, messageSize); + + if (flatbuf::VerifyHyperionRequestBuffer(verifier)) + { + auto message = flatbuf::GetHyperionRequest(msgData); + handleMessage(message); + continue; + } + qDebug()<<"Unable to pasrse msg"; + sendErrorReply("Unable to parse message"); + } + //emit newMessage(msgData,messageSize); + + + // Emit this to send a new priority register event to all Hyperion instances, + // emit registerGlobalInput(_priority, hyperion::COMP_FLATBUFSERVER, QString("%1@%2").arg("PLACE_ORIGIN_STRING_FROM_SENDER_HERE",_socket->peerAddress())); + + // Emit this to send the image data event to all Hyperion instances + // emit setGlobalInput(_priority, _image, _timeout); +} + +void FlatBufferClient::forceClose() +{ + _socket->close(); +} + +void FlatBufferClient::disconnected() +{ + qDebug()<<"Socket Closed"; + //emit clearGlobalPriority(_priority, hyperion::COMP_FLATBUFSERVER); + _socket->deleteLater(); + emit clientDisconnected(); +} + +void FlatBufferClient::handleMessage(const flatbuf::HyperionRequest * message) +{ + switch (message->command()) + { + case flatbuf::Command_COLOR: + qDebug()<<"handle colorReuest"; + if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_COLORREQUEST)) + { + sendErrorReply("Received COLOR command without ColorRequest"); + break; + } + //handleColorCommand(message->colorRequest()); + break; + case flatbuf::Command_IMAGE: + qDebug()<<"handle imageReuest"; + if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_IMAGEREQUEST)) + { + sendErrorReply("Received IMAGE command without ImageRequest"); + break; + } + handleImageCommand(message->imageRequest()); + break; + case flatbuf::Command_CLEAR: + if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_CLEARREQUEST)) + { + sendErrorReply("Received CLEAR command without ClearRequest"); + break; + } + //handleClearCommand(message->clearRequest()); + break; + case flatbuf::Command_CLEARALL: + //handleClearallCommand(); + break; + default: + qDebug()<<"handleNotImplemented"; + handleNotImplemented(); + } +} + +void FlatBufferClient::handleImageCommand(const flatbuf::ImageRequest *message) +{ + // extract parameters + int priority = message->priority(); + int duration = message->duration(); + int width = message->imagewidth(); + int height = message->imageheight(); + const auto & imageData = message->imagedata(); + + // make sure the prio is registered before setInput() + if(priority != _priority) + { + _hyperion->clear(_priority); + _hyperion->registerInput(priority, hyperion::COMP_FLATBUFSERVER, "proto@"+_clientAddress); + _priority = priority; + } + + // check consistency of the size of the received data + if ((int) imageData->size() != width*height*3) + { + sendErrorReply("Size of image data does not match with the width and height"); + return; + } + + // create ImageRgb + Image image(width, height); + memcpy(image.memptr(), imageData->data(), imageData->size()); + + _hyperion->setInputImage(_priority, image, duration); + + // send reply + sendSuccessReply(); +} + + +void FlatBufferClient::handleNotImplemented() +{ + sendErrorReply("Command not implemented"); +} + +void FlatBufferClient::sendMessage() +{ + auto size = _builder.GetSize(); + const uint8_t* buffer = _builder.GetBufferPointer(); + uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)}; + _socket->write((const char *) sizeData, sizeof(sizeData)); + _socket->write((const char *)buffer, size); + _socket->flush(); + _builder.Clear(); +} + +void FlatBufferClient::sendSuccessReply() +{ + auto reply = flatbuf::CreateHyperionReplyDirect(_builder, flatbuf::Type_REPLY, true); + _builder.Finish(reply); + + // send reply + sendMessage(); +} + +void FlatBufferClient::sendErrorReply(const std::string &error) +{ + // create reply + auto reply = flatbuf::CreateHyperionReplyDirect(_builder, flatbuf::Type_REPLY, false, error.c_str()); + _builder.Finish(reply); + + // send reply + sendMessage(); +} diff --git a/libsrc/flatbufserver/FlatBufferClient.h b/libsrc/flatbufserver/FlatBufferClient.h new file mode 100644 index 00000000..cbddfdac --- /dev/null +++ b/libsrc/flatbufserver/FlatBufferClient.h @@ -0,0 +1,122 @@ +#pragma once + +// util +#include +#include +#include +#include + +// flatbuffer FBS +#include "hyperion_reply_generated.h" +#include "hyperion_request_generated.h" + +class QTcpSocket; +class QTimer; +class Hyperion; + +namespace flatbuf { +class HyperionRequest; +} + +/// +/// @brief Socket (client) of FlatBufferServer +/// +class FlatBufferClient : public QObject +{ + Q_OBJECT +public: + /// + /// @brief Construct the client + /// @param socket The socket + /// @param timeout The timeout when a client is automatically disconnected and the priority unregistered + /// @param parent The parent + /// + explicit FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent = nullptr); + +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 prepared image to HyperionDaemon + /// + const bool setGlobalInputImage(const int priority, const Image& image, const int timeout_ms = -1); + + /// + /// @brief forward clear to HyperionDaemon + /// + void clearGlobalPriority(const int& priority, const hyperion::Components& component); + + /// + /// @brief Emits whenever the client disconnected + /// + void clientDisconnected(); + +public slots: + /// + /// @brief close the socket and call disconnected() + /// + void forceClose(); + +private slots: + /// + /// @brief Is called whenever the socket got new data to read + /// + void readyRead(); + + /// + /// @brief Is called when the socket closed the connection, also requests thread exit + /// + void disconnected(); + +private: + /// + /// @brief Handle the received message + /// + void handleMessage(const flatbuf::HyperionRequest *message); + + /// + /// Handle an incoming Image message + /// + /// @param message the incoming message + /// + void handleImageCommand(const flatbuf::ImageRequest * message); + + /// + /// Send handle not implemented + /// + void handleNotImplemented(); + + /// + /// Send a message to the connected client + /// + void sendMessage(); + + /// + /// Send a standard reply indicating success + /// + void sendSuccessReply(); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// + void sendErrorReply(const std::string & error); + +private: + Logger *_log; + QTcpSocket *_socket; + const QString _clientAddress; + QTimer *_timeoutTimer; + int _timeout; + int _priority; + Hyperion* _hyperion; + + QByteArray _receiveBuffer; + + // Flatbuffers builder + flatbuffers::FlatBufferBuilder _builder; +}; diff --git a/libsrc/flatbufserver/FlatBufferConnection.cpp b/libsrc/flatbufserver/FlatBufferConnection.cpp new file mode 100644 index 00000000..064b8d56 --- /dev/null +++ b/libsrc/flatbufserver/FlatBufferConnection.cpp @@ -0,0 +1,223 @@ +// stl includes +#include + +// Qt includes +#include + +// protoserver includes +#include + +FlatBufferConnection::FlatBufferConnection(const QString & address) : + _socket(), + _skipReply(false), + _prevSocketState(QAbstractSocket::UnconnectedState), + _log(Logger::getInstance("FLATBUFCONNECTION")) + { + QStringList parts = address.split(":"); + if (parts.size() != 2) + { + throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong address: Unable to parse address (%1)").arg(address).toStdString()); + } + _host = parts[0]; + + bool ok; + _port = parts[1].toUShort(&ok); + if (!ok) + { + throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong port: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + } + + // try to connect to host + Info(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + connectToHost(); + + // start the connection timer + _timer.setInterval(5000); + _timer.setSingleShot(false); + + connect(&_timer,SIGNAL(timeout()), this, SLOT(connectToHost())); + connect(&_socket, SIGNAL(readyRead()), this, SLOT(readData())); + _timer.start(); +} + +FlatBufferConnection::~FlatBufferConnection() +{ + _timer.stop(); + _socket.close(); +} + +void FlatBufferConnection::readData() +{ + qint64 bytesAvail; + while((bytesAvail = _socket.bytesAvailable())) + { + // ignore until we get 4 bytes. + if (bytesAvail < 4) { + continue; + } + + char sizeBuf[4]; + _socket.read(sizeBuf, sizeof(sizeBuf)); + + uint32_t messageSize = + ((sizeBuf[0]<<24) & 0xFF000000) | + ((sizeBuf[1]<<16) & 0x00FF0000) | + ((sizeBuf[2]<< 8) & 0x0000FF00) | + ((sizeBuf[3] ) & 0x000000FF); + + QByteArray buffer; + while((uint32_t)buffer.size() < messageSize) + { + _socket.waitForReadyRead(); + buffer.append(_socket.read(messageSize - buffer.size())); + } + + const uint8_t* replyData = reinterpret_cast(buffer.constData()); + flatbuffers::Verifier verifier(replyData, messageSize); + + if (!flatbuf::VerifyHyperionReplyBuffer(verifier)) + { + Error(_log, "Error while reading data from host"); + return; + } + + auto reply = flatbuf::GetHyperionReply(replyData); + + parseReply(reply); + } +} + +void FlatBufferConnection::setSkipReply(bool skip) +{ + _skipReply = skip; +} + +void FlatBufferConnection::setColor(const ColorRgb & color, int priority, int duration) +{ + auto colorReq = flatbuf::CreateColorRequest(_builder, priority, (color.red << 16) | (color.green << 8) | color.blue, duration); + auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_COLOR, colorReq); + + _builder.Finish(req); + sendMessage(_builder.GetBufferPointer(), _builder.GetSize()); +} + +void FlatBufferConnection::setImage(const Image &image, int priority, int duration) +{ + /* #TODO #BROKEN auto imgData = _builder.CreateVector>(image.memptr(), image.width() * image.height() * 3); + auto imgReq = flatbuf::CreateImageRequest(_builder, priority, image.width(), image.height(), imgData, duration); + auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_IMAGE,0,imgReq); + _builder.Finish(req); + sendMessage(_builder.GetBufferPointer(), _builder.GetSize());*/ +} + +void FlatBufferConnection::clear(int priority) +{ + auto clearReq = flatbuf::CreateClearRequest(_builder, priority); + auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_CLEAR,0,0,clearReq); + + _builder.Finish(req); + sendMessage(_builder.GetBufferPointer(), _builder.GetSize()); +} + +void FlatBufferConnection::clearAll() +{ + auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_CLEARALL); + + // send command message + _builder.Finish(req); + sendMessage(_builder.GetBufferPointer(), _builder.GetSize()); +} + +void FlatBufferConnection::connectToHost() +{ + // try connection only when + if (_socket.state() == QAbstractSocket::UnconnectedState) + { + _socket.connectToHost(_host, _port); + //_socket.waitForConnected(1000); + } +} + +void FlatBufferConnection::sendMessage(const uint8_t* buffer, uint32_t size) +{ + // print out connection message only when state is changed + if (_socket.state() != _prevSocketState ) + { + switch (_socket.state() ) + { + case QAbstractSocket::UnconnectedState: + Info(_log, "No connection to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + break; + + case QAbstractSocket::ConnectedState: + Info(_log, "Connected to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + break; + + default: + Debug(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + break; + } + _prevSocketState = _socket.state(); + } + + + if (_socket.state() != QAbstractSocket::ConnectedState) + { + return; + } + + const uint8_t header[] = { + uint8_t((size >> 24) & 0xFF), + uint8_t((size >> 16) & 0xFF), + uint8_t((size >> 8) & 0xFF), + uint8_t((size ) & 0xFF)}; + + // write message + int count = 0; + count += _socket.write(reinterpret_cast(header), 4); + count += _socket.write(reinterpret_cast(buffer), size); + if (!_socket.waitForBytesWritten()) + { + Error(_log, "Error while writing data to host"); + return; + } +} + +bool FlatBufferConnection::parseReply(const flatbuf::HyperionReply *reply) +{ + bool success = false; + + switch (reply->type()) + { + case flatbuf::Type_REPLY: + { + if (!_skipReply) + { + if (!reply->success()) + { + if (flatbuffers::IsFieldPresent(reply, flatbuf::HyperionReply::VT_ERROR)) + { + throw std::runtime_error("PROTOCONNECTION ERROR: " + reply->error()->str()); + } + else + { + throw std::runtime_error("PROTOCONNECTION ERROR: No error info"); + } + } + else + { + success = true; + } + } + break; + } + case flatbuf::Type_VIDEO: + { + VideoMode vMode = (VideoMode)reply->video(); + emit setVideoMode(vMode); + break; + } + } + + return success; +} diff --git a/libsrc/flatbufserver/FlatBufferServer.cpp b/libsrc/flatbufserver/FlatBufferServer.cpp new file mode 100644 index 00000000..ffef50f1 --- /dev/null +++ b/libsrc/flatbufserver/FlatBufferServer.cpp @@ -0,0 +1,113 @@ +#include +#include "FlatBufferClient.h" + +// qt +#include +#include +#include + + +#include + + +FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent) + : QObject(parent) + , _server(new QTcpServer(this)) + , _log(Logger::getInstance("FLATBUFSERVER")) + , _timeout(5000) + , _config(config) +{ + +} + +FlatBufferServer::~FlatBufferServer() +{ + stopServer(); + delete _server; +} + +void FlatBufferServer::initServer() +{ + qDebug()<<"Thread in InitServer is"<thread(); + connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection); + + // apply config + handleSettingsUpdate(settings::FLATBUFSERVER, _config); +} + +void FlatBufferServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) +{ + qDebug()<<"Thread in handleSettingsUpdate is"<thread(); + if(type == settings::FLATBUFSERVER) + { + const QJsonObject& obj = config.object(); + + quint16 port = obj["port"].toInt(19400); + + // port check + if(_server->serverPort() != port) + { + stopServer(); + _port = port; + } + + // new timeout just for new connections + _timeout = obj["timeout"].toInt(5000); + // enable check + obj["enable"].toBool(true) ? startServer() : stopServer(); + } +} + +void FlatBufferServer::newConnection() +{ + qDebug()<<"Thread in newConnection is"<thread(); + while(_server->hasPendingConnections()) + { + 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); + // forward data + //connect(clientThread, &FlatBufferClient::); + _openConnections.append(client); + } + } +} + +void FlatBufferServer::clientDisconnected() +{ + FlatBufferClient* client = qobject_cast(sender()); + client->deleteLater(); + _openConnections.removeAll(client); +} + +void FlatBufferServer::startServer() +{ + if(!_server->isListening()) + { + if(!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Failed to bind port %d", _port); + } + else + { + Info(_log,"Started on port %d", _port); + } + } +} + +void FlatBufferServer::stopServer() +{ + if(_server->isListening()) + { + // close client connections + for(auto client : _openConnections) + { + client->forceClose(); + } + _server->close(); + Info(_log, "Stopped"); + } +} diff --git a/libsrc/flatbufserver/hyperion_reply.fbs b/libsrc/flatbufserver/hyperion_reply.fbs new file mode 100644 index 00000000..878f2612 --- /dev/null +++ b/libsrc/flatbufserver/hyperion_reply.fbs @@ -0,0 +1,15 @@ +namespace flatbuf; + +enum Type : int { + REPLY = 0, + VIDEO = 1, +} + +table HyperionReply { + type:Type; + success:bool; + error:string; + video:int; +} + +root_type HyperionReply; diff --git a/libsrc/flatbufserver/hyperion_request.fbs b/libsrc/flatbufserver/hyperion_request.fbs new file mode 100644 index 00000000..3ce9f423 --- /dev/null +++ b/libsrc/flatbufserver/hyperion_request.fbs @@ -0,0 +1,35 @@ +namespace flatbuf; + +enum Command : int { + COLOR = 0, + IMAGE = 1, + CLEAR = 2, + CLEARALL = 3, +} + +table HyperionRequest { + command:Command; + colorRequest:flatbuf.ColorRequest; + imageRequest:flatbuf.ImageRequest; + clearRequest:flatbuf.ClearRequest; +} + +table ColorRequest { + priority:int; + RgbColor:int; + duration:int; +} + +table ImageRequest { + priority:int; + imagewidth:int; + imageheight:int; + imagedata:[ubyte]; + duration:int; +} + +table ClearRequest { + priority:int; +} + +root_type HyperionRequest; diff --git a/libsrc/hyperion/schema/schema-flatbufServer.json b/libsrc/hyperion/schema/schema-flatbufServer.json new file mode 100644 index 00000000..e86633d9 --- /dev/null +++ b/libsrc/hyperion/schema/schema-flatbufServer.json @@ -0,0 +1,37 @@ +{ + "type" : "object", + "required" : true, + "title" : "edt_conf_fbs_heading_title", + "properties" : + { + "enable" : + { + "type" : "boolean", + "required" : true, + "title" : "edt_conf_general_enable_title", + "default" : true, + "propertyOrder" : 1 + }, + "port" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_general_port_title", + "minimum" : 1024, + "maximum" : 65535, + "default" : 19400, + "propertyOrder" : 2 + }, + "timeout" : + { + "type" : "integer", + "required" : true, + "title" : "edt_conf_fbs_timeout_title", + "append" : "edt_append_s", + "minimum" : 1, + "default" : 5, + "propertyOrder" : 3 + } + }, + "additionalProperties" : false +} From 2a77f6f0122691fe1961259178c1ce9d0caca053 Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Fri, 28 Dec 2018 18:12:45 +0100 Subject: [PATCH 29/46] even more changes Signed-off-by: Paulchen-Panther --- CMakeLists.txt | 6 +- CompileHowto.md | 15 +- README.md | 2 +- assets/webconfig/i18n/de.json | 18 +- assets/webconfig/i18n/en.json | 22 +- assets/webconfig/js/content_network.js | 23 +- assets/webconfig/js/hyperion.js | 2 +- assets/webconfig/js/ledsim.js | 10 + bin/service/hyperion.systemd | 5 +- cmake/debian/postinst | 47 +- cmake/debian/preinst | 95 +- cmake/debian/prerm | 40 + cmake/packages.cmake | 4 +- config/hyperion.config.json.commented | 21 +- config/hyperion.config.json.default | 10 +- dependencies/CMakeLists.txt | 42 + include/api/JsonAPI.h | 254 ++++ include/api/JsonCB.h | 112 ++ include/boblightserver/BoblightServer.h | 2 +- include/bonjour/bonjourserviceregister.h | 5 + include/flatbufserver/FlatBufferConnection.h | 8 +- include/flatbufserver/FlatBufferServer.h | 2 - include/grabber/DispmanxFrameGrabber.h | 2 +- include/grabber/V4L2Grabber.h | 12 +- include/grabber/V4L2Wrapper.h | 1 - include/grabber/X11Grabber.h | 2 +- include/hyperion/CaptureCont.h | 7 + include/hyperion/ComponentRegister.h | 4 +- include/hyperion/Grabber.h | 6 +- include/hyperion/GrabberWrapper.h | 9 +- include/hyperion/Hyperion.h | 15 +- include/hyperion/PriorityMuxer.h | 7 + include/hyperion/SettingsManager.h | 3 - include/jsonserver/JsonServer.h | 13 - include/protoserver/ProtoServer.h | 25 - include/udplistener/UDPListener.h | 30 +- include/utils/Components.h | 7 +- include/utils/NetUtils.h | 34 + include/utils/Stats.h | 8 + include/utils/settings.h | 3 + include/webserver/WebServer.h | 7 +- libsrc/CMakeLists.txt | 1 + libsrc/api/CMakeLists.txt | 21 + libsrc/api/JsonAPI.cpp | 1069 +++++++++++++++++ libsrc/api/JsonCB.cpp | 304 +++++ libsrc/blackborder/BlackBorderProcessor.cpp | 12 +- .../BoblightClientConnection.cpp | 21 +- .../boblightserver/BoblightClientConnection.h | 12 +- libsrc/boblightserver/BoblightServer.cpp | 6 +- libsrc/bonjour/bonjourserviceregister.cpp | 8 +- libsrc/bonjour/bonjourserviceresolver.cpp | 2 +- libsrc/effectengine/CMakeLists.txt | 2 +- libsrc/effectengine/Effect.cpp | 2 +- libsrc/flatbufserver/FlatBufferClient.cpp | 10 +- libsrc/flatbufserver/FlatBufferConnection.cpp | 106 +- libsrc/flatbufserver/FlatBufferServer.cpp | 7 - .../grabber/dispmanx/DispmanxFrameGrabber.cpp | 22 +- libsrc/grabber/dispmanx/DispmanxWrapper.cpp | 2 +- libsrc/grabber/osx/OsxFrameGrabber.cpp | 9 +- libsrc/grabber/v4l2/V4L2Grabber.cpp | 76 +- libsrc/grabber/v4l2/V4L2Wrapper.cpp | 4 +- libsrc/grabber/x11/X11Grabber.cpp | 6 +- libsrc/hyperion/CMakeLists.txt | 1 + libsrc/hyperion/CaptureCont.cpp | 18 +- libsrc/hyperion/ComponentRegister.cpp | 4 +- libsrc/hyperion/Grabber.cpp | 13 +- libsrc/hyperion/GrabberWrapper.cpp | 16 +- libsrc/hyperion/Hyperion.cpp | 22 +- libsrc/hyperion/LinearColorSmoothing.cpp | 2 +- libsrc/hyperion/PriorityMuxer.cpp | 6 + libsrc/hyperion/SettingsManager.cpp | 19 +- libsrc/hyperion/hyperion.schema.json | 4 + libsrc/hyperion/resource.qrc | 1 + libsrc/hyperion/schema/schema-device.json | 13 +- .../hyperion/schema/schema-grabberV4L2.json | 42 +- .../hyperion/schema/schema-instCapture.json | 4 +- libsrc/hyperion/schema/schema-webConfig.json | 8 - libsrc/jsonserver/JsonServer.cpp | 56 +- libsrc/protoserver/ProtoClientConnection.cpp | 7 +- libsrc/protoserver/ProtoClientConnection.h | 5 +- libsrc/protoserver/ProtoServer.cpp | 59 +- libsrc/python/CMakeLists.txt | 2 +- libsrc/udplistener/UDPListener.cpp | 41 +- libsrc/utils/Stats.cpp | 50 +- libsrc/webserver/CgiHandler.cpp | 9 +- libsrc/webserver/CgiHandler.h | 5 +- libsrc/webserver/QtHttpServer.h | 76 +- libsrc/webserver/StaticFileServing.cpp | 5 +- libsrc/webserver/StaticFileServing.h | 12 +- libsrc/webserver/WebJsonRpc.h | 4 + libsrc/webserver/WebServer.cpp | 37 +- libsrc/webserver/WebSocketClient.cpp | 9 +- src/hyperion-v4l2/hyperion-v4l2.cpp | 4 +- src/hyperion-x11/X11Wrapper.cpp | 3 +- src/hyperion-x11/X11Wrapper.h | 3 + src/hyperiond/CMakeLists.txt | 4 +- src/hyperiond/hyperiond.cpp | 69 +- src/hyperiond/hyperiond.h | 6 +- src/hyperiond/main.cpp | 2 +- 99 files changed, 2610 insertions(+), 673 deletions(-) create mode 100644 cmake/debian/prerm create mode 100644 include/api/JsonAPI.h create mode 100644 include/api/JsonCB.h create mode 100644 include/utils/NetUtils.h create mode 100644 libsrc/api/CMakeLists.txt create mode 100644 libsrc/api/JsonAPI.cpp create mode 100644 libsrc/api/JsonCB.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index facbd343..bdb31224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ SET ( DEFAULT_X11 OFF ) SET ( DEFAULT_WS281XPWM OFF ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_PROTO_LIBS OFF ) +SET ( DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS OFF ) SET ( DEFAULT_TESTS OFF ) IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) @@ -163,6 +164,9 @@ message(STATUS "ENABLE_PROFILER = ${ENABLE_PROFILER}") SET ( PROTOBUF_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/proto ) SET ( PROTOBUF_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/proto ) +SET ( FLATBUFFERS_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/flatbuf ) +SET ( FLATBUFFERS_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/flatbuf ) + # check all json files FILE ( GLOB_RECURSE HYPERION_SCHEMAS RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/libsrc/*schema*.json ) SET( JSON_FILES @@ -246,7 +250,7 @@ if (UNIX AND NOT APPLE) endif () # add QT5 dependency -SET(QT_MIN_VERSION "5.2.0") +SET(QT_MIN_VERSION "5.5.0") find_package(Qt5 COMPONENTS Core Gui Network SerialPort REQUIRED) message( STATUS "Found Qt Version: ${Qt5Core_VERSION}" ) IF ( "${Qt5Core_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" ) diff --git a/CompileHowto.md b/CompileHowto.md index 25c6e2a5..dcbb869d 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -6,24 +6,13 @@ 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 ``` -### Ubuntu 14.04 specific -You need a newer version of cmake (minimum 3.0.0). Install it from the ppa or website -``` -sudo apt-get install software-properties-common -sudo add-apt-repository ppa:george-edison55/cmake-3.x -sudo apt-get update && sudo apt-get upgrade -``` **on RPI you need the videocore IV headers** ``` sudo apt-get install libraspberrypi-dev ``` -**OSMC** -libraspberrypi-dev is not available, use this instead -``` -sudo apt-get install rbp-userland-dev-osmc -``` + **ATTENTION Win10LinuxSubsystem** we do not (/we can't) support using hyperion in linux subsystem of MS Windows 10, albeit some users tested it with success. Keep in mind to disable all linux specific led and grabber hardware via cmake. Because we use QT as framework in hyperion, serialport leds and network driven devices could work. @@ -71,7 +60,7 @@ sudo make install/strip sudo make uninstall # ... or run it from compile directory bin/hyperiond -# webui is located on localhost:8090 +# webui is located on localhost:8099 ``` diff --git a/README.md b/README.md index 4bc70795..4b9d9354 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you need further support please open a topic at the our new forum! [Hyperion webpage/forum](https://www.hyperion-project.org). ## Requirements -* Debian 8, Ubuntu 14.04 or higher. Windows is not supported currently. +* Debian 9, Ubuntu 16.04 or higher. Windows is not supported currently. ## Building See [Compilehowto](CompileHowto.md) and [CrossCompileHowto](CrossCompileHowto.txt). diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index 469a5e21..730dc199 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -27,6 +27,7 @@ "general_col_green" : "grün", "general_col_blue" : "blau", "general_button_savesettings" : "Einstellungen speichern", + "general_btn_yes" : "Ja", "general_btn_ok" : "OK", "general_btn_cancel" : "Abbrechen", "general_btn_continue" : "Fortfahren", @@ -346,7 +347,7 @@ "edt_dev_enum_sub_min_warm_adjust" : "Minimale Anpassung: warm", "edt_dev_enum_white_off" : "Weiß ist aus", "edt_dev_general_heading_title" : "Allgemeine Einstellungen", - "edt_dev_general_ledCount_title" : "Anzahl Hardware LEDs", + "edt_dev_general_hardwareLedCount_title" : "Anzahl Hardware LEDs", "edt_dev_general_colorOrder_title" : "RGB Byte Reihenfolge", "edt_dev_general_rewriteTime_title" : "Aktualisierungszeit", "edt_dev_spec_header_title" : "Spezifische Einstellungen", @@ -482,13 +483,11 @@ "edt_conf_smooth_continuousOutput_expl" : "Aktualisiere die LEDs, auch wenn das Bild sich nicht geändert hat.", "edt_conf_v4l2_heading_title" : "USB Aufnahme", "edt_conf_v4l2_device_title" : "Gerät", - "edt_conf_v4l2_device_expl" : "Der Pfad zum USB Aufnahmegerät.", - "edt_conf_v4l2_input_title" : "Eingang", - "edt_conf_v4l2_input_expl" : "Der Eingang des Pfades.", + "edt_conf_v4l2_device_expl" : "Der Pfad zum USB (v4l) Aufnahmegerät. Wähle 'auto' für automatische Erkennung. Beispiel: '/dev/video0'", "edt_conf_v4l2_standard_title" : "Videoformat", - "edt_conf_v4l2_standard_expl" : "Wähle das passende Videoformat deiner Region.", + "edt_conf_v4l2_standard_expl" : "Wähle das passende Videoformat deiner Region. Auf 'Auto' wird der gewählte Modus vom v4l interface beibehalten.", "edt_conf_v4l2_sizeDecimation_title" : "Bildverkleinerung Faktor", - "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Bildverkleinerung ausgehend der von der ursprünglichen Größe, 1 bedeutet keine Änderung (originales Bild).", + "edt_conf_v4l2_sizeDecimation_expl" : "Der Faktor der Bildverkleinerung ausgehend von der ursprünglichen Größe, 1 bedeutet keine Änderung (originales Bild).", "edt_conf_v4l2_cropLeft_title" : "Entferne links", "edt_conf_v4l2_cropLeft_expl" : "Anzahl der Pixel auf der linken Seite die vom Bild entfernt werden.", "edt_conf_v4l2_cropRight_title" : "Entferne rechts", @@ -498,7 +497,7 @@ "edt_conf_v4l2_cropBottom_title" : "Entferne unten", "edt_conf_v4l2_cropBottom_expl" : "Anzahl der Pixel auf der unteren Seite die vom Bild entfernt werden.", "edt_conf_v4l2_signalDetection_title" : "Signal Erkennung", - "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten.", + "edt_conf_v4l2_signalDetection_expl" : "Wenn aktiviert, wird die USB Aufnahme temporär bei \"kein Signal\" abgeschalten. Das Bild muss dazu 4 Sekunden lang unter die Schwellwerte fallen.", "edt_conf_v4l2_redSignalThreshold_title" : "Rote Signalschwelle", "edt_conf_v4l2_redSignalThreshold_expl" : "Je höher die rote Schwelle je eher wird abgeschalten bei entsprechendem rot-Anteil.", "edt_conf_v4l2_greenSignalThreshold_title" : "Grüne Signalschwelle", @@ -513,6 +512,11 @@ "edt_conf_v4l2_sDVOffsetMax_expl" : "Signal Erkennungs-Bereich vertikal maximum (0.0-1.0)", "edt_conf_v4l2_sDHOffsetMax_title" : "Signal Erkennung HMax", "edt_conf_v4l2_sDHOffsetMax_expl" : "Signal Erkennungs-Bereich horizontal maximum (0.0-1.0)", + "edt_conf_instCapture_heading_title" : "Instance Aufnahme", + "edt_conf_instC_systemEnable_title" : "Aktiviere Plattform Aufnahme", + "edt_conf_instC_systemEnable_expl" : "Aktiviert die Plattform Aufnahme für diese LED Hardware Instanz", + "edt_conf_instC_v4lEnable_title" : "Aktiviere USB Aufnahme", + "edt_conf_instC_v4lEnable_expl" : "Aktiviert die USB Aufnahme für diese LED Hardware Instanz", "edt_conf_fg_heading_title" : "Plattform Aufnahme", "edt_conf_fg_type_title" : "Typ", "edt_conf_fg_type_expl" : "Art der Plattform Aufnahme, standard ist 'auto'", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 5c2ae968..51067818 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -27,6 +27,7 @@ "general_col_green" : "green", "general_col_blue" : "blue", "general_button_savesettings" : "Save settings", + "general_btn_yes" : "Yes", "general_btn_ok" : "OK", "general_btn_cancel" : "Cancel", "general_btn_continue" : "Continue", @@ -347,7 +348,7 @@ "edt_dev_enum_white_off" : "White off", "edt_dev_general_heading_title" : "General Settings", "edt_dev_general_name_title" : "Configuration name", - "edt_dev_general_ledCount_title" : "Count of all hardware LEDs", + "edt_dev_general_hardwareLedCount_title" : "Hardware LED count", "edt_dev_general_colorOrder_title" : "RGB byte order", "edt_dev_general_rewriteTime_title" : "Refresh time", "edt_dev_spec_header_title" : "Specific Settings", @@ -483,17 +484,9 @@ "edt_conf_smooth_continuousOutput_expl" : "Update the leds even there is no changed picture.", "edt_conf_v4l2_heading_title" : "USB Capture", "edt_conf_v4l2_device_title" : "Device", - "edt_conf_v4l2_device_expl" : "The path to the usb capture.", - "edt_conf_v4l2_input_title" : "Input", - "edt_conf_v4l2_input_expl" : "Input of this path.", + "edt_conf_v4l2_device_expl" : "The path to the usb capture interface. Set to 'auto' for auto detection. Example: '/dev/video0'", "edt_conf_v4l2_standard_title" : "Video standard", - "edt_conf_v4l2_standard_expl" : "Select the video standard for your region.", - "edt_conf_v4l2_width_title" : "Width", - "edt_conf_v4l2_width_expl" : "The width of the picture. (-1 = auto width)", - "edt_conf_v4l2_height_title" : "Height", - "edt_conf_v4l2_height_expl" : "The height of the picture. (-1 = auto height)", - "edt_conf_v4l2_frameDecimation_title" : "Frame decimation", - "edt_conf_v4l2_frameDecimation_expl" : "The factor of frame decimation", + "edt_conf_v4l2_standard_expl" : "Select the video standard for your region. 'Auto' keeps the chosen one from v4l interface", "edt_conf_v4l2_sizeDecimation_title" : "Size decimation", "edt_conf_v4l2_sizeDecimation_expl" : "The factor of size decimation. 1 means no decimation (keep original size)", "edt_conf_v4l2_cropLeft_title" : "Crop left", @@ -505,7 +498,7 @@ "edt_conf_v4l2_cropBottom_title" : "Crop bottom", "edt_conf_v4l2_cropBottom_expl" : "Count of pixels on the bottom side that are removed from the picture.", "edt_conf_v4l2_signalDetection_title" : "Signal detection", - "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found.", + "edt_conf_v4l2_signalDetection_expl" : "If enabled, usb capture will be temporarily disabled when no signal was found. This will happen when the picture fall below the threshold value for a period of 4 seconds.", "edt_conf_v4l2_redSignalThreshold_title" : "Red signal threshold", "edt_conf_v4l2_redSignalThreshold_expl" : "Darkens low red values (recognized as black)", "edt_conf_v4l2_greenSignalThreshold_title" : "Green signal threshold", @@ -520,6 +513,11 @@ "edt_conf_v4l2_sDVOffsetMax_expl" : "Signal detection area vertical maximum (0.0-1.0)", "edt_conf_v4l2_sDHOffsetMax_title" : "Signal Detection HMax", "edt_conf_v4l2_sDHOffsetMax_expl" : "Signal detection area horizontal maximum (0.0-1.0)", + "edt_conf_instCapture_heading_title" : "Instance Capture", + "edt_conf_instC_systemEnable_title" : "Enable platform capture", + "edt_conf_instC_systemEnable_expl" : "Enables the platform capture for this led hardware instance", + "edt_conf_instC_v4lEnable_title" : "Enable USB capture", + "edt_conf_instC_v4lEnable_expl" : "Enables the USB capture for this led hardware instance", "edt_conf_fg_heading_title" : "Platform Capture", "edt_conf_fg_type_title" : "Type", "edt_conf_fg_type_expl" : "Type of platform capture, default is 'auto'", diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 5c176e2f..eb276c08 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -4,6 +4,7 @@ $(document).ready( function() { 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; @@ -20,6 +21,11 @@ $(document).ready( function() { $('#conf_cont_proto').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_ps_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver')); $('#conf_cont_proto').append(createHelpTable(schema.protoServer.properties, $.i18n("edt_conf_ps_heading_title"))); + //flatbufserver + $('#conf_cont').append(createRow('conf_cont_flatbuf')) + $('#conf_cont_flatbuf').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_fbs_heading_title"), 'editor_container_fbserver', 'btn_submit_fbserver')); + $('#conf_cont_flatbuf').append(createHelpTable(schema.flatbufServer.properties, $.i18n("edt_conf_fbs_heading_title"))); + //boblight $('#conf_cont').append(createRow('conf_cont_bobl')) $('#conf_cont_bobl').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_bobls_heading_title"), 'editor_container_boblightserver', 'btn_submit_boblightserver')); @@ -43,6 +49,7 @@ $(document).ready( function() { $('#conf_cont').addClass('row'); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_js_heading_title"), 'editor_container_jsonserver', 'btn_submit_jsonserver')); $('#conf_cont').append(createOptPanel('fa-sitemap', $.i18n("edt_conf_ps_heading_title"), 'editor_container_protoserver', 'btn_submit_protoserver')); + $('#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_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') @@ -62,7 +69,7 @@ $(document).ready( function() { requestWriteConfig(conf_editor_json.getValue()); }); - //proto + //protobuffer conf_editor_proto = createJsonEditor('editor_container_protoserver', { protoServer : schema.protoServer }, true, true); @@ -75,6 +82,19 @@ $(document).ready( function() { requestWriteConfig(conf_editor_proto.getValue()); }); + //flatbuffer + conf_editor_fbs = createJsonEditor('editor_container_fbserver', { + flatbufServer : schema.flatbufServer + }, true, true); + + conf_editor_fbs.on('change',function() { + conf_editor_fbs.validate().length ? $('#btn_submit_fbserver').attr('disabled', true) : $('#btn_submit_fbserver').attr('disabled', false); + }); + + $('#btn_submit_fbserver').off().on('click',function() { + requestWriteConfig(conf_editor_fbs.getValue()); + }); + //boblight conf_editor_bobl = createJsonEditor('editor_container_boblightserver', { boblightServer : schema.boblightServer @@ -122,6 +142,7 @@ $(document).ready( function() { { createHint("intro", $.i18n('conf_network_json_intro'), "editor_container_jsonserver"); createHint("intro", $.i18n('conf_network_proto_intro'), "editor_container_protoserver"); + createHint("intro", $.i18n('conf_network_fbs_intro'), "editor_container_fbserver"); createHint("intro", $.i18n('conf_network_bobl_intro'), "editor_container_boblightserver"); createHint("intro", $.i18n('conf_network_udpl_intro'), "editor_container_udplistener"); createHint("intro", $.i18n('conf_network_forw_intro'), "editor_container_forwarder"); diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 78273a59..1ec7c7c8 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -173,7 +173,7 @@ function sendToHyperion(command, subcommand, msg) // also used for watchdog function requestServerInfo() { - sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update"]'); + sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update"]'); } function requestSysInfo() diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js index 698fc92c..0b3666e2 100644 --- a/assets/webconfig/js/ledsim.js +++ b/assets/webconfig/js/ledsim.js @@ -244,4 +244,14 @@ $(document).ready(function() { }; image.src ='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOgAAABQCAYAAAATMDlgAAAABGdBTUEAALGPC/xhBQAACjppQ0NQUGhvdG9zaG9wIElDQyBwcm9maWxlAABIiZ2Wd1RU1xaHz713eqHNMBQpQ++9DSC9N6nSRGGYGWAoAw4zNLEhogIRRUQEFUGCIgaMhiKxIoqFgGDBHpAgoMRgFFFReTOyVnTl5b2Xl98fZ31rn733PWfvfda6AJC8/bm8dFgKgDSegB/i5UqPjIqmY/sBDPAAA8wAYLIyMwJCPcOASD4ebvRMkRP4IgiAN3fEKwA3jbyD6HTw/0malcEXiNIEidiCzclkibhQxKnZggyxfUbE1PgUMcMoMfNFBxSxvJgTF9nws88iO4uZncZji1h85gx2GlvMPSLemiXkiBjxF3FRFpeTLeJbItZMFaZxRfxWHJvGYWYCgCKJ7QIOK0nEpiIm8cNC3ES8FAAcKfErjv+KBZwcgfhSbukZuXxuYpKArsvSo5vZ2jLo3pzsVI5AYBTEZKUw+Wy6W3paBpOXC8DinT9LRlxbuqjI1ma21tZG5sZmXxXqv27+TYl7u0ivgj/3DKL1fbH9lV96PQCMWVFtdnyxxe8FoGMzAPL3v9g0DwIgKepb+8BX96GJ5yVJIMiwMzHJzs425nJYxuKC/qH/6fA39NX3jMXp/igP3Z2TwBSmCujiurHSU9OFfHpmBpPFoRv9eYj/ceBfn8MwhJPA4XN4oohw0ZRxeYmidvPYXAE3nUfn8v5TE/9h2J+0ONciURo+AWqsMZAaoALk1z6AohABEnNAtAP90Td/fDgQv7wI1YnFuf8s6N+zwmXiJZOb+DnOLSSMzhLysxb3xM8SoAEBSAIqUAAqQAPoAiNgDmyAPXAGHsAXBIIwEAVWARZIAmmAD7JBPtgIikAJ2AF2g2pQCxpAE2gBJ0AHOA0ugMvgOrgBboMHYASMg+dgBrwB8xAEYSEyRIEUIFVICzKAzCEG5Ah5QP5QCBQFxUGJEA8SQvnQJqgEKoeqoTqoCfoeOgVdgK5Cg9A9aBSagn6H3sMITIKpsDKsDZvADNgF9oPD4JVwIrwazoML4e1wFVwPH4Pb4Qvwdfg2PAI/h2cRgBARGqKGGCEMxA0JRKKRBISPrEOKkUqkHmlBupBe5CYygkwj71AYFAVFRxmh7FHeqOUoFmo1ah2qFFWNOoJqR/WgbqJGUTOoT2gyWgltgLZD+6Aj0YnobHQRuhLdiG5DX0LfRo+j32AwGBpGB2OD8cZEYZIxazClmP2YVsx5zCBmDDOLxWIVsAZYB2wglokVYIuwe7HHsOewQ9hx7FscEaeKM8d54qJxPFwBrhJ3FHcWN4SbwM3jpfBaeDt8IJ6Nz8WX4RvwXfgB/Dh+niBN0CE4EMIIyYSNhCpCC+ES4SHhFZFIVCfaEoOJXOIGYhXxOPEKcZT4jiRD0ie5kWJIQtJ20mHSedI90isymaxNdiZHkwXk7eQm8kXyY/JbCYqEsYSPBFtivUSNRLvEkMQLSbyklqSL5CrJPMlKyZOSA5LTUngpbSk3KabUOqkaqVNSw1Kz0hRpM+lA6TTpUumj0lelJ2WwMtoyHjJsmUKZQzIXZcYoCEWD4kZhUTZRGiiXKONUDFWH6kNNppZQv6P2U2dkZWQtZcNlc2RrZM/IjtAQmjbNh5ZKK6OdoN2hvZdTlnOR48htk2uRG5Kbk18i7yzPkS+Wb5W/Lf9ega7goZCisFOhQ+GRIkpRXzFYMVvxgOIlxekl1CX2S1hLipecWHJfCVbSVwpRWqN0SKlPaVZZRdlLOUN5r/JF5WkVmoqzSrJKhcpZlSlViqqjKle1QvWc6jO6LN2FnkqvovfQZ9SU1LzVhGp1av1q8+o66svVC9Rb1R9pEDQYGgkaFRrdGjOaqpoBmvmazZr3tfBaDK0krT1avVpz2jraEdpbtDu0J3XkdXx08nSadR7qknWddFfr1uve0sPoMfRS9Pbr3dCH9a30k/Rr9AcMYANrA67BfoNBQ7ShrSHPsN5w2Ihk5GKUZdRsNGpMM/Y3LjDuMH5homkSbbLTpNfkk6mVaappg+kDMxkzX7MCsy6z3831zVnmNea3LMgWnhbrLTotXloaWHIsD1jetaJYBVhtseq2+mhtY823brGestG0ibPZZzPMoDKCGKWMK7ZoW1fb9banbd/ZWdsJ7E7Y/WZvZJ9if9R+cqnOUs7ShqVjDuoOTIc6hxFHumOc40HHESc1J6ZTvdMTZw1ntnOj84SLnkuyyzGXF66mrnzXNtc5Nzu3tW7n3RF3L/di934PGY/lHtUejz3VPRM9mz1nvKy81nid90Z7+3nv9B72UfZh+TT5zPja+K717fEj+YX6Vfs98df35/t3BcABvgG7Ah4u01rGW9YRCAJ9AncFPgrSCVod9GMwJjgouCb4aYhZSH5IbyglNDb0aOibMNewsrAHy3WXC5d3h0uGx4Q3hc9FuEeUR4xEmkSujbwepRjFjeqMxkaHRzdGz67wWLF7xXiMVUxRzJ2VOitzVl5dpbgqddWZWMlYZuzJOHRcRNzRuA/MQGY9czbeJ35f/AzLjbWH9ZztzK5gT3EcOOWciQSHhPKEyUSHxF2JU0lOSZVJ01w3bjX3ZbJ3cm3yXEpgyuGUhdSI1NY0XFpc2imeDC+F15Oukp6TPphhkFGUMbLabvXu1TN8P35jJpS5MrNTQBX9TPUJdYWbhaNZjlk1WW+zw7NP5kjn8HL6cvVzt+VO5HnmfbsGtYa1pjtfLX9j/uhal7V166B18eu612usL1w/vsFrw5GNhI0pG38qMC0oL3i9KWJTV6Fy4YbCsc1em5uLJIr4RcNb7LfUbkVt5W7t32axbe+2T8Xs4mslpiWVJR9KWaXXvjH7puqbhe0J2/vLrMsO7MDs4O24s9Np55Fy6fK88rFdAbvaK+gVxRWvd8fuvlppWVm7h7BHuGekyr+qc6/m3h17P1QnVd+uca1p3ae0b9u+uf3s/UMHnA+01CrXltS+P8g9eLfOq669Xru+8hDmUNahpw3hDb3fMr5talRsLGn8eJh3eORIyJGeJpumpqNKR8ua4WZh89SxmGM3vnP/rrPFqKWuldZachwcFx5/9n3c93dO+J3oPsk42fKD1g/72ihtxe1Qe277TEdSx0hnVOfgKd9T3V32XW0/Gv94+LTa6ZozsmfKzhLOFp5dOJd3bvZ8xvnpC4kXxrpjux9cjLx4qye4p/+S36Urlz0vX+x16T13xeHK6at2V09dY1zruG59vb3Pqq/tJ6uf2vqt+9sHbAY6b9je6BpcOnh2yGnowk33m5dv+dy6fnvZ7cE7y+/cHY4ZHrnLvjt5L/Xey/tZ9+cfbHiIflj8SOpR5WOlx/U/6/3cOmI9cmbUfbTvSeiTB2Ossee/ZP7yYbzwKflp5YTqRNOk+eTpKc+pG89WPBt/nvF8frroV+lf973QffHDb86/9c1Ezoy/5L9c+L30lcKrw68tX3fPBs0+fpP2Zn6u+K3C2yPvGO9630e8n5jP/oD9UPVR72PXJ79PDxfSFhb+BQOY8/wldxZ1AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfhAwcSDgBKmnp+AAAgAElEQVR42u19eXwV1d3398zcLTc3KyEJIQlI2EEBWSMom0JbrcX2EbHW1uJbXFprVahvK1pcnmqlT6tYq8WiVqlLVWjRvlZcELCIgEXZIgk7CQlZyHKT3Hvnzpzf+8edCSeTmXvnJvC0+Mn5fM5nZs7MnDkzc77nt57fYfQQwJYC9BAAFQxRSNAgg4NBhQQVgAZ02UaFYyNHhX1NKItanDdnTcgkAZAAyPrWpe/L+r6R3frWo+8bW6++79H3xeyzKPcJ95i3HlP94nPdpraJOfYOBIAbr6Xvm481oVw8JotzYiaLerlwbGxngoXQm87J5GJL9T2CCwxeuOADgw8avHpPA5hFhrAVk3jO6CGw2LfLHACJB0zfSgm6IU9QsZhh8xLGy0mmzIQtsziWOoHSKCOLVlq12g58PIlM9mUE4EBvVz9HAQoA9EtIAPxgSANDOiSkQYYfKtyQIEMFdfS9qIP+KgIU+jkxkcW+uCXSQSoCUxPAaqY3skBP5ARgtRtN4mURrLIJjFYgljpeRbMYf6yoZaJjsS4tDoBNwxXjsd1egJ6rAKXlAAhecKSDIRsMfcCRAyADbnjBIEPS2VCj/6kWfTdqAqhkQXFhAVQjGxgzMKBxndXVBBCZASvZUFBuUWYeNWABTikBOM1gtCtjtpRSsynjJgDGu8fMHlOcYx2g63u7+rlKQWVIIKSAIR0cfcGQD4Z+kNAHHKlgcEMDs+TqVBMRilrswyFArco1K/7XTElFqqmZujcs7rdL4sgi2bKt1hS088chi+GCLACoJWBT41FKEYiazf0EMIr9pd50zgJUgksHaBoYskHoB4YicBSAIQMMbksxSzP1UTUBsYkHVGYDWCKAcwuKRg5lULHc7sFWFNSKKspxqKdkC1AtATg1G0qaiBXmFookC/6B+GkWpDedkwB1QQKHB4RUMGSCkAuG/ghhAKLIBIGgQQWHCg7eRXNrpalVTPvi1tg3ckQotyWzxoPsQGbW2PqEnGLKfj2nCA3VTEBmFjKnHdA7s8JmoBn7qqlMc3CdGZiqDdhtgCvxWGa93fzcZnEBBhc4fGAIgCMLvtwC/NcLI5A7IeU0SthpVQ45UPo42U9UljDZaWKtFECwUUE7LUMc9fXpY0mg8XbDTXfLkjzHAIBH0YD83o5+LrO4DAwSJLjBkQKGFMjwwx1wIaWP1PuJupdYkuVnLRF6/+E5DlBj2I1BNQZWBkbGYBwFcFTnB5lpoGYO6Z6ZVrKzRGToLN2XzHEy73Wm2mPPlzA093bzc1sG7ewTwICYH1HHWN8K4HkA1YK6xwmTKp4zq1TN18XbN1/PLY7t6iSbesxlTlworJ6dzLXc4fPhoN3ma7mNZoz3dvEvgwwqOuwwxNwXGBM7gAIgbCFeUQLgWXWsRB3SCVB5gvviAcgK1PFAzx2AOx4InQwY3PRNuYNByapeO2clEBEY69UXnatKotNOAhywkVooCVVPMuogSnCtFbDt2pPMgIEEFNruefHak2iAitcWJ+2Op06zex4DQL3gPJcBKgljbozFNWszKAFwEnV69IBtTUQ1uUO2MBH1NNfFk6gjmfPJlDs95nG+bW/6UiiJWFyAwqTYSUT1nFK0RBTMCesY71mJwJss6J1QR57gGXb3xRv44okLiersTec8BRV/qzG7y5qCsiTYMyesKyUAoh1Vcyo7dpfCxZMPEwE8WVmZHNZtx/LbAbKXkn5pAGr+1ZIlBaUkZScgvqdtd9g6OKRG3AH4ekphu8PiOqHK3OGgRwn+Ty84v1QANVhdw/fcejROlnLamQSA7slcyZosEsl5PaGw1A3w9kTOtGNreZwBkvWC9MsCUHKkJOqJ5tRMHRLJXNQNpVB3ZcdkqGMyLLCT5ye6F4hvorIbSHsp6JdKBoUAUFdCFhc2lNApNbACAuDM/hdPXk0E1mRl057ImN2x1XZHeeZE1OhN52iSOsL8mMP/xDebxQMnR/yZnlaUFA4URuIkT6vpljxJ1tAJOK3msiU6zxOcS8YhI968Os2i7RRHwRQrILLc726KRqOOr7V73m9+85v/eKDYtX3OnDlnpV4AaGpq0l39uEkOleJSz3gyJAOQRUQSAMYYa0PMVRBI3q3N2PoBpBKRxhjjAOpN57OJSOQDggCCLGaZ747cyAEwIuqjD1dgMa+qRiIK6W3gANKJKDXOYGXFKkeIqAmAIkmSJqjj4okG6fqzNIdiQRcKyjkHY0zinNfr/wT6e4GI+gFwJQFWTkStX3zxRXDUqFHc7XaDiPDJJ59gypQpcW/Un5cPwE1EjDEWZozV3nnnnd3q2KLzhd5+n/69PLEiCimKEvT5fJ1GkdbWVgQCgaSep7c9F4BXfzYxxirfeecdMMaQlpaGYDCY9Hvo9UoAcvXvAgDtjLFTjDE67agg/t7OLG48cHbqkESUxhhbzBjzA3ArivJea2vr2oyMDC7LciK21OoZGhFdwRj7KmMsSkSRurq6xWlpaS0+nw+MMYWIZjDGFgBoAyARUW1tbe3dmZmZIa/Xm0hWtaOckxljdyI2uZQR0ana2tr/1jStIi8vj8uy3A7gFsbY15GcE3yYMdbIOd+nqurbR44c2Tx48GC3AFIzuKJEdBNjbAFjLOxQtrQ7529tbV0G4GXToPASgGx94HEEUMZY28iRI09qmrYpGo2+xRg7DAAHDx5ESUlJovtfBJDLGJOIaA+A7yD+rHpLYOodOwBgDICpAC4EkC0ClDEW8nq9LZzzEwA+0TRtq9vt3m2Ac8+ePRg9enQyeHoSwBB9wPa2trb+lDH21vTp09nGjRupb9++qKur685g04cx9hsAIxljpKrqRxs2bPi/ANpdndzfyZaCihKqrbKEiGTGWAGATABobm7O37t3r3/cuHHtGRkZqgPAd/GBJaIsxlixTgnaduzYkVlSUhIaMmRIlDHmamtre9fv939LkqSh+ohUkpKSsmj37t2Pjx07llwul1Nbo/FMP4CbAIw1zlVVVb3y3HPPNV900UXunJycKGNMY4zlMMZGdkuukKRZkiQtPO+889Z/8MEHN11wwQWhnJwct9WgRUS5jLGhZ4JNq6ysHAggDUC7ziKDiIYwxvp38z2u9Hq9Pw2Hw4/6fL4nDHDecMMNeP755+1uGwxgIAAoihIFkAOgCbGp+47ASUQu/R9dpwPUrWc7KkUArne5XK2c848VRXnC5/O9kyQ4QUQDGWNjjOOUlJTn169fP33OnDl7R40ahb1793aXzXUxxobo74L29vbqVatWZQKISF3CuRohZ+OzuY60mm1tbd7KykpfJBKBhfzlZFZKp1Gdc45jx44FGhsbO9wrOOdNtbW1TxORcS1LTU29KhAITGxoaGBIzkFeAXC5PiIb71D51FNPrT9x4gTzeDzc5XIR55z053VXiJMBpMmy/K2ZM2furKurG1hWVqZasJl0JuREIzU0NKQCCJg0DD15gAdAodfrXREKhf7niy++SAkEAiwOODv9U1VVZQAZhijhRE4johIA/wTwOwCl+oDqTsRJ6m3NZoxd7vV6/xGNRleFw+E+xrfYtWuXIyyZBqg+06ZNe2z06NEZBjiLi4uT/oiapnV6x1Ao5K6pqQkAkLpqcYFEvrhOXNYMQLFoNMo454m0j05sh2CMQdM0qKpKes+lQCBA1dXVmwKBwN8DgcDXAUCW5UBRUdH3d+/eXZGent6QkpLCHZiDQgBGAnhIfIcPPvjgpTfffPPQj3/847bhw4crkiRxVVVJkiSx00QbGxvLFEVpFWTfrqiUZW9qamo/n8/XT78OjLGiwYMHr961a9f3KioqjgwdOtROpgUA7eTJk1soJsCJchjp7eh4tlgOAC6Xy3/kyJEadHVDYcL/aqmurt4hy7LMTs9m6kI4PR5PRiAQKHa73VlGoc/nuzM/Pz+ks9GqIF/FA54RU8YJ1ZR1irlSFz06ff9oNNocCoVqI5FIC+c8whhjsiz7fD5fltfrzXW73eniu7pcroWMsbF1dXV39u3bd+MFF1wAAJg8eTI++eQTx+BKSUmZ/Y9//OOmwsLC3wKIHjt2DOnp6WhpaemB3oiYqqoSANbVDgrEc1RIxiYodhzqJjgtW6//dAJAkiRRcXGxUllZ+ex55503zuVyFQJAamrqpKKioiv27dv34rhx47gOKDvKGdXllyfEZ+3du3fNPffc88G3vvWt6OTJk4M5OTmq1UiqaVro5ZdfXvXaa6/tDQQCGo8l6JQWmqYREaFv376eSZMm5cydO/fiESNGXO9yuTIAwO12jzvvvPNu+dvf/vZQbm5uc2ZmpqWphHOulpaW3tvY2Nju8/lUA6j6AGh8a5HsdhrcgsGgsSaApbwZDoePFxYW3tGnTx+ZMWYZBMrr9bLZs2enzp49e+Cll146v6CgYJ5xLiMj454DBw7sGzx48EtJaInJgRIlRR84zdoktbKycuPnn3/+cVlZ2RdlZWV1O3bsaK6qqop4vV4+bNgw77Rp0zJGjRqVP3jw4AtLSkouyczMHG88V5blC/v06fNqS0vLvenp6X8EQMmA02hifn7+HRs2bPho5syZn2RlZWmNjY095nYMBVhngBrdNb6rn1PXsw48OVAGOQYq57yLySIlJQWBQOBIfX39M3l5efcbL9evX78flZeXb6yrqzuUl5cXz64pAbgFwCCBHdz161//ek1paWl0/PjxTYMHD1YERUqXdnm93mhxcXH9hRde2KrL49A0jVRVhaZpxDmncDhMFRUVB99///2d9913X/nkyZMfkSTJDwDZ2dk3EdHqNWvW7Fu4cGEE9pG22xVFqR44cGB7NBolRVGYzhaRoigUCoUM8wfpooX5/ymwn8jNALQ1NDQEEZv/a2lse+GFF+iFF17YX1BQsGXTpk1HSkpKfmKcKy4ufiAvL2/9yZMnT6GHE8b1riMD+JkZnC0tLZ+vWLHiyWeeeaa8pqamSVGUVp0LMiLB8RMnTrANGzYwAO6+fftunDx58ku33XbbtEsvvfQBSZLSdCDkBQKBR6urq9u//e1vv/zJJ59Qe3t7Umy/LMv5kydPfqysrGz6FVdcoTQ2NmqSJEHgHLudurK4EhJ5EiXSwlqBlMOZpxHgIJCtDtJO7cjPz5e2bNmyJj09fabf75+hywiu0tLSR9asWXPtvHnzuN/vtxsAxgD4hiF5q6ra+t57760Lh8OnJkyY0Dhp0qSQziabB52ONrtcLrV///7tCxcuDKanp1vaZFVVpba2NpSXl8uTJk36Y11d3eicnJxFBus4Y8aMBSUlJfdPnz6dlZSUWNqRGWOa3+9vW7t2bXDAgAHGPE9SVRWKohDnHC6XC3l5eWaAiiCM1wGjuhKpNYFMh8bGxtZly5Y98Ic//OFCv99/id5Z+7/++utzL7744r/0BKACa/sVAPeKv7+qqur9KVOmPFxZWdkEoEVXMrULg4/5/aT6+nr5rbfeOvXWW28dWbp06c577rnn9z6fb6QO0sy+ffv+6v777993ySWXfG7cn5OTg/r6esv2KYpSK8tyQJZlv87qTszMzHzg4MGDSwAwoY/2KEm26/7YsyGJZld0+sgCe2tlVAfiL6zShQVijHFN07o8lzFG48ePl8vLyx9RVbVGkI2Gzp49+wc7d+6EqqpW4HQB+D8A8oznHT9+/OM33njj04EDB56aNm1aMDc3V40jv3Z0et3eaGt3dblcyMjIoIkTJ0aPHTvm+spXvvJTIupAUWFh4dUA5M2bN7vsQMQ5p0gkAlVVO9kBXS4X+f1+BAIB+Hw+iiMDJeo4TmZ2EwAqKipSVq9e3dTS0vI7499LkuQtKSmZrStlpB6CcziAteK5w4cPvzVx4sRfVFZWVgM4AaAKQKNOPTWb9+NEFAXQ5na7Gx966KGtq1atuiEUCu0U2N3+EyZMeGLMmDHpxk124NTFmqZXX331HiJSjLL8/Py7du7ceSXOoBeX1CkmswjSxCyulYdMpx/MGCOTQdkuRIeThVREtsfKa4d8Ph9yc3Or6+rqHuecGx2f5eTkzM/KyhpXU1PD0Hlq+ikA1wD4ijAyNq9cufJVWZYbSktLm4YOHRo1ydhWFBSirAkHs1eKioro008/bVYU5R8dKlGPJx9A6j//+U85HA5bUmsiQiQSoWAwaDkQ6AZ0nEntrw1bh/LycgCgrKysfZzzo8b3drvd/RELPNwtgOrt9wBYLWpoDxw4sGbu3LmPVldX10uSVKv/v3AylDoajWqXXXaZ8qMf/WjH888/f0M0Gj1k9NmUlJSpGzZs+G8AuOWWW1iC9/cvXrx40+HDh38vNn3UqFHLV65cWQIAN9988xmkoIZ5Re6iJHLilG7H6hLnHAKL68QP1sqdrhMFEeTQLvfn5eVRU1PTe6FQ6APjnCzLfYqKihZWVFSkhsNhg8Vr022dvxDrX7du3YpNmzYdvuiii1rGjx8fFjTA8VjcDhbWxM4nnHAtSVK5WMmwYcOyPvvsM9lQNJi1qZxz0jSNCeyrCM4OYJ7tECeapon7LYyxZgFgXgCp3QGo8Gl/AOAC46CxsXHHd7/73acrKioaATRwzpsRJ9x5vPTuu+/SiBEjcOutt+5bu3btAvFcRkbGdZs3b5761FNPkcvlitvOsWPH4rHHHvtdJBLZYpS73e6h11xzzYMAPE8//fQZoqBWi3cxW9aIJ5AbrexeThzPu4BN0NjaUVAza81lWabCwsL2qqqqpznnHeq0tLS0GcXFxZft3buX8Zj07gbwlFj30aNH37v55ps3XnzxxaEpU6a09uvXT0P8KXKd2qWqqlNFGgmDR45Yz/79+xW32y2Hw2FYmTp0Ss0FCipSnX9LYDCv12uE8Tfsm2pH/+oe9cwE8FWDenLOw+vWrXvl448/rkPM1bMZPVjSwufzoaysjAKBgLpy5crd9fX1DwgDZtqQIUOuA+A+/RrWAJUkCU888UT9kSNHluoDhtHX5h86dGiBSP3T0tK6CVAz5ZRtlUTxNK+26/uZEOZkbqWWAAhmNrKLwiktLQ2apn3R0NDQaQgrLi7+STAYHFReXh7VtbbnGefa2tqO/+xnP1v1jW98A+PGjWscPnx4RJIky4HFSklkWJPitM28b8hslwuUqBWA4vf7EQ6H47H4OHnyJOKIEv+rSZKkgYyxAcZxJBIxANTdBg0SqWdDQ8PuVatWfabLmj0Cp25OgsvlQmtrK95///1IQ0PDm5zzY8brpKamTnj88cdLHNhoGQAMHz58Q0tLy2PC/5ALCwsf/Oijj4YDQE1NTbf8dDtT0MRKIu5QfrQysyRcshfWy/3GM93EW8uWhg4dyvbv3/9SW1vb+wL70XfSpEn3K4oyUZc9XTowwu+///4riqI0DR8+/NTUqVPb09LSbJdI0ylwl7YJLC4SvCtjjIWI6BbEnKQNBcjbAFySJKkpKSlkw1oSACovLzfkTUrE0p4Jdb/dYME5Z4yxb+syJzjnyp49e7br4NS6UScDMNr4LkSkHT16dPvmzZsNZZByJtquqipycnIAgE6cOLFf07Ttxj/1eDzFxcXFJUjsodSR5syZ8+v29vZ1Ql8rHjt27CMTJ05My8/Px+DBg3sgg9ot1BVHg+fEZklEpCiKqmmauMSSOas2+0bu4u5nAYIu7Kcsyxg3bpz62WefLVNVtcm43+/3jx81atQfARQZ11dXV29/+eWXtxYVFTVOnz69uV+/fiqSDMals7hcoKCIYyNuJ6KJJvMBnnnmmTezsrLI7/crut22i9a1pqYmCiD64IMPUhIUDrrS6YwB0xgUGGPXArhBlEfvuuuuLTi9DlTyfTLG2Xj1/x0qLy/frVPO8JnUkBpa2lmzZgV1gKq62JHWp0+fkszMTJ+TekaMGIHt27e3rlu37m5VVU8Y5ampqV9bu3btLQDYgQPdW0PZZbnArhQXlE6CfwEA+vbtO+Saa665Ljs7O4TYzBSnUQA78ChJ0oWmzsEFJVE8WZhSU1NpwIABdSdPnvzvgoKC5YZ3nSzLmYJWr+XVV1/9y8mTJ2vmzZvXfP755yuyLMeNGih67ohmFk3TRDOLnQzvBjANMV/SfoL8+4/33nuvMhAItI8cOTJqRUEZY65jx44tzs7ObvL7/Y4UJIwx3tTU9Fufz1ebhEyJcDgcb5iW9Jkk3wTwR/HE1q1b//TFF1/U62BKmoJyzt2SJBUK/ye4cePGQ4g505/xtU5feeUVLFiwALIsH9Drd8uy7E9PTy/w+/2+pqYmKdFAU1ZWhilTpuDaa6/9YurUqfcUFRU9a3y7goKC+z/44INNs2bN2lpcXIxjx44lCVDZZgxz5qgQ16kgPT19QHp6+gBRAdDTwdswJSRQPHXk/Px8rays7IPs7Ox3U1JSLjNX+PHHH69+5JFHdi5ZsqRtypQpbTZ+u50Aq2ma5Q/bu3dvJC0tbTKAcRYsOkPMMfx8ALMQc1oHAIRCocrnnnvuLxUVFXVDhgxpnTdvnmpll2SMyUVFRd9Pktqpu3btWgegAc6mddHDDz+cQkRfZYz1s7jeDaAAwEWIOat3pKqqqve/+c1vrgHQprPw3aF2LiLKM/oK5zz85ptvnkQcF8WepAULFhjf6SRizg4pOhVNlyTJjdNRo+OmrVu34vbbb2ezZs166dNPP52enp5+g/7PfKWlpSueffbZeQsXLqwGQG632/FEd5elnk3qQkWdBl4+62E2dLnHcSBol8vF8/LyTtXU1KwsLi6+QJZlwyEBBw8efPuOO+54a/78+eqUKVNa+vfvr8FBhANdW2vm5rFjx46QJElfAXCHDfWQLShG+5NPPvnL+++/f1dxcXHjRRdd1DZp0qQz1hE555GdO3dm6h2vzcknnjZtWhqA2wFMsfinkhWPVVVVteH666//dX19/SlJkpo550o3/6/EGEsRB5jq6upWnP11ZlpFs40kSR4iciej6Hr88ccJgPLOO+/cP2/evClut3u4rjWeeNVVV929devWO7dv38537tzpXDxx6EkUl5WMI29xItKISO1B5hZaXKdRBTgA5ObmsvXr129QVfVTgXVqWr169ZslJSWhCRMmNI0ZM0ZxuVyO4ghZeDJ1mEQ455IARnPulNra2spnzpw5/+67797Rr1+/hhEjRjTecccdarxOwTmPEpGjzDmPMsbcwWDQWSCbroOJaCG3nYhYXl7+l8mTJ9+/YcOGKsRslN0GlP57uQAUQwg7qwCVZblTmAJ9soPUHU30/Pnzjxw6dGix6CWWnp6+6Ic//OHlO3fuxLhx43oog9p7ETkNhAUAOHTo0Kbnn3/+rz6fj0uSRERkKFE6FD0GNRSVK4YiSFVVfvPNN19TVFQ0V/yBAkAcR/ibOnVq2O12K8IP0Nxud3jgwIEtU6dObc/IyNAcvhu1t7fbKU46Zu9wzsM2WtjWlpaWY7t27Xpv9uzZa1NTU6P9+/dvKCkpaXjwwQeVQYMGERFxk/dVBzWcPXv211taWpTU1FTmhIMkIr5nz55TVjqCBEog4pyHYhiRvKZBIhSJROqbmpoOvPXWW28sWrRoI2NM022UTT2RFfXQNuKSie45c+akrl+//qwAVFVVuFwuMMYyINhyo9Fo2IJTcpyGDx/+98bGxqczMzNv1wca37Bhwx5ZsmTJv5YvX17l9F9Yy6AMTuaDUiL7oKZpiqIoDQMGDAj6/X5N77gdFFAHYgdAjXNGUhRF8/v9zRYjLJBkkOjCwkJuNvy73W41Ly8vXFBQoMJZdEAAoIaGBho4cKBZGUMTJ070V1ZWrjp69OjWI0eOZESjUVlQLFFzc7Ny4sSJ4Nq1a6uPHj3akJ2dreXk5DSPHj26ZdmyZdHzzz+fJ5LVP/zww3odCMmwkKokSRGH5hb2xBNPNC5duvT2ffv2ned2uwfOnTt3sdvt7muwnLt37/7zfffdt2bdunVVANpcLpeqqmqjLsP1SJETiURUSZKq9BA5cLlcqfPnzy9Yv3592dlwYTS8hRhjhQZAiUgNBoNNkUgk6UFBjE30i1/8Ytmjjz46xev1TtaVbyOWLl26fPny5d9FLJxNQr2MtQxq70UU18nAMNQbD5UkieXn50emTZsW7N+/f8Rq6pmpzFgqj3SneJ6WlhYxj+4Ci8vhLAi13ewCLkkS17W2Thd1woEDB2j8+PFdWNycnBz53XffPfzYY49V19TUBCKRCNM0DZxzUhQF0B3ms7Ozo2PHjo3269evfc6cOcoNN9zA9TmgZGaZzalPnz5aQ0NDi0N5EsIA4TQCBFu9enV09erV/wJwAEBqRUVF6uDBg5cCkBhjrkGDBpXm5+f/CcBJACFVVcM9MKt0SuFwWPP5fCcE1jMwZsyYoQA+cLlcLBqNnnE9R2trK2OMjdJZeKiq2nbs2LGaYDAYTVavIjokrFixounuu+++NT8/f5MkSak6q7vgxIkT2woKCn7bfRYXCc0sTgIxd5SnpKRogUDArIBx4perWWhMY3aWrrNk4rK5VnKjweIkA04AtGLFCrrmmmu6sIWKotDUqVNpxIgRoYqKinAkEgHnHJFIhDjnaG9vhyzLWl5eHo0ePZoPHz6cp6endzHNxIlmIH477Swq5Yz2tABo3b9//1NFRUUzvV7vxTqVGPXggw9+t6qq6o5t27aF6urqzhj7uW3bNj5r1qwyWZYbGWNZkiR5CgsLJ44aNeqVvXv31p6Nl01NTc3WNdKyzt42V1RUHNVlyG57LV199dXo37//58ePH/9pYWHhk0Z5bm7uXdu2bdvCGNuW6B92paCsC4vbiRLBWQDoeEodHocydzpvpaY3sGnq1AlXrbaSJwz/WQvKjjjH2Lp1a1dtmySRoiiUlpbGR44cyadNm2b3foiz/2932xN6gfH9tCuuuKLmyJEj9xQVFf1NkqQsvZP94Fe/+tXm0aNHvzhixAhWVlZ2RgaLyy+/nNra2vYR0QnGWBYAKTMzc8qdd9456FNMvgwAABKUSURBVMYbbzyFbjrI28na+neeCD1gFwBqamqqWLduXbUuRnT7vV577TUA4E8++eQL995771y/33+lzhX0Hz58+M+feOKJhbfddltDvH8tOfQiikdd4gWqhihz2oDaavUyDpslDgw5FvH9gc11chvTSLJrp9gqWvRwidBZ2UTUyfK7/oeAs0vas2cPBg4cuLmmpmaZWD5ixIgVr7/++tiysrIzGtxs3bp1xzjnW4zv4vP5Si699NKrJEnyjh49+ox+HM65jFi0hjRDSbV///4tn332WS1jrEcAnTRpEgDQI4880rply5almqYdFWTVK6+44orrAUi6g0sSAJXiKomcTjWDSTtr53trV2bpjysqkZA4wgMXlA+2czgTyNeOJpJLkmRHjZ14PYkgxX9aMsJT9u/f/6lgMPiS8M6ZX/va1/5nxowZmWdKgVNdXY1rr71WPXXq1HOITcIGABQWFv74o48+mrpnzx5at25djymnHjgbjLH7AHQ4sCiKUn355Zf/VVfi9Ihab9u2rWP/sssu233ixIkHxfPFxcUPrFmzZvz+/fu5PQVlJrbWXiZ1OsHaLoaQVRQFOARDF4DaRC6wXbvFanaITo3h4H261G8FJJ1lhgPK20Xm/N+YZH0mrBIbNmx4QFXVDsdSn8837aWXXroFgMwYM6hGt1O/fv0wffp05OXlfdzW1vasMBh4J0+e/MaHH3444corr+zxt9K5lZsA3CeWL1++/N5QKBRkjLXjDNtei4uLVwWDweeFd0q78sorX927d28O51yzBSgJwLQJBBRvcnVCQNmwx/HqgQ07SZxzZmKbrahtF/DX1dVZUnid9XW6ZGDcZMQEgvWqZ5achsHW/qdST/P3WrlyZXlNTc0jBnVhjHn69u3746NHj04CIIlUoztp+fLl2LhxIwAgEAj8RFGU7UKHTr344ov/dvjw4YvEb3XVVVc5ppzGdyaiGwCsEM9/8sknv7v33nt3yrIcIqLQmQJonz59AAC33nor+8lPfrJYUZRdgpZ6wLx585ZrmuaxBKimt0LVswZLh81kl/DrdB8/bYDjDrW4ZCODMhN7a2VqsWTBDx48CDsWVxiNE2mp48qghkklnpxpBc5kgMn+zSj++9//Tq+++uqfI5FIR6wgl8uV36dPn0eWLFmSOXDgwB61b8mSJQCAuXPnAgBft27d9/SwJAZIC4qLi98Ih8PX68IY1q5da5hLbEEpADOTiH6BmJN/BygqKyvXX3fddW8ACGua1ngmlVENDQ0AgN///vf07LPPniorK7uTc24EjZJSUlK+4na7L7AEqFkQ1Kz1904WjLVVEiHx6mN21JBby/Xcrh5bJdQPfvADy8nkNu2DA3bXDBwoikKqqnIn4OyuzFlfX68gNrOj2zxeT9jDG2+8EYsXLw6/8cYbSzRNOy6YKi6566677jpy5EiPOYLs7Gy88847ICK6+uqrKw4ePHibOI1LkqR8j8fzTDQa/Wtra+uMsrIyn05xLf+L/s4ZRHQ9gP8HYBkE18uGhoYdP//5z3938ODBWsScQEI4iyassWPHbmpubv4dOsfxspwc7rILX+C2lj/tNLnA6VkmZj6f4CyuUaIl4DtRPQsZtDuO/KI3UzxzSEKAAqBQKAQTNba8x/hO3aGeK1asGDl+/PiMKVOmtIvR7ZMwn7Qyxg51t3etWrUKAPCd73zn2Pnnn7/oggsueNs4l5ubu3jr1q2bGGPvDBkyBBUVFd16xqlTp0RwaSNGjFj/8ccfLxw/fvwzbre7SD/ndblcX3e5XDOHDRtWpmna+wB2McYqEVvdTgKQhdjc0gsBzAXQH6ao9DU1NR9+73vfe2T9+vXHAdQhZvs9K26FQqzc6PLly3+7bNmyOR6PZ0K8e1yaBepsAqc6XQS3E5hEdz4H99sBVSSfVnU6Xdm6y+RvVVW54eWDxAsUU6KPrweOtgW8GDsoWQrDGHP/8Ic//JMYSSHJ5Caif+paS9YTKjF69Gg2ZsyYdxoaGn6dnZ292JBHL7zwwsevvvrqS1577bVaU6dMOnm9XkQiERowYAAvLS19b9GiRV999NFHH8vIyJgtqDIDjLGJjLGJ+nFYYE99sI+KwHfs2LGytLR0taqqjYyxen1pSBVnKYnf4eGHH67/+te/fnNpaelWxFmbRjJYWlEGVRMriAAHc0L1zsiROIqf5TP0SO5k0uBynY3s6RQ4IiKy8CRKBE7bTh2JRMSpaJbg7Abr10kGlyQpwBhLg75uaJI5RVGUbJ2KuM31J8P67tmzhwDQG2+88WtFUTpmCbnd7qFPP/30vYZ815NwK0bkwqNHj/Jp06bxlStXVmRmZl5XVla2NBQK2YUo8CFm00yzAicRRRsaGrY/88wzP5o4ceJKVVUbGGMniciJ3BlPgZlUuvvuu9lFF130aV1d3e3xntMBUHPm1iBNZANl+qwAQ3ngMYXJTMbEwvXIaX7h56cxxpge8tEp5SRB3gsIbQu43W5JVVWzqSUROMW6mF6X3+fzSZqmoa2tzVL73F0ziiRJgTM5iuueQGmGzMMYyzbOeTyetGQ73qJFi2qbm5vvJSJDQ8Oys7N/dPLkye/feOONVpQhS3ie43f76KOPKDU1VQFwauTIkY8/9dRT127ZsuXumpqad0Oh0HF95o21bUhVW4LB4MGKioo1zz777JKvfvWrixctWvQBYpPYT+iUM6FSSPxWbrc7oK8/2630q1/9imbOnMnGjBmzqqWl5UXxMR6PJ02WZdZJBk1gU3BCpSQAwVOnTi2vqqrq297ennr48OGDbrebxYm2HpeNZIxRU1PTurq6uoa6ujp3JBLR6uvrw0OGDImndbUsIyJfe3v7M1VVVf86efJkemtra2TXrl2nsrKy4ilzLOVhIkJLS8sLNTU1h2pqatJDoRBfs2ZNtcvlourq6rhsbbKptrb2xfr6+qra2lqvpmlST8DJGGPNzc11iMWsbdUVJLcfP368qKmpKa0hpm70JglSGjRo0PubNm26KRQKjQ6FQh5ZlqXm5mb6/PPP0xCLJdTxHRsbG+88duzYgKampoy6urr6ZNrf1tYGfeYM3XXXXXtkWT5SWlq6pri4uHDGjBmD8vLyclNSUtIZYx7d7BUJBoPN+/fvr928efPx8vLy5mPHjrUjFvGhhYjadGDGfd8xY8bg888/R3Nz89LKysphdXV16S0tLW07d+40ZkF1S1zYsGEDAYi+/fbb9w8ePHhfMBjMVlVVqqysPF5RUREFQF0AaiODOmIhJUkKHTp06G/vvvtuZk1Njc/r9WLkyJHtKSkpGhK7zlnZItnx48e3v/DCC/srKirSVVVlgwcPDhcUFKgJnOWtBpaUHTt2fLhs2bJ/HT16NItzLhcXF0dGjhypeDwecgpOYwCdMWPGP3fu3LkXscWK3R6PRykoKIiaPJbIrE1MNuXn528CsFt/jnwmtIiSJEU55/zqq69mOTk5zwHoq7PAYIyFicixHFZQUABVVdULL7xwDYAPcTqUi4bT0/+5oKF9QXiexBhrJyLHDum6SKIhNpEi+tFHH7UCqHvppZf2Cay7MTmdCRxbVJdPwwCMd3TEf3/++ed48cUXWWZm5l8A9BH+RY/iJOXl5WHWrFl8wYIFRwD8QecuDK4jCIDYCUIKB4oBjOTASAJGMeSOyMXaYV5clAKgFsBtAI7oH9uWEhIRBYNBVlVV5Wpra2OSJFFWVlY0Pz8/Ki4+hPgeQJ3Ot7a24sCBA+6amhpJVVVKT0/nQ4cOVfLy8lRhDUxHVLShoYFt375dLisrc4XDYRYIBNQJEyYokyZNUoVAYYnAyYkIW7duZU8++aRcUVHhaW9vZ5qmqf369VMeeughtbS0tJPM2VOuVOh0Es5MiuL0fFIJp6MlAKcjK/JuttMljPFWdYnXGWBTeqg5ZegcPFYSyrmFBZF6+C+MdzTeTzsD/8Sly+3Gt9MAKOwYIYWAYjIBtB/WDvN1Bugh/eXjmUc6fG8NCmewt4LW0axMiuvFI0zoJiIiWZbBOSeL9T5FYNmxzhSNRjvmlDLGyOVykR6gGjZKMFgBV1EUYoyhsbGRKisrkZqaSllZWejbt28n171zwDuoN/0HJ5dqQg0XyKQDJZHl4j0WGthklqHvVL9QHwksWjyvobh1ut1uOxY7nkNCl2OPxwMAlJubi9zc3C4O773g7E1nBKDmkOwWAHUUPc+GkiWSMYHELoRWoEsWnIlU5fHMKHbHXbYiIHvB2ZvOGAVNANBE4LEDS7xZJonmXXKbuhPdn0jJ42RRo25te6lmb/p3A9TJtDAnbG0iZY6dgiZpu6dDzTEcsLVxI+L3grM3nVWAmlciMgGUCZ2ZOWRxndg9ndhFeQLgJhsFwammthecvek/i4LGURLZRT5IRFntANYdcCbraxs31hESx0cC4jvOM9EX9j8dnOfQfNPe5JCCMopN4zYoqBexpQNkJJgYbQO4ePIgR+J4tE6ngCWzwFNSfrboOqvmXOntxBhr6e3qXwIKqvvgktwZoBmIBVUK2bC7tp3YivKgq2Y1kauUGTx2ywBY1UMO25lwQaFz9P+SpoIAzOvt6ucoQDWANIATEOWxrBBYlNBha/QCmNT7qc7NxGMTkHvTuQpQ3VOYa0CEgDYNaJHBT4VR29aOwzIHSIgiRgBYPK2JldDKYR/Kr2sSvdlELtu8z0zXG95eLE6WQADS4JJy4HFBkLlVEE6AK7zTc6WudNSpe0OiWAyJPIednAcSWm+V6FlfFaw3nW0KygFFA8IEtHCgPopTlf/CD2XAk60BjIMMZ0aKHVtPUTNyGLF1CVr1rZjb9RxCzAFTE8DTeSEw86JahgukR8hefevT98WtsR/LDD5ocGl9kOFZiQsG5cDjaoIWfRvB+muR2c8FhlcROvVLhCqz4HFxuNnpdkgAZ7GXE9cBN46t1gY3rxsuHmumcnEirhrnfvP19pkMeYXxXoCe6zKoxoEIB4IcOMWBFA2aK4QTYRVII8Bt1QcM2VXsW4oOvFY9t+jboABWYxu1pJwuEzjdQvZYgDPFBMgUIYtlUQaE1Exkel7H+bMnIsOvgLR7UPnxi2ipzYF7/GVIPe/HSM2LQqq/B23/AiRvJ6uSoQFlnfnHTgK8ZgFGxQTcqA2Q7cCZ6HozYKPCj4m1T+vt5uc+QNs50EKAVwUkHitrIcCnxdwBGbdgV8VjVQBnUKCSbfo2oufTUxuMOEnmSQh2VNNtAqfXRDXF7BGALQMA74+MlE2Y851BSM3iIP48atevQu32CNx0D2qPDkHh/IFwFy2Bf3g65GO3Ibw1pi/jp1EpCc028+0uxNcHi4yC+MqKvo0K45OqNz2agCprNhS482jaC9BzGaA8xrZGtBh7Cw1QOdCuAQEOeDRA1lnbjjk7qg1L22aillasbbiDrZVNIDVTUNm07zKVSxbXiGHxjS3RpSjIXoUZtxbDX8hB0W0I/v1O7H8lAi8Ahu1ojS5Dw/EnkLskDVL/m+CZ6QM7+hNEN7YZSGM6FZUFwdUlUE/ZNHK5HcqlENgJZiNOW5VJ+o9gNudOZ+rt5ucwQK9iwJ8JKoBWDnBdHg1ynXpqALMDZ9QEzlYdhGaAhrqA07zOhBl0ZnnUDOJ4YI3VzyCBcKTtKSwYPR/jbs2G53wClAq0rSzF5lU6GywDBAku9iec3J8B94GHkf1LP9gFN8Lzf86HS7sWkecOg6sMkocYqFPzufBoUfslm1gM25iCCf4OWYATpnHIDFZjvxegX4rUIVG9QGA8BkiXBrj1fUmMUSTKnapAFVtNAG0TtiE9xxaQNPN4ThRDogzqFlhYY5tiUhQZ/hS/aC1H7ZUD0edxN6R8AsI1CN9egL++DqSDIV0ioT4ZHmhQtd+jMO9GBB7zxMI0ohlYcy+0258AgoDs7jRKqTayoyGQW5VbbbU4siVPcF/UxNpGLRQG77BeU8u5SkH/TMB1DAjHBK6oCkQJCGsmImD+54oOuoiwNbIi6EYUoQ/FN4GYeTUpTplIfWEqf4DAAI349yWw3yAWnqKFgBveQd06IMRdyINqsl9oIHjgx634W+NCfPu7AHsSwH9lAFc9Djn7UeA7KYxV5WrEap2+hhTndcRxCiZqzIXyqOl6s6zL4SzW3KUEvNfr6teb/g3JWNlb388gol8SkaIHYThORJc4rYufrkcmoseFesqIaDoRyeZn9qbe1JvigFPYH01E7wkrFP6diIqSBZSpzu8Q0Um9vhAR3U9EGVbX9qbe1JvsgXQDER0VwPkIEQW6CyJT3aVEtFOoez0Rje4FaW/qTYmBlElELxIR18HTTETXnAnwmECaQkRPCiANEdFig+XtTb3pbKT/D85E97Jm8YYnAAAAAElFTkSuQmCC'; } + + // ------------------------------------------------------------------ + $(hyperion).on("cmd-settings-update",function(event){ + var obj = event.response.data + Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) { + serverInfo[val] = obj[val]; + }); + leds = serverConfig.leds + updateLedLayout(); + }); }); diff --git a/bin/service/hyperion.systemd b/bin/service/hyperion.systemd index 8d9f1f2c..35817d1b 100644 --- a/bin/service/hyperion.systemd +++ b/bin/service/hyperion.systemd @@ -1,13 +1,14 @@ [Unit] -Description=Hyperion ambient light systemd service +Description=Hyperion ambient light systemd service for user %i After=network.target [Service] ExecStart=/usr/bin/hyperiond WorkingDirectory=/usr/share/hyperion/bin +User=%i TimeoutStopSec=5 KillMode=mixed -Restart=always +Restart=on-failure RestartSec=2 [Install] diff --git a/cmake/debian/postinst b/cmake/debian/postinst index a49b4795..aa7e5e1e 100644 --- a/cmake/debian/postinst +++ b/cmake/debian/postinst @@ -6,6 +6,7 @@ install_file() dest="$2" if [ ! -e "$dest" ] + then cp "$src" "${dest}" return 1 else @@ -15,10 +16,7 @@ install_file() } -echo "--- hyperion ambient light postinstall ---" -echo "- install configuration template" -mkdir -p /etc/hyperion -mkdir -p /usr/share/hyperion/custom-effects +echo "---Hyperion ambient light postinstall ---" #check system CPU_RPI=`grep -m1 -c 'BCM2708\|BCM2709\|BCM2710\|BCM2835' /proc/cpuinfo` @@ -27,25 +25,29 @@ CPU_X32X64=`uname -m | grep 'x86_32\|i686\|x86_64' | wc -l` #Check for a bootloader as Berryboot BOOT_BERRYBOOT=$(grep -m1 -c '\(/var/media\|/media/pi\)/berryboot' /etc/mtab) -#get current system ip + add default port -address=$(ip -o -4 a | awk '$2 == "eth0" { gsub(/\/.*/, "", $4); print $4 }')":8099" +#get current system ip +NET_IF=`netstat -rn | awk '/^0.0.0.0/ {thif=substr($0,74,10); print thif;} /^default.*UG/ {thif=substr($0,65,10); print thif;}'` +NET_IP=`ifconfig ${NET_IF} | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'` #check if hyperion is running HYPERION_RUNNING=false pgrep hyperiond > /dev/null 2>&1 && HYPERION_RUNNING=true +# search for users in system, returns first entry +FOUND_USR=`who | grep -o '^\w*\b'` || "root" + start_msg="" restart_msg="" -SERVICE_POSTFIX="" if grep -m1 systemd /proc/1/comm > /dev/null then echo "--> init deamon: systemd" # systemd $HYPERION_RUNNING && systemctl stop hyperiond 2> /dev/null - install_file /usr/share/hyperion/service/hyperion.systemd /etc/systemd/system/hyperiond.service && systemctl -q enable hyperiond.service - start_msg="--> systemctl start hyperiond" - systemctl start hyperiond + install_file /usr/share/hyperion/service/hyperion.systemd /etc/systemd/system/hyperiond@.service + systemctl enable hyperiond"@${FOUND_USR}".service + start_msg="--> systemctl start hyperiond for user ${FOUND_USR}" + systemctl start hyperiond"@${FOUND_USR}" elif [ -e /sbin/initctl ] then @@ -100,9 +102,12 @@ if [ $CPU_RPI -eq 1 ]; then fi fi +echo ${start_msg} + echo "-----------------------------------------------------------------------------" echo "--> Hyperion has been installed/updated!" -echo "--> For configuration, visit with your browser: ${address}" +echo "--> For configuration, visit with your browser: ${NET_IP}:8090" +echo "--> or if already used by another service try: ${NET_IP}:8091" $REBOOTMESSAGE echo "-----------------------------------------------------------------------------" echo "Webpage: www.hyperion-project.org" @@ -110,28 +115,12 @@ echo "Wiki: wiki.hyperion-project.org" echo "Forum: forum.hyperion-project.org" echo "-----------------------------------------------------------------------------" -# try to open the browser for desktops. TODO: add headless detection(?) -if [ $CPU_X32X64 -eq 1] -echo "--> Will open browser with target: ${address}" - if [[ -e /usr/bin/xdg-open ]] - then - xdg-open http://"$address" - elif [[ -e /usr/bin/x-www-browser ]] - then - x-www-browser http://"$address" - elif [[ -e /usr/bin/www-browser ]] - then - www-browser http://"$address" - fi -fi - if [ -e /opt/hyperion/ ] then echo echo "---------------------------------------------------------------------------------" - echo "- It seemd that you have an older version of hyperion installed in /opt/hyerion -" - echo "- please remove it and check your config to avoid problems -" + echo "- It seemd that you have an older version of hyperion installed in /opt/hyperion -" + echo "- please remove it to avoid problems -" echo "---------------------------------------------------------------------------------" fi - diff --git a/cmake/debian/preinst b/cmake/debian/preinst index 335aa696..515b5200 100644 --- a/cmake/debian/preinst +++ b/cmake/debian/preinst @@ -1,51 +1,56 @@ #!/bin/sh -# check which init script we should use -USE_SYSTEMD=`grep -m1 -c systemd /proc/1/comm` -USE_INITCTL=`which /sbin/initctl | wc -l` -USE_SERVICE=`which /usr/sbin/service | wc -l` +echo "---Hyperion ambient light preinst ---" -#check for hyperion install -if [ -d /usr/share/hyperion/bin ];then - if [ -e /etc/hyperion/hyperion.config.json ];then - file=`grep -m1 -c '"general"' /etc/hyperion/hyperion.config.json` - if [ $file -ne 1 ]; then - echo "--> It seems you are running an old version of Hyperion (1.X). Will create a backup at /usr/share/hyperion/Backup_Hyperion_1.0 and reset configuration / system service" - - # Stop hyperion daemon if it is running - echo '---> Stop Hyperion, if necessary' - if [ $USE_SYSTEMD -eq 1 ]; then - service hyperion stop 2>/dev/null - elif [ $USE_INITCTL -eq 1 ]; then - /sbin/initctl stop hyperion 2>/dev/null - elif [ $USE_SERVICE -eq 1 ]; then - /usr/sbin/service hyperion stop 2>/dev/null - fi - - #Backup - echo "--> Move old config(s) and files to /usr/share/hyperion/Backup_Hyperion_1.0" - mkdir /usr/share/hyperion/Backup_Hyperion_1.0 - mv /usr/share/hyperion /usr/share/hyperion/Backup_Hyperion_1.0 - mv /etc/hyperion/* /usr/share/hyperion/Backup_Hyperion_1.0 +# search for users in system, returns first entry +FOUND_USR=`who | grep -o '^\w*\b'` || "root" - #Disabling and delete service files - if [ $USE_SYSTEMD -eq 1 ]; then - # Delete and disable Hyperion systemd script - echo '---> Delete and disable Hyperion systemd service' - systemctl disable hyperion.service - rm -v /etc/systemd/system/hyperion* 2>/dev/null - elif [ $USE_INITCTL -eq 1 ]; then - echo '---> Delete and disable Hyperion initctl script' - rm -v /etc/init/hyperion* 2>/dev/null - initctl reload-configuration - elif [ $USE_SERVICE -eq 1 ]; then - # Delete and disable Hyperion init.d script - echo '---> Delete and disable Hyperion init.d script' - update-rc.d -f hyperion remove - rm /etc/init.d/hyperion* 2>/dev/null - fi - - echo "--> Hyperion 1.0 installation has been moved" - fi +# stop running daemon before we install +if pgrep hyperiond > /dev/null 2>&1 +then + if grep -m1 systemd /proc/1/comm > /dev/null + then + echo "--> stop init deamon: systemd" + # systemd + systemctl stop hyperiond"@${FOUND_USR}" 2> /dev/null + + elif [ -e /sbin/initctl ] + then + echo "--> stop init deamon: upstart" + # upstart + initctl stop hyperiond + + else + echo "--> stop init deamon: sysV" + # sysV + service hyperiond stop 2>/dev/null fi fi + + +#$USR=hyperionIS; + +#addToGroup() +##{ +# getent group $1 && adduser $USR $1; +#} + +#check if user exists +#if id $USR >/dev/null 2>&1; then +# echo "--> hyperion user exists, skip creation"; +#else + ## create user +# echo "--> Create Hyperion user"; +# adduser --system --group $USR; +#fi + +# add user to groups if required +## secondary user groups that are required to access system things +#addToGroup(dialout); +#addToGroup(video); +#addToGroup(audio); +#addToGroup(systemd-journal); +# platform specific groups +#addToGroup(i2c); +#addToGroup(spi); +#addToGroup(gpio); diff --git a/cmake/debian/prerm b/cmake/debian/prerm new file mode 100644 index 00000000..748ed9e5 --- /dev/null +++ b/cmake/debian/prerm @@ -0,0 +1,40 @@ +#!/bin/sh + +echo "---Hyperion ambient light prerm ---" + +# search for users in system, returns first entry +FOUND_USR=`who | grep -o '^\w*\b'` || "root" + +# stop running daemon before we delete it +HYPERION_RUNNING=false +pgrep hyperiond > /dev/null 2>&1 && HYPERION_RUNNING=true + +if grep -m1 systemd /proc/1/comm > /dev/null +then + echo "--> stop init deamon: systemd" + # systemd + $HYPERION_RUNNING && systemctl stop hyperiond"@${FOUND_USR}" 2> /dev/null + # disable user specific symlink + echo "--> Disable service and remove entry" + systemctl -q disable hyperiond"@${FOUND_USR}" + rm -v /etc/systemd/system/hyperiond@.service 2>/dev/null + +elif [ -e /sbin/initctl ] +then + echo "--> stop init deamon: upstart" + # upstart + $HYPERION_RUNNING && initctl stop hyperiond + echo "--> Remove upstart service" + rm -v /etc/init/hyperion* 2>/dev/null + initctl reload-configuration + +else + echo "--> stop init deamon: sysV" + # sysV + $HYPERION_RUNNING && service hyperiond stop 2>/dev/null + echo "--> Remove sysV service" + update-rc.d -f hyperion remove + rm /etc/init.d/hyperion* 2>/dev/null +fi + +return 0 diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 7fef5d09..0d016880 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -13,9 +13,9 @@ SET ( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ) SET ( CPACK_DEBIAN_PACKAGE_MAINTAINER "Hyperion Team") SET ( CPACK_DEBIAN_PACKAGE_NAME "Hyperion" ) -SET ( CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/preinst" ) +SET ( CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/preinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/prerm" ) SET ( CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://www.hyperion-project.org" ) -SET ( CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5core5a (>= 5.2.0), libqt5network5 (>= 5.2.0), libqt5gui5 (>= 5.2.0), libqt5serialport5 (>= 5.2.0), libavahi-core7 (>= 0.6.31), libavahi-compat-libdnssd1 (>= 0.6.31), libusb-1.0-0, libpython3.4, libc6" ) +SET ( CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5core5a (>= 5.5.0), libqt5network5 (>= 5.5.0), libqt5gui5 (>= 5.5.0), libqt5serialport5 (>= 5.5.0), libqt5sql5 (>= 5.5.0), libavahi-core7 (>= 0.6.31), libavahi-compat-libdnssd1 (>= 0.6.31), libusb-1.0-0, libpython3.5, libc6" ) SET ( CPACK_DEBIAN_PACKAGE_SECTION "Miscellaneous" ) SET ( CPACK_RPM_PACKAGE_NAME "Hyperion" ) diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 30375404..c5313fca 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -20,14 +20,14 @@ /// Device configuration contains the following fields: /// * 'name' : The user friendly name of the device (only used for display purposes) - /// * 'type' : The type of the device or leds (known types for now are - /// APA102, WS2801, P9813, LPD6803, LPD8806, ---------PWM---------, WS2812b (just RPi1), WS281X (RPi1, RPi2, RPi3), --------OTHER--------, PhilipsHUE, AtmoOrb, PiBlaster, Tinkerforge, FadeCandy, RawHID (USB), UDP, SEDU, TPM2, USBASP-WS2801, USBASP-WS2812, ------3rd PARTY------, Adalight, AdalightAPA102, Atmo, Lightpack, Multi-Lightpack, Paintpack, Test (file), None) + /// * 'type' : The type of the device /// * [device type specific configuration] /// * 'colorOrder' : The order of the color bytes ('rgb', 'rbg', 'bgr', etc.). /// * 'rewriteTime': in ms. Data is resend to leds, if no new data is available in thistime. 0 means no refresh "device" : { "type" : "file", + "hardwareLedCount" : 1, "output" : "/dev/null", "rate" : 1000000, "colorOrder" : "rgb", @@ -98,8 +98,7 @@ }, /// Configuration for the embedded V4L2 grabber - /// * device : V4L2 Device to use [default="/dev/video0"] - /// * input : V4L2 input to use [default=0] + /// * device : V4L2 Device to use [default="auto"] (Auto detection) /// * standard : Video standard (PAL/NTSC/SECAM/NO_CHANGE) [default="NO_CHANGE"] /// * sizeDecimation : Size decimation factor [default=8] /// * cropLeft : Cropping from the left [default=0] @@ -118,7 +117,6 @@ [ { "device" : "auto", - "input" : 0, "standard" : "NO_CHANGE", "sizeDecimation" : 8, "priority" : 240, @@ -233,13 +231,22 @@ "port" : 19444 }, - /// The configuration of the Proto server which enables the protobuffer remote interface + /// The configuration of the Protobuffer server which enables the Protobuffer remote interface /// * port : Port at which the protobuffer server is started "protoServer" : { "port" : 19445 }, + /// The configuration of the Flatbuffer server which enables the Flatbuffer remote interface + /// * port : Port at which the flatbuffer server is started + "flatbufServer" : + { + "enable" : true, + "port" : 19400, + "timeout" : 5 + }, + /// The configuration of the boblight server which enables the boblight remote interface /// * enable : Enable or disable the boblight server (true/false) /// * port : Port at which the boblight server is started @@ -269,12 +276,10 @@ }, /// Configuration of the Hyperion webserver - /// * enable : enable or disable the webserver (true/false) /// * document_root : path to hyperion webapp files (webconfig developer only) /// * port : the port where hyperion webapp is accasible "webConfig" : { - "enable" : true, "document_root" : "/path/to/files", "port" : 8090 }, diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 33f27ece..7ced8462 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -12,6 +12,7 @@ "device" : { "type" : "file", + "hardwareLedCount" : 1, "output" : "/dev/null", "rate" : 1000000, "colorOrder" : "rgb", @@ -58,7 +59,6 @@ [ { "device" : "auto", - "input" : 0, "standard" : "NO_CHANGE", "sizeDecimation" : 8, "cropLeft" : 0, @@ -135,6 +135,13 @@ "port" : 19445 }, + "flatbufServer" : + { + "enable" : true, + "port" : 19400, + "timeout" : 5 + }, + "boblightServer" : { "enable" : false, @@ -154,7 +161,6 @@ "webConfig" : { - "enable" : true, "document_root" : "", "port" : 8090 }, diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index e0443059..9d91a34e 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -9,6 +9,48 @@ if(ENABLE_WS281XPWM) external/rpi_ws281x/rpihw.c) endif() +set(USE_SYSTEM_FLATBUFFERS_LIBS ${DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS} CACHE BOOL "use flatbuffers library from system") + +if (USE_SYSTEM_FLATBUFFERS_LIBS) + find_package(flatbuffers REQUIRED) + include_directories(${FLATBUFFERS_INCLUDE_DIRS}) +else () + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared flatbuffers library") + set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "Build Flatbuffers with tests") + add_subdirectory(external/flatbuffers) + + if(CMAKE_CROSSCOMPILING) + # when crosscompiling import the flatc executable targets from a file generated by a native build + option(IMPORT_FLATC "flatc export file (flatc_export.cmake) from a native build" "IMPORT_FLATC-FILE_NOT_FOUND") + include(${IMPORT_FLATC}) + else() + # export the flatc compiler so it can be used when cross compiling + export(TARGETS flatc FILE "${CMAKE_BINARY_DIR}/flatc_export.cmake") + endif() + + # define the include for the flatbuffers library at the parent scope + set(FLATBUFFERS_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/flatbuffers/include") + set(FLATBUFFERS_INCLUDE_DIRS ${FLATBUFFERS_INCLUDE_DIRS} PARENT_SCOPE) + + # define the flatc executable at the parent scope + get_property(FLATBUFFERS_FLATC_EXECUTABLE TARGET flatc PROPERTY LOCATION) + set(FLATBUFFERS_FLATC_EXECUTABLE ${FLATBUFFERS_FLATC_EXECUTABLE} PARENT_SCOPE) +endif() + +message(STATUS "Using flatbuffers compiler: " ${FLATBUFFERS_FLATC_EXECUTABLE}) + + +function(compile_flattbuffer_schema SRC_FBS OUTPUT_DIR) + string(REGEX REPLACE "\\.fbs$" "_generated.h" GEN_HEADER ${SRC_FBS}) + add_custom_command( + OUTPUT ${GEN_HEADER} + COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" -c --no-includes --gen-mutable + --gen-object-api + -o "${OUTPUT_DIR}" + "${SRC_FBS}" + DEPENDS flatc) +endfunction() + set(USE_SYSTEM_PROTO_LIBS ${DEFAULT_USE_SYSTEM_PROTO_LIBS} CACHE BOOL "use protobuf library from system") if (USE_SYSTEM_PROTO_LIBS) diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h new file mode 100644 index 00000000..580975d2 --- /dev/null +++ b/include/api/JsonAPI.h @@ -0,0 +1,254 @@ +#pragma once + +// hyperion includes +#include +#include +#include +#include + +// qt includess +#include +#include +#include +#include + +// createEffect helper +struct find_schema: std::unary_function +{ + QString pyFile; + find_schema(QString pyFile):pyFile(pyFile) { } + bool operator()(EffectSchema const& schema) const + { + return schema.pyFile == pyFile; + } +}; + +// deleteEffect helper +struct find_effect: std::unary_function +{ + QString effectName; + find_effect(QString effectName) :effectName(effectName) { } + bool operator()(EffectDefinition const& effectDefinition) const + { + return effectDefinition.name == effectName; + } +}; + +class JsonCB; + +class JsonAPI : public QObject +{ + Q_OBJECT + +public: + /// + /// Constructor + /// + /// @param peerAddress provide the Address of the peer + /// @param log The Logger class of the creator + /// @param parent Parent QObject + /// @param noListener if true, this instance won't listen for hyperion push events + /// + JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener = false); + + /// + /// Handle an incoming JSON message + /// + /// @param message the incoming message as string + /// + void handleMessage(const QString & message, const QString& httpAuthHeader = ""); + +public slots: + /// _timer_ledcolors requests ledcolor updates (if enabled) + void streamLedcolorsUpdate(); + + /// push images whenever hyperion emits (if enabled) + void setImage(const Image & image); + + /// process and push new log messages from logger (if enabled) + void incommingLogMessage(Logger::T_LOG_MESSAGE); + +signals: + /// + /// Signal emits with the reply message provided with handleMessage() + /// + void callbackMessage(QJsonObject); + + /// + /// Signal emits whenever a jsonmessage should be forwarded + /// + void forwardJsonMessage(QJsonObject); + +private: + + // The JsonCB instance which handles data subscription/notifications + JsonCB* _jsonCB; + // true if further callbacks are forbidden (http) + bool _noListener; + /// The peer address of the client + QString _peerAddress; + + /// Log instance + Logger* _log; + + /// Hyperion instance + Hyperion* _hyperion; + + /// timer for ledcolors streaming + QTimer _timer_ledcolors; + + // streaming buffers + QJsonObject _streaming_leds_reply; + QJsonObject _streaming_image_reply; + QJsonObject _streaming_logging_reply; + + /// flag to determine state of log streaming + bool _streaming_logging_activated; + + /// mutex to determine state of image streaming + QMutex _image_stream_mutex; + + /// timeout for live video refresh + volatile qint64 _image_stream_timeout; + + /// + /// Handle an incoming JSON Color message + /// + /// @param message the incoming message + /// + void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Image message + /// + /// @param message the incoming message + /// + void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message + /// + /// @param message the incoming message + /// + void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message (Write JSON Effect) + /// + /// @param message the incoming message + /// + void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message (Delete JSON Effect) + /// + /// @param message the incoming message + /// + void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON System info message + /// + /// @param message the incoming message + /// + void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Server info message + /// + /// @param message the incoming message + /// + void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Clear message + /// + /// @param message the incoming message + /// + void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Adjustment message + /// + /// @param message the incoming message + /// + void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON SourceSelect message + /// + /// @param message the incoming message + /// + void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON GetConfig message and check subcommand + /// + /// @param message the incoming message + /// + void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON GetConfig message from handleConfigCommand() + /// + /// @param message the incoming message + /// + void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON SetConfig message from handleConfigCommand() + /// + /// @param message the incoming message + /// + void handleConfigSetCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Component State message + /// + /// @param message the incoming message + /// + void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Led Colors message + /// + /// @param message the incoming message + /// + void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Logging message + /// + /// @param message the incoming message + /// + void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Proccessing message + /// + /// @param message the incoming message + /// + void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON VideoMode message + /// + /// @param message the incoming message + /// + void handleVideoModeCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON message of unknown type + /// + void handleNotImplemented(); + + /// + /// Send a standard reply indicating success + /// + void sendSuccessReply(const QString &command="", const int tan=0); + + /// + /// Send a standard reply indicating success with data + /// + void sendSuccessDataReply(const QJsonDocument &doc, const QString &command="", const int &tan=0); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// + void sendErrorReply(const QString & error, const QString &command="", const int tan=0); +}; diff --git a/include/api/JsonCB.h b/include/api/JsonCB.h new file mode 100644 index 00000000..98d1c3bb --- /dev/null +++ b/include/api/JsonCB.h @@ -0,0 +1,112 @@ +#pragma once + +// qt incl +#include +#include + +// components def +#include +// bonjour +#include +// videModes +#include +// settings +#include + +class Hyperion; +class ComponentRegister; +class BonjourBrowserWrapper; +class PriorityMuxer; + +class JsonCB : public QObject +{ + Q_OBJECT + +public: + JsonCB(QObject* parent); + + /// + /// @brief Subscribe to future data updates given by cmd + /// @param cmd The cmd which will be subscribed for + /// @return True on success, false if not found + /// + bool subscribeFor(const QString& cmd); + + /// + /// @brief Get all possible commands to subscribe for + /// @return The list of commands + /// + QStringList getCommands() { return _availableCommands; }; + /// + /// @brief Get all subscribed commands + /// @return The list of commands + /// + QStringList getSubscribedCommands() { return _subscribedCommands; }; +signals: + /// + /// @brief Emits whenever a new json mesage callback is ready to send + /// @param The JsonObject message + /// + void newCallback(QJsonObject); + +private slots: + /// + /// @brief handle component state changes + /// + void handleComponentState(const hyperion::Components comp, const bool state); + + /// + /// @brief handle emits from bonjour wrapper + /// @param bRegisters The full register map + /// + void handleBonjourChange(const QMap& bRegisters); + + /// + /// @brief handle emits from PriorityMuxer + /// + void handlePriorityUpdate(); + + /// + /// @brief Handle imageToLedsMapping updates + /// + void handleImageToLedsMappingChange(const int& mappingType); + + /// + /// @brief Handle the adjustment update + /// + void handleAdjustmentChange(); + + /// + /// @brief Handle video mode change + /// @param mode The new videoMode + /// + void handleVideoModeChange(const VideoMode& mode); + + /// + /// @brief Handle effect list change + /// + void handleEffectListChange(); + + /// + /// @brief Handle a config part change. This does NOT include (global) changes from other hyperion instances + /// @param type The settings type from enum + /// @param data The data as QJsonDocument + /// + void handleSettingsChange(const settings::type& type, const QJsonDocument& data); + +private: + /// pointer of Hyperion instance + Hyperion* _hyperion; + /// pointer of comp register + ComponentRegister* _componentRegister; + /// Bonjour instance + BonjourBrowserWrapper* _bonjour; + /// priority muxer instance + PriorityMuxer* _prioMuxer; + /// contains all available commands + QStringList _availableCommands; + /// contains active subscriptions + QStringList _subscribedCommands; + /// construct callback msg + void doCallback(const QString& cmd, const QVariant& data); +}; diff --git a/include/boblightserver/BoblightServer.h b/include/boblightserver/BoblightServer.h index 3b711816..d0e1567b 100644 --- a/include/boblightserver/BoblightServer.h +++ b/include/boblightserver/BoblightServer.h @@ -31,7 +31,7 @@ public: /// @param hyperion Hyperion instance /// @param port port number on which to start listening for connections /// - BoblightServer(const QJsonDocument& config); + BoblightServer(Hyperion* hyperion, const QJsonDocument& config); ~BoblightServer(); /// diff --git a/include/bonjour/bonjourserviceregister.h b/include/bonjour/bonjourserviceregister.h index 6e3e14ec..d0c65d87 100755 --- a/include/bonjour/bonjourserviceregister.h +++ b/include/bonjour/bonjourserviceregister.h @@ -47,6 +47,8 @@ public: void registerService(const BonjourRecord &record, quint16 servicePort, std::vector> txt = std::vector>()); inline BonjourRecord registeredRecord() const {return finalRecord; } + const quint16 & getPort() { return _port; }; + signals: void error(DNSServiceErrorType error); void serviceRegistered(const BonjourRecord &record); @@ -62,6 +64,9 @@ private: DNSServiceRef dnssref; QSocketNotifier *bonjourSocket; BonjourRecord finalRecord; + + // current port + quint16 _port = 0; }; #endif // BONJOURSERVICEREGISTER_H diff --git a/include/flatbufserver/FlatBufferConnection.h b/include/flatbufserver/FlatBufferConnection.h index a4c06a59..a9cc9eed 100644 --- a/include/flatbufserver/FlatBufferConnection.h +++ b/include/flatbufserver/FlatBufferConnection.h @@ -19,7 +19,7 @@ #include "hyperion_request_generated.h" /// -/// Connection class to setup an connection to the hyperion server and execute commands. Used from standalone capture binaries (x11/dispamnx/...) +/// Connection class to setup an connection to the hyperion server and execute commands. /// class FlatBufferConnection : public QObject { @@ -40,7 +40,7 @@ public: ~FlatBufferConnection(); /// Do not read reply messages from Hyperion if set to true - void setSkipReply(bool skip); + void setSkipReply(const bool& skip); /// /// Set all leds to the specified color @@ -116,8 +116,8 @@ private: /// Host port uint16_t _port; - /// Skip receiving reply messages from Hyperion if set - bool _skipReply; + /// buffer for reply + QByteArray _receiveBuffer; QTimer _timer; QAbstractSocket::SocketState _prevSocketState; diff --git a/include/flatbufserver/FlatBufferServer.h b/include/flatbufserver/FlatBufferServer.h index 799e3d92..eb751213 100644 --- a/include/flatbufserver/FlatBufferServer.h +++ b/include/flatbufserver/FlatBufferServer.h @@ -9,7 +9,6 @@ class QTcpServer; class FlatBufferClient; -class NetOrigin; /// /// @brief A TcpServer to receive images of different formats with Google Flatbuffer @@ -57,7 +56,6 @@ private: private: QTcpServer* _server; - NetOrigin* _netOrigin; Logger* _log; int _timeout; quint16 _port; diff --git a/include/grabber/DispmanxFrameGrabber.h b/include/grabber/DispmanxFrameGrabber.h index cd885015..82b14b35 100644 --- a/include/grabber/DispmanxFrameGrabber.h +++ b/include/grabber/DispmanxFrameGrabber.h @@ -42,7 +42,7 @@ public: /// ///@brief Set new width and height for dispmanx, overwrite Grabber.h impl - virtual void setWidthHeight(int width, int height); + virtual bool setWidthHeight(int width, int height); private: /// diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index 7277b69a..b4026131 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -14,6 +14,8 @@ #include #include +class QTimer; + /// Capture class for V4L2 devices /// /// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html @@ -23,7 +25,6 @@ class V4L2Grabber : public Grabber public: V4L2Grabber(const QString & device, - int input, VideoStandard videoStandard, PixelFormat pixelFormat, int pixelDecimation @@ -71,7 +72,7 @@ public: /// /// @brief overwrite Grabber.h implementation /// - virtual void setInputVideoStandard(int input, VideoStandard videoStandard); + virtual void setDeviceVideoStandard(QString device, VideoStandard videoStandard); public slots: @@ -86,6 +87,11 @@ signals: private slots: int read_frame(); + /// + /// @brief Is called whenever the _readFrameAdaptTimer emits to unlock read_frame() through _readFrame bool + /// + void unlockReadFrame() { _readFrame = true; }; + private: void getV4Ldevices(); @@ -161,4 +167,6 @@ private: bool _initialized; bool _deviceAutoDiscoverEnabled; + QTimer* _readFrameAdaptTimer; + bool _readFrame = false; }; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index dfa14014..e013e0ef 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -9,7 +9,6 @@ class V4L2Wrapper : public GrabberWrapper public: V4L2Wrapper(const QString & device, - int input, VideoStandard videoStandard, PixelFormat pixelFormat, int pixelDecimation ); diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index af833f33..ab7adf45 100755 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -42,7 +42,7 @@ public: /// /// @brief Apply new width/height values, overwrite Grabber.h implementation as X11 doesn't use width/height, just pixelDecimation to calc dimensions /// - virtual void setWidthHeight(int width, int height); + virtual bool setWidthHeight(int width, int height) { return true; }; /// /// @brief Apply new pixelDecimation diff --git a/include/hyperion/CaptureCont.h b/include/hyperion/CaptureCont.h index 4322776c..dd15e122 100644 --- a/include/hyperion/CaptureCont.h +++ b/include/hyperion/CaptureCont.h @@ -6,6 +6,7 @@ #include class Hyperion; +class QTimer; /// /// @brief Capture Control class which is a interface to the HyperionDaemon native capture classes. @@ -48,6 +49,11 @@ private slots: /// void handleV4lImage(const Image & image); + /// + /// @brief Is called from _v4lInactiveTimer to set source after specific time to inactive + /// + void setV4lInactive(); + private: /// Hyperion instance Hyperion* _hyperion; @@ -59,4 +65,5 @@ private: /// Reflect state of v4l capture and prio bool _v4lCaptEnabled; quint8 _v4lCaptPrio; + QTimer* _v4lInactiveTimer; }; diff --git a/include/hyperion/ComponentRegister.h b/include/hyperion/ComponentRegister.h index 91bd7228..4f1a13c6 100644 --- a/include/hyperion/ComponentRegister.h +++ b/include/hyperion/ComponentRegister.h @@ -33,9 +33,9 @@ public: /// /// @brief Check if a component is currently enabled /// @param comp The component from enum - /// @return True if component is running else false + /// @return True if component is running else false. Not found is -1 /// - bool isComponentEnabled(const hyperion::Components& comp) const; + int isComponentEnabled(const hyperion::Components& comp) const; /// contains all components and their state std::map getRegister() { return _componentStates; }; diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index 88207e03..16e2ee5e 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -36,7 +36,7 @@ public: /// /// @brief Apply new width/height values, on errors (collide with cropping) reject the values /// - virtual void setWidthHeight(int width, int height); + virtual bool setWidthHeight(int width, int height); /// /// @brief Apply new pixelDecimation (used from x11) @@ -66,9 +66,9 @@ public: virtual void setSignalDetectionEnable(bool enable) {}; /// - /// @brief Apply input and videoStanded (used from v4l) + /// @brief Apply device and videoStanded (used from v4l) /// - virtual void setInputVideoStandard(int input, VideoStandard videoStandard) {}; + virtual void setDeviceVideoStandard(QString device, VideoStandard videoStandard) {}; /// /// @brief Apply display index (used from x11) diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index ca70a436..2fc45f48 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -1,12 +1,13 @@ #pragma once #include +#include +#include #include #include #include #include -#include #include #include #include @@ -98,9 +99,6 @@ protected: QString _grabberName; - /// Pointer to Hyperion for writing led values - Hyperion * _hyperion; - /// The timer for generating events with the specified update rate QTimer* _timer; @@ -110,9 +108,6 @@ protected: /// The Logger instance Logger * _log; - // forwarding enabled - bool _forward; - Grabber *_ggrabber; /// The image used for grabbing frames diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index c39dce72..e981e1b5 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -49,6 +49,7 @@ class ColorAdjustment; class SettingsManager; class BGEffectHandler; class CaptureCont; +class BoblightServer; /// /// The main class of Hyperion. This gives other 'users' access to the attached LedDevice through @@ -105,6 +106,8 @@ public: /// PriorityMuxer* getMuxerInstance() { return &_muxer; }; + ImageProcessor* getImageProcessor() { return _imageProcessor; }; + /// /// @brief Get a setting by settings::type from SettingsManager /// @param type The settingsType from enum @@ -145,7 +148,7 @@ public: bool isCurrentPriority(const int priority) const; /// - /// Returns a list of active priorities + /// Returns a list of all registered priorities /// /// @return The list with priorities /// @@ -279,6 +282,13 @@ public slots: /// const bool setInputImage(const int priority, const Image& 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 + /// + const bool setInputInactive(const quint8& priority); + /// /// Writes a single color to all the leds for the given time and priority /// Registers comp color or provided type against muxer @@ -540,4 +550,7 @@ private: std::vector _ledBuffer; /// buffer for leds (without adjustment) std::vector _rawLedBuffer; + + /// Boblight instance + BoblightServer* _boblightServer; }; diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index 21af614b..e6a4b0ff 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -162,6 +162,13 @@ public: /// const bool setInputImage(const int priority, const Image& image, int64_t timeout_ms = -1); + /// + /// @brief Set the given priority to inactive + /// @param priority The priority + /// @return True on success false if not found + /// + const bool setInputInactive(const quint8& priority); + /// /// Clears the specified priority channel and update _currentPriority on success /// diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h index f3fa1d63..2394cc7b 100644 --- a/include/hyperion/SettingsManager.h +++ b/include/hyperion/SettingsManager.h @@ -6,7 +6,6 @@ // qt incl #include -class SettingsTable; class Hyperion; /// @@ -63,8 +62,6 @@ private: Hyperion* _hyperion; /// Logger instance Logger* _log; - /// instance of database table interface - SettingsTable* _sTable; /// the schema static QJsonObject schemaJson; /// the current config of this instance diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index e0c64206..a6c55053 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -8,12 +8,10 @@ #include #include -class Hyperion; class QTcpServer; class QTcpSocket; class JsonClientConnection; class BonjourServiceRegister; -class ComponentRegister; class NetOrigin; /// @@ -50,12 +48,7 @@ private slots: /// void closedConnection(void); - /// forward message to all json slaves - void forwardJsonMessage(const QJsonObject &message); - public slots: - /// process current forwarder state - void componentStateChanged(const hyperion::Components component, bool enable); /// /// forward message to a single json slaves @@ -75,18 +68,12 @@ private: /// The TCP server object QTcpServer * _server; - /// Link to Hyperion to get config state emiter - Hyperion * _hyperion; - /// List with open connections QSet _openConnections; /// the logger instance Logger * _log; - /// Component Register pointer - ComponentRegister* _componentRegister; - NetOrigin* _netOrigin; /// port diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h index e285a6cb..9ad9afa0 100644 --- a/include/protoserver/ProtoServer.h +++ b/include/protoserver/ProtoServer.h @@ -25,8 +25,6 @@ class ProtoConnection; class QTcpServer; class Hyperion; class BonjourServiceRegister; -class ComponentRegister; -class NetOrigin; namespace proto { class HyperionRequest; @@ -55,8 +53,6 @@ public: uint16_t getPort() const; public slots: - void sendImageToProtoSlaves(int priority, const Image & image, int duration_ms); - void componentStateChanged(const hyperion::Components component, bool enable); /// /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor @@ -65,12 +61,6 @@ public slots: /// void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config); -signals: - /// - /// Forwarding videoMode - /// - void videoMode(const VideoMode VideoMode); - private slots: /// /// Slot which is called when a client tries to create a new connection @@ -83,8 +73,6 @@ private slots: /// void closedConnection(ProtoClientConnection * connection); - void newMessage(const proto::HyperionRequest * message); - private: /// Hyperion instance Hyperion * _hyperion; @@ -94,26 +82,13 @@ private: /// List with open connections QSet _openConnections; - QStringList _forwardClients; - - /// Hyperion proto connection object for forwarding - QList _proxy_connections; /// Logger instance Logger * _log; - /// Component Register - ComponentRegister* _componentRegister; - - /// Network Origin Check - NetOrigin* _netOrigin; - /// Service register BonjourServiceRegister * _serviceRegister = nullptr; - /// flag if forwarder is enabled - bool _forwarder_enabled; - uint16_t _port = 0; /// Start server diff --git a/include/udplistener/UDPListener.h b/include/udplistener/UDPListener.h index bf294df6..0ab01d92 100644 --- a/include/udplistener/UDPListener.h +++ b/include/udplistener/UDPListener.h @@ -11,15 +11,13 @@ // Hyperion includes #include #include +#include // settings #include -class Hyperion; -class UDPClientConnection; class BonjourServiceRegister; class QUdpSocket; -class NetOrigin; /// /// This class creates a UDP server which accepts connections from boblight clients. @@ -67,6 +65,22 @@ public slots: /// 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& ledColors, const int timeout_ms = -1, const bool& clearEffect = true); + + /// + /// @brief forward clear to HyperionDaemon + /// + void clearGlobalPriority(const int& _priority, const hyperion::Components& component); + private slots: /// /// Slot which is called when a client tries to create a new connection @@ -75,15 +89,10 @@ private slots: void processTheDatagram(const QByteArray * datagram, const QHostAddress * sender); private: - /// Hyperion instance - Hyperion * _hyperion; /// The UDP server object QUdpSocket * _server; - /// List with open connections - QSet _openConnections; - /// hyperion priority int _priority; @@ -94,7 +103,7 @@ private: Logger * _log; /// Bonjour Service Register - BonjourServiceRegister* _bonjourService = nullptr; + BonjourServiceRegister* _serviceRegister = nullptr; /// state of connection bool _isActive; @@ -103,7 +112,4 @@ private: QHostAddress _listenAddress; uint16_t _listenPort; QAbstractSocket::BindFlag _bondage; - - /// Check Network Origin - NetOrigin* _netOrigin; }; diff --git a/include/utils/Components.h b/include/utils/Components.h index 3e917d9a..1840f9a3 100644 --- a/include/utils/Components.h +++ b/include/utils/Components.h @@ -22,7 +22,8 @@ enum Components COMP_IMAGE, COMP_EFFECT, COMP_PROTOSERVER, - COMP_LEDDEVICE + COMP_LEDDEVICE, + COMP_FLATBUFSERVER }; inline const char* componentToString(Components c) @@ -42,6 +43,7 @@ inline const char* componentToString(Components c) case COMP_IMAGE: return "Image"; case COMP_PROTOSERVER: return "Proto Server"; case COMP_LEDDEVICE: return "LED device"; + case COMP_FLATBUFSERVER: return "Image Receiver"; default: return ""; } } @@ -63,6 +65,7 @@ inline const char* componentToIdString(Components c) case COMP_IMAGE: return "IMAGE"; case COMP_PROTOSERVER: return "PROTOSERVER"; case COMP_LEDDEVICE: return "LEDDEVICE"; + case COMP_FLATBUFSERVER: return "FLATBUFSERVER"; default: return ""; } } @@ -83,7 +86,7 @@ inline Components stringToComponent(QString component) if (component == "IMAGE") return COMP_IMAGE; if (component == "PROTOSERVER") return COMP_PROTOSERVER; if (component == "LEDDEVICE") return COMP_LEDDEVICE; - + if (component == "FLATBUFSERVER") return COMP_FLATBUFSERVER; return COMP_INVALID; } diff --git a/include/utils/NetUtils.h b/include/utils/NetUtils.h new file mode 100644 index 00000000..1678947a --- /dev/null +++ b/include/utils/NetUtils.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +namespace NetUtils { + /// + /// @brief Check if the port is available for listening + /// @param[in/out] port The port to test, will be incremented if port is in use + /// @param log The logger of the caller to print + /// @return True on success else false + /// + static const bool portAvailable(quint16& port, Logger* log) + { + const quint16 prevPort = port; + QTcpServer server; + bool corrected = false; + while (!server.listen(QHostAddress::Any, port)) + { + corrected = true; + Warning(log,"Port '%d' is already in use, will increment", port); + port ++; + } + server.close(); + if(corrected) + { + Warning(log, "The requested Port '%d' was already in use, will use Port '%d' instead", prevPort, port); + return false; + } + return true; + } + +} diff --git a/include/utils/Stats.h b/include/utils/Stats.h index d2c2556c..7263f50b 100644 --- a/include/utils/Stats.h +++ b/include/utils/Stats.h @@ -16,6 +16,14 @@ class Stats : public QObject public: Stats(); + static Stats* getInstance() { return instance; }; + static Stats* instance; + + void handleDataUpdate(const QJsonObject& config); + +private: + friend class HyperionDaemon; + Stats(const QJsonObject& config); ~Stats(); private: diff --git a/include/utils/settings.h b/include/utils/settings.h index 7af1a262..9191b012 100644 --- a/include/utils/settings.h +++ b/include/utils/settings.h @@ -29,6 +29,7 @@ enum type { WEBSERVER, INSTCAPTURE, NETWORK, + FLATBUFSERVER, INVALID }; @@ -62,6 +63,7 @@ inline QString typeToString(const type& type) case WEBSERVER: return "webConfig"; case INSTCAPTURE: return "instCapture"; case NETWORK: return "network"; + case FLATBUFSERVER: return "flatbufServer"; default: return "invalid"; } } @@ -94,6 +96,7 @@ inline type stringToType(const QString& type) else if (type == "webConfig") return WEBSERVER; else if (type == "instCapture") return INSTCAPTURE; else if (type == "network") return NETWORK; + else if (type == "flatbufServer") return FLATBUFSERVER; else return INVALID; } }; diff --git a/include/webserver/WebServer.h b/include/webserver/WebServer.h index 7d0aaf39..d719f38c 100644 --- a/include/webserver/WebServer.h +++ b/include/webserver/WebServer.h @@ -5,13 +5,13 @@ #include #include -// hyperion / utils -#include +// utils include #include // settings #include +class BonjourServiceRegister; class StaticFileServing; class QtHttpServer; @@ -42,7 +42,6 @@ public slots: private: Logger* _log; - Hyperion* _hyperion; QString _baseUrl; quint16 _port; StaticFileServing* _staticFileServing; @@ -50,6 +49,8 @@ private: const QString WEBSERVER_DEFAULT_PATH = ":/webconfig"; const quint16 WEBSERVER_DEFAULT_PORT = 8090; + + BonjourServiceRegister * _serviceRegister = nullptr; }; #endif // WEBSERVER_H diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index d2d85b23..9173e1cd 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(commandline) add_subdirectory(blackborder) add_subdirectory(jsonserver) add_subdirectory(protoserver) +add_subdirectory(flatbufserver) add_subdirectory(bonjour) add_subdirectory(boblightserver) add_subdirectory(udplistener) diff --git a/libsrc/api/CMakeLists.txt b/libsrc/api/CMakeLists.txt new file mode 100644 index 00000000..64180a9c --- /dev/null +++ b/libsrc/api/CMakeLists.txt @@ -0,0 +1,21 @@ +# Define the current source locations + +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/api) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/api) + +FILE ( GLOB_RECURSE Api_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +set(Api_RESOURCES ${CURRENT_SOURCE_DIR}/JSONRPC_schemas.qrc ) + +add_library(hyperion-api + ${Api_SOURCES} + ${Api_RESOURCES} +) + +target_link_libraries(hyperion-api + hyperion + hyperion-utils + Qt5::Core + Qt5::Gui + Qt5::Network +) diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp new file mode 100644 index 00000000..29a9319f --- /dev/null +++ b/libsrc/api/JsonAPI.cpp @@ -0,0 +1,1069 @@ +// project includes +#include + +// stl includes +#include +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// hyperion includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// bonjour wrapper +#include + +// ledmapping int <> string transform methods +#include + +// api includes +#include + +using namespace hyperion; + +JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener) + : QObject(parent) + , _jsonCB(new JsonCB(this)) + , _noListener(noListener) + , _peerAddress(peerAddress) + , _log(log) + , _hyperion(Hyperion::getInstance()) + , _streaming_logging_activated(false) + , _image_stream_timeout(0) +{ + // 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); + // notify hyperion about a jsonMessageForward + connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); + + // led color stream update timer + connect(&_timer_ledcolors, SIGNAL(timeout()), this, SLOT(streamLedcolorsUpdate())); + _image_stream_mutex.unlock(); +} + +void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader) +{ + const QString ident = "JsonRpc@"+_peerAddress; + Q_INIT_RESOURCE(JSONRPC_schemas); + QJsonObject message; + // parse the message + if(!JsonUtils::parse(ident, messageString, message, _log)) + { + sendErrorReply("Errors during message parsing, please consult the Hyperion Log."); + return; + } + + // check basic message + if(!JsonUtils::validate(ident, message, ":schema", _log)) + { + sendErrorReply("Errors during message validation, please consult the Hyperion Log."); + return; + } + + // check specific message + const QString command = message["command"].toString(); + if(!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log)) + { + sendErrorReply("Errors during specific message validation, please consult the Hyperion Log"); + return; + } + + int tan = message["tan"].toInt(); + + // switch over all possible commands and handle them + if (command == "color") handleColorCommand (message, command, tan); + else if (command == "image") handleImageCommand (message, command, tan); + else if (command == "effect") handleEffectCommand (message, command, tan); + else if (command == "create-effect") handleCreateEffectCommand (message, command, tan); + else if (command == "delete-effect") handleDeleteEffectCommand (message, command, tan); + else if (command == "sysinfo") handleSysInfoCommand (message, command, tan); + else if (command == "serverinfo") handleServerInfoCommand (message, command, tan); + else if (command == "clear") handleClearCommand (message, command, tan); + else if (command == "adjustment") handleAdjustmentCommand (message, command, tan); + else if (command == "sourceselect") handleSourceSelectCommand (message, command, tan); + else if (command == "config") handleConfigCommand (message, command, tan); + else if (command == "componentstate") handleComponentStateCommand(message, command, tan); + else if (command == "ledcolors") handleLedColorsCommand (message, command, tan); + else if (command == "logging") handleLoggingCommand (message, command, tan); + else if (command == "processing") handleProcessingCommand (message, command, tan); + else if (command == "videomode") handleVideoModeCommand (message, command, tan); + else handleNotImplemented (); +} + +void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // extract parameters + int priority = message["priority"].toInt(); + int duration = message["duration"].toInt(-1); + QString origin = message["origin"].toString() + "@"+_peerAddress; + + std::vector colorData(_hyperion->getLedCount()); + const QJsonArray & jsonColor = message["color"].toArray(); + unsigned int i = 0; + for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i) + { + colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt()); + colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt()); + colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt()); + } + + // copy full blocks of led colors + unsigned size = i; + while (i + size < _hyperion->getLedCount()) + { + memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb)); + i += size; + } + + // copy remaining block of led colors + if (i < _hyperion->getLedCount()) + { + memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb)); + } + + // register and set color + _hyperion->registerInput(priority, hyperion::COMP_COLOR, origin); + _hyperion->setInput(priority, colorData, duration); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonAPI::handleImageCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // extract parameters + int priority = message["priority"].toInt(); + int duration = message["duration"].toInt(-1); + int width = message["imagewidth"].toInt(); + int height = message["imageheight"].toInt(); + QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); + + // check consistency of the size of the received data + if (data.size() != width*height*3) + { + sendErrorReply("Size of image data does not match with the width and height", command, tan); + return; + } + + // create ImageRgb + Image image(width, height); + memcpy(image.memptr(), data.data(), data.size()); + + _hyperion->registerInput(priority, hyperion::COMP_IMAGE, "JsonRpc@"+_peerAddress); + _hyperion->setInputImage(priority, image, duration); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // extract parameters + int priority = message["priority"].toInt(); + int duration = message["duration"].toInt(-1); + QString pythonScript = message["pythonScript"].toString(); + QString origin = message["origin"].toString() + "@"+_peerAddress; + const QJsonObject & effect = message["effect"].toObject(); + const QString & effectName = effect["name"].toString(); + + // set output + if (effect.contains("args")) + { + _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); + } + else + { + _hyperion->setEffect(effectName, priority, duration, origin); + } + + // send reply + sendSuccessReply(command, tan); +} + +void JsonAPI::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) +{ + if (!message["args"].toObject().isEmpty()) + { + QString scriptName; + (message["script"].toString().mid(0, 1) == ":" ) + ? scriptName = ":/effects//" + message["script"].toString().mid(1) + : scriptName = message["script"].toString(); + + std::list effectsSchemas = _hyperion->getEffectSchemas(); + std::list::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName)); + + if (it != effectsSchemas.end()) + { + if(!JsonUtils::validate("JsonRpc@"+_peerAddress, message["args"].toObject(), it->schemaFile, _log)) + { + sendErrorReply("Error during arg validation against schema, please consult the Hyperion Log", command, tan); + return; + } + + QJsonObject effectJson; + QJsonArray effectArray; + effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray(); + + if (effectArray.size() > 0) + { + if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) + { + sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan); + return; + } + + effectJson["name"] = message["name"].toString(); + effectJson["script"] = message["script"].toString(); + effectJson["args"] = message["args"].toObject(); + + std::list availableEffects = _hyperion->getEffects(); + std::list::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString())); + + QFileInfo newFileName; + if (iter != availableEffects.end()) + { + newFileName.setFile(iter->file); + if (newFileName.absoluteFilePath().mid(0, 1) == ":") + { + sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan); + return; + } + } else + { + QString f = FileUtils::convertPath(effectArray[0].toString() + "/" + message["name"].toString().replace(QString(" "), QString("")) + QString(".json")); + newFileName.setFile(f); + } + + if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log)) + { + sendErrorReply("Error while saving effect, please check the Hyperion Log", command, tan); + return; + } + + Info(_log, "Reload effect list"); + _hyperion->reloadEffects(); + sendSuccessReply(command, tan); + } else + { + sendErrorReply("Can't save new effect. Effect path empty", command, tan); + return; + } + } else + sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan); + } else + sendErrorReply("Missing or empty Object 'args'", command, tan); +} + +void JsonAPI::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) +{ + QString effectName = message["name"].toString(); + std::list effectsDefinition = _hyperion->getEffects(); + std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); + + if (it != effectsDefinition.end()) + { + QFileInfo effectConfigurationFile(it->file); + if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" ) + { + if (effectConfigurationFile.exists()) + { + bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); + if (result) + { + Info(_log, "Reload effect list"); + _hyperion->reloadEffects(); + sendSuccessReply(command, tan); + } else + sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan); + } else + sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan); + } else + sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan); + } else + sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan); +} + +void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan) +{ + // create result + QJsonObject result; + QJsonObject info; + result["success"] = true; + result["command"] = command; + result["tan"] = tan; + + SysInfo::HyperionSysInfo data = SysInfo::get(); + QJsonObject system; + system["kernelType" ] = data.kernelType; + system["kernelVersion" ] = data.kernelVersion; + system["architecture" ] = data.architecture; + system["wordSize" ] = data.wordSize; + system["productType" ] = data.productType; + system["productVersion"] = data.productVersion; + system["prettyName" ] = data.prettyName; + system["hostName" ] = data.hostName; + system["domainName" ] = data.domainName; + info["system"] = system; + + QJsonObject hyperion; + hyperion["jsonrpc_version" ] = QString(HYPERION_JSON_VERSION); + hyperion["version" ] = QString(HYPERION_VERSION); + hyperion["build" ] = QString(HYPERION_BUILD_ID); + hyperion["time" ] = QString(__DATE__ " " __TIME__); + hyperion["id" ] = _hyperion->getId(); + info["hyperion"] = hyperion; + + // send the result + result["info" ] = info; + emit callbackMessage(result); +} + +void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& command, const int tan) +{ + QJsonObject info; + + // collect priority information + QJsonArray priorities; + uint64_t now = QDateTime::currentMSecsSinceEpoch(); + QList activePriorities = _hyperion->getActivePriorities(); + activePriorities.removeAll(255); + int currentPriority = _hyperion->getCurrentPriority(); + + foreach (int priority, activePriorities) { + const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); + QJsonObject item; + item["priority"] = priority; + if (int(priorityInfo.timeoutTime_ms - now) > -1 ) + { + item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); + } + // owner has optional informations to the component + if(!priorityInfo.owner.isEmpty()) + item["owner"] = priorityInfo.owner; + + item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); + item["origin"] = priorityInfo.origin; + item["active"] = (priorityInfo.timeoutTime_ms >= -1); + item["visible"] = (priority == currentPriority); + + if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + { + QJsonObject LEDcolor; + + // add RGB Value to Array + QJsonArray RGBValue; + RGBValue.append(priorityInfo.ledColors.begin()->red); + RGBValue.append(priorityInfo.ledColors.begin()->green); + RGBValue.append(priorityInfo.ledColors.begin()->blue); + LEDcolor.insert("RGB", RGBValue); + + uint16_t Hue; + float Saturation, Luminace; + + // add HSL Value to Array + QJsonArray HSLValue; + ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); + + HSLValue.append(Hue); + HSLValue.append(Saturation); + HSLValue.append(Luminace); + LEDcolor.insert("HSL", HSLValue); + + item["value"] = LEDcolor; + } + // priorities[priorities.size()] = item; + priorities.append(item); + } + + info["priorities"] = priorities; + info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); + + // collect adjustment information + QJsonArray adjustmentArray; + for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) + { + const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); + if (colorAdjustment == nullptr) + { + Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId)); + continue; + } + + QJsonObject adjustment; + adjustment["id"] = adjustmentId; + + QJsonArray whiteAdjust; + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); + adjustment.insert("white", whiteAdjust); + + QJsonArray redAdjust; + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); + adjustment.insert("red", redAdjust); + + QJsonArray greenAdjust; + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); + adjustment.insert("green", greenAdjust); + + QJsonArray blueAdjust; + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); + adjustment.insert("blue", blueAdjust); + + QJsonArray cyanAdjust; + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); + adjustment.insert("cyan", cyanAdjust); + + QJsonArray magentaAdjust; + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); + adjustment.insert("magenta", magentaAdjust); + + QJsonArray yellowAdjust; + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); + adjustment.insert("yellow", yellowAdjust); + + adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); + adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); + adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); + adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); + adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); + adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); + adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); + + adjustmentArray.append(adjustment); + } + + info["adjustment"] = adjustmentArray; + + // collect effect info + QJsonArray effects; + const std::list & effectsDefinitions = _hyperion->getEffects(); + for (const EffectDefinition & effectDefinition : effectsDefinitions) + { + QJsonObject effect; + effect["name"] = effectDefinition.name; + effect["file"] = effectDefinition.file; + effect["script"] = effectDefinition.script; + effect["args"] = effectDefinition.args; + effects.append(effect); + } + + info["effects"] = effects; + + // get available led devices + QJsonObject ledDevices; + ledDevices["active"] = _hyperion->getActiveDevice(); + QJsonArray availableLedDevices; + for (auto dev: LedDevice::getDeviceMap()) + { + availableLedDevices.append(dev.first); + } + + ledDevices["available"] = availableLedDevices; + info["ledDevices"] = ledDevices; + + QJsonObject grabbers; + QJsonArray availableGrabbers; +#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) + // get available grabbers + //grabbers["active"] = ????; + for (auto grabber: GrabberWrapper::availableGrabbers()) + { + availableGrabbers.append(grabber); + } +#endif + grabbers["available"] = availableGrabbers; + info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode())); + info["grabbers"] = grabbers; + + // get available components + QJsonArray component; + std::map components = _hyperion->getComponentRegister().getRegister(); + for(auto comp : components) + { + QJsonObject item; + item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); + item["enabled"] = comp.second; + + component.append(item); + } + + info["components"] = component; + info["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()) + { + if (session.port<0) continue; + QJsonObject item; + item["name"] = session.serviceName; + item["type"] = session.registeredType; + item["domain"] = session.replyDomain; + item["host"] = session.hostName; + item["address"]= session.address; + item["port"] = session.port; + sessions.append(item); + } + info["sessions"] = sessions; + + sendSuccessDataReply(QJsonDocument(info), command, tan); + + // AFTER we send the info, the client might want to subscribe to future updates + if(message.contains("subscribe")) + { + // check if listeners are allowed + if(_noListener) + return; + + QJsonArray subsArr = message["subscribe"].toArray(); + // catch the all keyword and build a list of all cmds + if(subsArr.contains("all")) + { + subsArr = QJsonArray(); + for(const auto & entry : _jsonCB->getCommands()) + { + subsArr.append(entry); + } + } + for(const auto & entry : subsArr) + { + if(entry == "settings-update") + continue; + + if(!_jsonCB->subscribeFor(entry.toString())) + sendErrorReply(QString("Subscription for '%1' not found. Possible values: %2").arg(entry.toString(), _jsonCB->getCommands().join(", ")), command, tan); + } + } +} + +void JsonAPI::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + int priority = message["priority"].toInt(); + + if(priority > 0) + _hyperion->clear(priority); + else if(priority < 0) + _hyperion->clearall(); + else + { + sendErrorReply("Priority 0 is not allowed", command, tan); + return; + } + + // send reply + sendSuccessReply(command, tan); +} + +void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan) +{ + const QJsonObject & adjustment = message["adjustment"].toObject(); + + const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first()); + ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); + if (colorAdjustment == nullptr) + { + Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str()); + return; + } + + if (adjustment.contains("red")) + { + const QJsonArray & values = adjustment["red"].toArray(); + colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + + if (adjustment.contains("green")) + { + const QJsonArray & values = adjustment["green"].toArray(); + colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + + if (adjustment.contains("blue")) + { + const QJsonArray & values = adjustment["blue"].toArray(); + colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + if (adjustment.contains("cyan")) + { + const QJsonArray & values = adjustment["cyan"].toArray(); + colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + if (adjustment.contains("magenta")) + { + const QJsonArray & values = adjustment["magenta"].toArray(); + colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + if (adjustment.contains("yellow")) + { + const QJsonArray & values = adjustment["yellow"].toArray(); + colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + if (adjustment.contains("white")) + { + const QJsonArray & values = adjustment["white"].toArray(); + colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt()); + } + + if (adjustment.contains("gammaRed")) + { + colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB()); + } + if (adjustment.contains("gammaGreen")) + { + colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB()); + } + if (adjustment.contains("gammaBlue")) + { + colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble()); + } + + if (adjustment.contains("backlightThreshold")) + { + colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble()); + } + if (adjustment.contains("backlightColored")) + { + colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool()); + } + if (adjustment.contains("brightness")) + { + colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt()); + } + if (adjustment.contains("brightnessCompensation")) + { + colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt()); + } + + // commit the changes + _hyperion->adjustmentsUpdated(); + + sendSuccessReply(command, tan); +} + +void JsonAPI::handleSourceSelectCommand(const QJsonObject& message, const QString& command, const int tan) +{ + bool success = false; + if (message["auto"].toBool(false)) + { + _hyperion->setSourceAutoSelectEnabled(true); + success = true; + } + else if (message.contains("priority")) + { + success = _hyperion->setCurrentSourcePriority(message["priority"].toInt()); + } + + if (success) + { + sendSuccessReply(command, tan); + } + else + { + sendErrorReply("setting current priority failed", command, tan); + } +} + +void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& command, const int tan) +{ + QString subcommand = message["subcommand"].toString(""); + QString full_command = command + "-" + subcommand; + + if (subcommand == "getschema") + { + handleSchemaGetCommand(message, full_command, tan); + } + else if (subcommand == "setconfig") + { + handleConfigSetCommand(message, full_command, tan); + } + else if (subcommand == "getconfig") + { + sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); + } + else if (subcommand == "reload") + { + _hyperion->freeObjects(true); + Process::restartHyperion(); + sendErrorReply("failed to restart hyperion", full_command, tan); + } + else + { + sendErrorReply("unknown or missing subcommand", full_command, tan); + } +} + +void JsonAPI::handleConfigSetCommand(const QJsonObject& message, const QString &command, const int tan) +{ + if (message.contains("config")) + { + QJsonObject config = message["config"].toObject(); + if(_hyperion->getComponentRegister().isComponentEnabled(hyperion::COMP_ALL)) + { + if(_hyperion->saveSettings(config, true)) + sendSuccessReply(command,tan); + else + sendErrorReply("Failed to save configuration, more information at the Hyperion log", command, tan); + } + else + sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan); + } +} + +void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) +{ + // create result + QJsonObject schemaJson, alldevices, properties; + + // make sure the resources are loaded (they may be left out after static linking) + Q_INIT_RESOURCE(resource); + + // read the hyperion json schema from the resource + QString schemaFile = ":/hyperion-schema"; + + try + { + schemaJson = QJsonFactory::readSchema(schemaFile); + } + catch(const std::runtime_error& error) + { + throw std::runtime_error(error.what()); + } + + // collect all LED Devices + properties = schemaJson["properties"].toObject(); + alldevices = LedDevice::getLedDeviceSchemas(); + properties.insert("alldevices", alldevices); + + // collect all available effect schemas + QJsonObject pyEffectSchemas, pyEffectSchema; + QJsonArray in, ex; + const std::list & effectsSchemas = _hyperion->getEffectSchemas(); + for (const EffectSchema & effectSchema : effectsSchemas) + { + if (effectSchema.pyFile.mid(0, 1) == ":") + { + QJsonObject internal; + internal.insert("script", effectSchema.pyFile); + internal.insert("schemaLocation", effectSchema.schemaFile); + internal.insert("schemaContent", effectSchema.pySchema); + in.append(internal); + } + else + { + QJsonObject external; + external.insert("script", effectSchema.pyFile); + external.insert("schemaLocation", effectSchema.schemaFile); + external.insert("schemaContent", effectSchema.pySchema); + ex.append(external); + } + } + + if (!in.empty()) + pyEffectSchema.insert("internal", in); + if (!ex.empty()) + pyEffectSchema.insert("external", ex); + + pyEffectSchemas = pyEffectSchema; + properties.insert("effectSchemas", pyEffectSchemas); + + schemaJson.insert("properties", properties); + + // send the result + sendSuccessDataReply(QJsonDocument(schemaJson), command, tan); +} + +void JsonAPI::handleComponentStateCommand(const QJsonObject& message, const QString &command, const int tan) +{ + const QJsonObject & componentState = message["componentstate"].toObject(); + + QString compStr = componentState["component"].toString("invalid"); + bool compState = componentState["state"].toBool(true); + + Components component = stringToComponent(compStr); + + if (compStr == "ALL" ) + { + if(_hyperion->getComponentRegister().setHyperionEnable(compState)) + sendSuccessReply(command, tan); + else + sendErrorReply(QString("Hyperion is already %1").arg(compState ? "enabled" : "disabled"), command, tan ); + + return; + } + else if (component != COMP_INVALID) + { + // send result before apply + sendSuccessReply(command, tan); + _hyperion->setComponentState(component, compState); + return; + } + sendErrorReply("invalid component name", command, tan); +} + +void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &command, const int tan) +{ + // create result + QString subcommand = message["subcommand"].toString(""); + + if (subcommand == "ledstream-start") + { + _streaming_leds_reply["success"] = true; + _streaming_leds_reply["command"] = command+"-ledstream-update"; + _streaming_leds_reply["tan"] = tan; + _timer_ledcolors.start(125); + } + else if (subcommand == "ledstream-stop") + { + _timer_ledcolors.stop(); + } + else if (subcommand == "imagestream-start") + { + _streaming_image_reply["success"] = true; + _streaming_image_reply["command"] = command+"-imagestream-update"; + _streaming_image_reply["tan"] = tan; + connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection); + } + else if (subcommand == "imagestream-stop") + { + disconnect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage); + } + else + { + sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan); + return; + } + + sendSuccessReply(command+"-"+subcommand,tan); +} + +void JsonAPI::handleLoggingCommand(const QJsonObject& message, const QString &command, const int tan) +{ + // create result + QString subcommand = message["subcommand"].toString(""); + _streaming_logging_reply["success"] = true; + _streaming_logging_reply["command"] = command; + _streaming_logging_reply["tan"] = tan; + + if (subcommand == "start") + { + if (!_streaming_logging_activated) + { + _streaming_logging_reply["command"] = command+"-update"; + connect(LoggerManager::getInstance(),SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE))); + Debug(_log, "log streaming activated for client %s",_peerAddress.toStdString().c_str()); // needed to trigger log sending + } + } + else if (subcommand == "stop") + { + if (_streaming_logging_activated) + { + disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0); + _streaming_logging_activated = false; + Debug(_log, "log streaming deactivated for client %s",_peerAddress.toStdString().c_str()); + + } + } + else + { + sendErrorReply("unknown subcommand",command,tan); + return; + } + + sendSuccessReply(command+"-"+subcommand,tan); +} + +void JsonAPI::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) +{ + _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); + + sendSuccessReply(command, tan); +} + +void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &command, const int tan) +{ + _hyperion->setVideoMode(parse3DMode(message["videoMode"].toString("2D"))); + + sendSuccessReply(command, tan); +} + + +void JsonAPI::handleNotImplemented() +{ + sendErrorReply("Command not implemented"); +} + +void JsonAPI::sendSuccessReply(const QString &command, const int tan) +{ + // create reply + QJsonObject reply; + reply["success"] = true; + reply["command"] = command; + reply["tan"] = tan; + + // send reply + emit callbackMessage(reply); +} + +void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, const int &tan) +{ + QJsonObject reply; + reply["success"] = true; + reply["command"] = command; + reply["tan"] = tan; + if(doc.isArray()) + reply["info"] = doc.array(); + else + reply["info"] = doc.object(); + + emit callbackMessage(reply); +} + +void JsonAPI::sendErrorReply(const QString &error, const QString &command, const int tan) +{ + // create reply + QJsonObject reply; + reply["success"] = false; + reply["error"] = error; + reply["command"] = command; + reply["tan"] = tan; + + // send reply + emit callbackMessage(reply); +} + + +void JsonAPI::streamLedcolorsUpdate() +{ + QJsonObject result; + QJsonArray leds; + + const std::vector & ledColors = _hyperion->getRawLedBuffer(); + for(auto color = ledColors.begin(); color != ledColors.end(); ++color) + { + QJsonObject item; + item["index"] = int(color - ledColors.begin()); + item["red"] = color->red; + item["green"] = color->green; + item["blue"] = color->blue; + leds.append(item); + } + + result["leds"] = leds; + _streaming_leds_reply["result"] = result; + + // send the result + emit callbackMessage(_streaming_leds_reply); +} + +void JsonAPI::setImage(const Image & image) +{ + if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) ) + { + _image_stream_timeout = QDateTime::currentMSecsSinceEpoch(); + + QImage jpgImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + jpgImage.save(&buffer, "jpg"); + + QJsonObject result; + result["image"] = "data:image/jpg;base64,"+QString(ba.toBase64()); + _streaming_image_reply["result"] = result; + emit callbackMessage(_streaming_image_reply); + + _image_stream_mutex.unlock(); + } +} + +void JsonAPI::incommingLogMessage(Logger::T_LOG_MESSAGE msg) +{ + QJsonObject result, message; + QJsonArray messageArray; + + if (!_streaming_logging_activated) + { + _streaming_logging_activated = true; + QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); + for(int i=0; ilength(); i++) + { + message["appName"] = logBuffer->at(i).appName; + message["loggerName"] = logBuffer->at(i).loggerName; + message["function"] = logBuffer->at(i).function; + message["line"] = QString::number(logBuffer->at(i).line); + message["fileName"] = logBuffer->at(i).fileName; + message["message"] = logBuffer->at(i).message; + message["levelString"] = logBuffer->at(i).levelString; + + messageArray.append(message); + } + } + else + { + message["appName"] = msg.appName; + message["loggerName"] = msg.loggerName; + message["function"] = msg.function; + message["line"] = QString::number(msg.line); + message["fileName"] = msg.fileName; + message["message"] = msg.message; + message["levelString"] = msg.levelString; + + messageArray.append(message); + } + + result.insert("messages", messageArray); + _streaming_logging_reply["result"] = result; + + // send the result + emit callbackMessage(_streaming_logging_reply); +} diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp new file mode 100644 index 00000000..f1f8e109 --- /dev/null +++ b/libsrc/api/JsonCB.cpp @@ -0,0 +1,304 @@ +// proj incl +#include + +// hyperion +#include +// components +#include +// bonjour wrapper +#include +// priorityMuxer +#include +#include +#include + +// Image to led map helper +#include + +using namespace hyperion; + +JsonCB::JsonCB(QObject* parent) + : QObject(parent) + , _hyperion(Hyperion::getInstance()) + , _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"; +} + +bool JsonCB::subscribeFor(const QString& type) +{ + if(!_availableCommands.contains(type)) + return false; + + if(type == "components-update") + { + _subscribedCommands << type; + connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection); + } + + if(type == "sessions-update") + { + _subscribedCommands << type; + connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection); + } + + if(type == "priorities-update") + { + _subscribedCommands << type; + connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection); + connect(_prioMuxer, &PriorityMuxer::autoSelectChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection); + } + + if(type == "imageToLedMapping-update") + { + _subscribedCommands << type; + connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection); + } + + if(type == "adjustment-update") + { + _subscribedCommands << type; + connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection); + } + + if(type == "videomode-update") + { + _subscribedCommands << type; + connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection); + } + + if(type == "effects-update") + { + _subscribedCommands << type; + connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection); + } + + if(type == "settings-update") + { + _subscribedCommands << type; + connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection); + } + + return true; +} + +void JsonCB::doCallback(const QString& cmd, const QVariant& data) +{ + QJsonObject obj; + obj["command"] = cmd; + + if(static_cast(data.type()) == QMetaType::QJsonArray) + obj["data"] = data.toJsonArray(); + else + obj["data"] = data.toJsonObject(); + + emit newCallback(obj); +} + +void JsonCB::handleComponentState(const hyperion::Components comp, const bool state) +{ + QJsonObject data; + data["name"] = componentToIdString(comp); + data["enabled"] = state; + + doCallback("components-update", QVariant(data)); +} + +void JsonCB::handleBonjourChange(const QMap& bRegisters) +{ + QJsonArray data; + for (const auto & session: bRegisters) + { + if (session.port<0) continue; + QJsonObject item; + item["name"] = session.serviceName; + item["type"] = session.registeredType; + item["domain"] = session.replyDomain; + item["host"] = session.hostName; + item["address"]= session.address; + item["port"] = session.port; + data.append(item); + } + + doCallback("sessions-update", QVariant(data)); +} + +void JsonCB::handlePriorityUpdate() +{ + QJsonObject data; + QJsonArray priorities; + uint64_t now = QDateTime::currentMSecsSinceEpoch(); + QList activePriorities = _prioMuxer->getPriorities(); + activePriorities.removeAll(255); + int currentPriority = _prioMuxer->getCurrentPriority(); + + foreach (int priority, activePriorities) { + const Hyperion::InputInfo priorityInfo = _prioMuxer->getInputInfo(priority); + QJsonObject item; + item["priority"] = priority; + if (int(priorityInfo.timeoutTime_ms - now) > -1 ) + { + item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); + } + // owner has optional informations to the component + if(!priorityInfo.owner.isEmpty()) + item["owner"] = priorityInfo.owner; + + item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); + item["origin"] = priorityInfo.origin; + item["active"] = (priorityInfo.timeoutTime_ms >= -1); + item["visible"] = (priority == currentPriority); + + if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty()) + { + QJsonObject LEDcolor; + + // add RGB Value to Array + QJsonArray RGBValue; + RGBValue.append(priorityInfo.ledColors.begin()->red); + RGBValue.append(priorityInfo.ledColors.begin()->green); + RGBValue.append(priorityInfo.ledColors.begin()->blue); + LEDcolor.insert("RGB", RGBValue); + + uint16_t Hue; + float Saturation, Luminace; + + // add HSL Value to Array + QJsonArray HSLValue; + ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red, + priorityInfo.ledColors.begin()->green, + priorityInfo.ledColors.begin()->blue, + Hue, Saturation, Luminace); + + HSLValue.append(Hue); + HSLValue.append(Saturation); + HSLValue.append(Luminace); + LEDcolor.insert("HSL", HSLValue); + + item["value"] = LEDcolor; + } + priorities.append(item); + } + + data["priorities"] = priorities; + data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); + + doCallback("priorities-update", QVariant(data)); +} + +void JsonCB::handleImageToLedsMappingChange(const int& mappingType) +{ + QJsonObject data; + data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType); + + doCallback("imageToLedMapping-update", QVariant(data)); +} + +void JsonCB::handleAdjustmentChange() +{ + QJsonArray adjustmentArray; + for (const QString& adjustmentId : _hyperion->getAdjustmentIds()) + { + const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId); + if (colorAdjustment == nullptr) + { + continue; + } + + QJsonObject adjustment; + adjustment["id"] = adjustmentId; + + QJsonArray whiteAdjust; + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG()); + whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB()); + adjustment.insert("white", whiteAdjust); + + QJsonArray redAdjust; + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG()); + redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB()); + adjustment.insert("red", redAdjust); + + QJsonArray greenAdjust; + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG()); + greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB()); + adjustment.insert("green", greenAdjust); + + QJsonArray blueAdjust; + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG()); + blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB()); + adjustment.insert("blue", blueAdjust); + + QJsonArray cyanAdjust; + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG()); + cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB()); + adjustment.insert("cyan", cyanAdjust); + + QJsonArray magentaAdjust; + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG()); + magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB()); + adjustment.insert("magenta", magentaAdjust); + + QJsonArray yellowAdjust; + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG()); + yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB()); + adjustment.insert("yellow", yellowAdjust); + + adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold(); + adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored(); + adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness(); + adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation(); + adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR(); + adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG(); + adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB(); + + adjustmentArray.append(adjustment); + } + + doCallback("adjustment-update", QVariant(adjustmentArray)); +} + +void JsonCB::handleVideoModeChange(const VideoMode& mode) +{ + QJsonObject data; + data["videomode"] = QString(videoMode2String(mode)); + doCallback("videomode-update", QVariant(data)); +} + +void JsonCB::handleEffectListChange() +{ + QJsonArray effectList; + QJsonObject effects; + const std::list & effectsDefinitions = _hyperion->getEffects(); + for (const EffectDefinition & effectDefinition : effectsDefinitions) + { + QJsonObject effect; + effect["name"] = effectDefinition.name; + effect["file"] = effectDefinition.file; + effect["script"] = effectDefinition.script; + effect["args"] = effectDefinition.args; + effectList.append(effect); + }; + effects["effects"] = effectList; + doCallback("effects-update", QVariant(effects)); +} + +void JsonCB::handleSettingsChange(const settings::type& type, const QJsonDocument& data) +{ + QJsonObject dat; + if(data.isObject()) + dat[typeToString(type)] = data.object(); + else + dat[typeToString(type)] = data.array(); + + doCallback("settings-update", QVariant(dat)); +} diff --git a/libsrc/blackborder/BlackBorderProcessor.cpp b/libsrc/blackborder/BlackBorderProcessor.cpp index 608c970a..a566df17 100644 --- a/libsrc/blackborder/BlackBorderProcessor.cpp +++ b/libsrc/blackborder/BlackBorderProcessor.cpp @@ -50,12 +50,16 @@ void BlackBorderProcessor::handleSettingsUpdate(const settings::type& type, cons _maxInconsistentCnt = obj["maxInconsistentCnt"].toInt(10); _blurRemoveCnt = obj["blurRemoveCnt"].toInt(1); _detectionMode = obj["mode"].toString("default"); + const double newThreshold = obj["threshold"].toDouble(5.0)/100.0; - if(_oldThreshold != obj["threshold"].toDouble(5.0/100)) + if(_oldThreshold != newThreshold) { - _oldThreshold = obj["threshold"].toDouble(5.0/100); - if(_detector != nullptr) delete _detector; - _detector = new BlackBorderDetector(obj["threshold"].toDouble(5.0/100)); + _oldThreshold = newThreshold; + + if(_detector != nullptr) + delete _detector; + + _detector = new BlackBorderDetector(newThreshold); } Debug(Logger::getInstance("BLACKBORDER"), "Set mode to: %s", QSTRING_CSTR(_detectionMode)); diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index 9997517b..92828df2 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -15,23 +15,22 @@ #include // hyperion util includes -//#include "hyperion/ImageProcessorFactory.h" -//#include "hyperion/ImageProcessor.h" -#include "utils/ColorRgb.h" +#include #include "HyperionConfig.h" +#include // project includes #include "BoblightClientConnection.h" -BoblightClientConnection::BoblightClientConnection(QTcpSocket *socket, const int priority) +BoblightClientConnection::BoblightClientConnection(Hyperion* hyperion, QTcpSocket *socket, const int priority) : QObject() , _locale(QLocale::C) , _socket(socket) - //, _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) - , _hyperion(Hyperion::getInstance()) + , _imageProcessor(hyperion->getImageProcessor()) + , _hyperion(hyperion) , _receiveBuffer() , _priority(priority) - , _ledColors(Hyperion::getInstance()->getLedCount(), ColorRgb::BLACK) + , _ledColors(hyperion->getLedCount(), ColorRgb::BLACK) , _log(Logger::getInstance("BOBLIGHT")) , _clientAddress(QHostInfo::fromName(socket->peerAddress().toString()).hostName()) { @@ -227,11 +226,11 @@ void BoblightClientConnection::sendLightMessage() int n = snprintf(buffer, sizeof(buffer), "lights %d\n", _hyperion->getLedCount()); sendMessage(QByteArray(buffer, n)); - //double h0, h1, v0, v1; + double h0, h1, v0, v1; for (unsigned i = 0; i < _hyperion->getLedCount(); ++i) { - //_imageProcessor->getScanParameters(i, h0, h1, v0, v1); - //n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); - //sendMessage(QByteArray(buffer, n)); + _imageProcessor->getScanParameters(i, h0, h1, v0, v1); + n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); + sendMessage(QByteArray(buffer, n)); } } diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h index 76eb1280..f4e4d00e 100644 --- a/libsrc/boblightserver/BoblightClientConnection.h +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -5,9 +5,12 @@ #include #include -// Hyperion includes -#include +// utils includes #include +#include + +class ImageProcessor; +class Hyperion; /// /// The Connection object created by \a BoblightServer when a new connection is establshed @@ -22,7 +25,7 @@ public: /// @param socket The Socket object for this connection /// @param hyperion The Hyperion server /// - BoblightClientConnection(QTcpSocket * socket, const int priority); + BoblightClientConnection(Hyperion* hyperion, QTcpSocket * socket, const int priority); /// /// Destructor @@ -74,6 +77,9 @@ private: /// The TCP-Socket that is connected tot the boblight-client QTcpSocket * _socket; + /// The processor for translating images to led-values + ImageProcessor * _imageProcessor; + /// Link to Hyperion for writing led-values to a priority channel Hyperion * _hyperion; diff --git a/libsrc/boblightserver/BoblightServer.cpp b/libsrc/boblightserver/BoblightServer.cpp index bba2c493..21e0da35 100644 --- a/libsrc/boblightserver/BoblightServer.cpp +++ b/libsrc/boblightserver/BoblightServer.cpp @@ -12,9 +12,9 @@ using namespace hyperion; -BoblightServer::BoblightServer(const QJsonDocument& config) +BoblightServer::BoblightServer(Hyperion* hyperion,const QJsonDocument& config) : QObject() - , _hyperion(Hyperion::getInstance()) + , _hyperion(hyperion) , _server(new QTcpServer(this)) , _openConnections() , _priority(0) @@ -96,7 +96,7 @@ void BoblightServer::newConnection() { Info(_log, "new connection"); _hyperion->registerInput(_priority, hyperion::COMP_BOBLIGHTSERVER, QString("Boblight@%1").arg(socket->peerAddress().toString())); - BoblightClientConnection * connection = new BoblightClientConnection(socket, _priority); + BoblightClientConnection * connection = new BoblightClientConnection(_hyperion, socket, _priority); _openConnections.insert(connection); // register slot for cleaning up after the connection closed diff --git a/libsrc/bonjour/bonjourserviceregister.cpp b/libsrc/bonjour/bonjourserviceregister.cpp index d3124b6f..d0a4609f 100755 --- a/libsrc/bonjour/bonjourserviceregister.cpp +++ b/libsrc/bonjour/bonjourserviceregister.cpp @@ -34,8 +34,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include - +#include BonjourServiceRegister::BonjourServiceRegister(QObject *parent) : QObject(parent), dnssref(0), bonjourSocket(0) @@ -54,10 +53,11 @@ BonjourServiceRegister::~BonjourServiceRegister() void BonjourServiceRegister::registerService(const QString& service, const int& port) { + _port = port; // zeroconf $configname@$hostname:port - QString prettyName = Hyperion::getInstance()->getQJsonConfig()["general"].toObject()["name"].toString(); + // TODO add name of the main instance registerService( - BonjourRecord(prettyName+"@"+QHostInfo::localHostName()+ ":" + QString::number(port), + BonjourRecord(QHostInfo::localHostName()+ ":" + QString::number(port), service, QString() ), diff --git a/libsrc/bonjour/bonjourserviceresolver.cpp b/libsrc/bonjour/bonjourserviceresolver.cpp index 217ae00f..aa1a932f 100644 --- a/libsrc/bonjour/bonjourserviceresolver.cpp +++ b/libsrc/bonjour/bonjourserviceresolver.cpp @@ -117,6 +117,6 @@ void BonjourServiceResolver::bonjourResolveReply(DNSServiceRef sdRef, DNSService void BonjourServiceResolver::finishConnect(const QHostInfo &hostInfo) { - emit bonjourRecordResolved(hostInfo, bonjourPort); + emit bonjourRecordResolved(hostInfo, bonjourPort); QMetaObject::invokeMethod(this, "cleanupResolve", Qt::QueuedConnection); } diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt index aac420f7..5fb38e1a 100644 --- a/libsrc/effectengine/CMakeLists.txt +++ b/libsrc/effectengine/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(PythonLibs 3.4 REQUIRED) +find_package(PythonLibs 3.5 REQUIRED) # Include the python directory. Also include the parent (which is for example /usr/include) # which may be required when it is not includes by the (cross-) compiler by default. diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index 7e1c52c3..b2d0b845 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -81,7 +81,7 @@ void Effect::run() PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _hyperion->getLedCount())); // add minimumWriteTime variable to the interpreter - PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", Hyperion::getInstance()->getLatchTime())); + PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", _hyperion->getLatchTime())); // add a args variable to the interpreter PyObject_SetAttrString(module, "args", EffectModule::json2python(_args)); diff --git a/libsrc/flatbufserver/FlatBufferClient.cpp b/libsrc/flatbufserver/FlatBufferClient.cpp index 335bf829..e1c4b36c 100644 --- a/libsrc/flatbufserver/FlatBufferClient.cpp +++ b/libsrc/flatbufserver/FlatBufferClient.cpp @@ -29,7 +29,6 @@ FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObje void FlatBufferClient::readyRead() { - qDebug()<<"readyRead"; _timeoutTimer->start(); _receiveBuffer += _socket->readAll(); @@ -37,7 +36,6 @@ void FlatBufferClient::readyRead() // check if we can read a header while(_receiveBuffer.size() >= 4) { - uint32_t messageSize = ((_receiveBuffer[0]<<24) & 0xFF000000) | ((_receiveBuffer[1]<<16) & 0x00FF0000) | @@ -47,10 +45,11 @@ void FlatBufferClient::readyRead() // check if we can read a complete message if((uint32_t) _receiveBuffer.size() < messageSize + 4) return; - // remove header + msg from buffer - const QByteArray& msg = _receiveBuffer.remove(0, messageSize + 4); + // extract message only and remove header + msg from buffer :: QByteArray::remove() does not return the removed data + const QByteArray msg = _receiveBuffer.right(messageSize); + _receiveBuffer.remove(0, messageSize + 4); - const uint8_t* msgData = reinterpret_cast(msg.mid(3, messageSize).constData()); + const uint8_t* msgData = reinterpret_cast(msg.constData()); flatbuffers::Verifier verifier(msgData, messageSize); if (flatbuf::VerifyHyperionRequestBuffer(verifier)) @@ -59,7 +58,6 @@ void FlatBufferClient::readyRead() handleMessage(message); continue; } - qDebug()<<"Unable to pasrse msg"; sendErrorReply("Unable to parse message"); } //emit newMessage(msgData,messageSize); diff --git a/libsrc/flatbufserver/FlatBufferConnection.cpp b/libsrc/flatbufserver/FlatBufferConnection.cpp index 064b8d56..c758c6d5 100644 --- a/libsrc/flatbufserver/FlatBufferConnection.cpp +++ b/libsrc/flatbufserver/FlatBufferConnection.cpp @@ -7,16 +7,15 @@ // protoserver includes #include -FlatBufferConnection::FlatBufferConnection(const QString & address) : - _socket(), - _skipReply(false), - _prevSocketState(QAbstractSocket::UnconnectedState), - _log(Logger::getInstance("FLATBUFCONNECTION")) - { +FlatBufferConnection::FlatBufferConnection(const QString & address) + : _socket() + , _prevSocketState(QAbstractSocket::UnconnectedState) + , _log(Logger::getInstance("FLATBUFCONNECTION")) +{ QStringList parts = address.split(":"); if (parts.size() != 2) { - throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong address: Unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Unable to parse address (%1)").arg(address).toStdString()); } _host = parts[0]; @@ -24,19 +23,17 @@ FlatBufferConnection::FlatBufferConnection(const QString & address) : _port = parts[1].toUShort(&ok); if (!ok) { - throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong port: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Unable to parse the port (%1)").arg(parts[1]).toStdString()); } - // try to connect to host + // init connect Info(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port); connectToHost(); // start the connection timer _timer.setInterval(5000); - _timer.setSingleShot(false); - connect(&_timer,SIGNAL(timeout()), this, SLOT(connectToHost())); - connect(&_socket, SIGNAL(readyRead()), this, SLOT(readData())); + connect(&_timer, &QTimer::timeout, this, &FlatBufferConnection::connectToHost); _timer.start(); } @@ -48,48 +45,43 @@ FlatBufferConnection::~FlatBufferConnection() void FlatBufferConnection::readData() { - qint64 bytesAvail; - while((bytesAvail = _socket.bytesAvailable())) + _receiveBuffer += _socket.readAll(); + + // check if we can read a header + while(_receiveBuffer.size() >= 4) { - // ignore until we get 4 bytes. - if (bytesAvail < 4) { + uint32_t messageSize = + ((_receiveBuffer[0]<<24) & 0xFF000000) | + ((_receiveBuffer[1]<<16) & 0x00FF0000) | + ((_receiveBuffer[2]<< 8) & 0x0000FF00) | + ((_receiveBuffer[3] ) & 0x000000FF); + + // check if we can read a complete message + if((uint32_t) _receiveBuffer.size() < messageSize + 4) return; + + // extract message only and remove header + msg from buffer :: QByteArray::remove() does not return the removed data + const QByteArray msg = _receiveBuffer.right(messageSize); + _receiveBuffer.remove(0, messageSize + 4); + + const uint8_t* msgData = reinterpret_cast(msg.constData()); + flatbuffers::Verifier verifier(msgData, messageSize); + + if (flatbuf::VerifyHyperionReplyBuffer(verifier)) + { + auto message = flatbuf::GetHyperionReply(msgData); + parseReply(message); continue; } - - char sizeBuf[4]; - _socket.read(sizeBuf, sizeof(sizeBuf)); - - uint32_t messageSize = - ((sizeBuf[0]<<24) & 0xFF000000) | - ((sizeBuf[1]<<16) & 0x00FF0000) | - ((sizeBuf[2]<< 8) & 0x0000FF00) | - ((sizeBuf[3] ) & 0x000000FF); - - QByteArray buffer; - while((uint32_t)buffer.size() < messageSize) - { - _socket.waitForReadyRead(); - buffer.append(_socket.read(messageSize - buffer.size())); - } - - const uint8_t* replyData = reinterpret_cast(buffer.constData()); - flatbuffers::Verifier verifier(replyData, messageSize); - - if (!flatbuf::VerifyHyperionReplyBuffer(verifier)) - { - Error(_log, "Error while reading data from host"); - return; - } - - auto reply = flatbuf::GetHyperionReply(replyData); - - parseReply(reply); + Error(_log, "Unable to parse reply"); } } -void FlatBufferConnection::setSkipReply(bool skip) +void FlatBufferConnection::setSkipReply(const bool& skip) { - _skipReply = skip; + if(skip) + disconnect(&_socket, &QTcpSocket::readyRead, 0, 0); + else + connect(&_socket, &QTcpSocket::readyRead, this, &FlatBufferConnection::readData, Qt::UniqueConnection); } void FlatBufferConnection::setColor(const ColorRgb & color, int priority, int duration) @@ -191,24 +183,22 @@ bool FlatBufferConnection::parseReply(const flatbuf::HyperionReply *reply) { case flatbuf::Type_REPLY: { - if (!_skipReply) + if (!reply->success()) { - if (!reply->success()) + if (flatbuffers::IsFieldPresent(reply, flatbuf::HyperionReply::VT_ERROR)) { - if (flatbuffers::IsFieldPresent(reply, flatbuf::HyperionReply::VT_ERROR)) - { - throw std::runtime_error("PROTOCONNECTION ERROR: " + reply->error()->str()); - } - else - { - throw std::runtime_error("PROTOCONNECTION ERROR: No error info"); - } + throw std::runtime_error("PROTOCONNECTION ERROR: " + reply->error()->str()); } else { - success = true; + throw std::runtime_error("PROTOCONNECTION ERROR: No error info"); } } + else + { + success = true; + } + break; } case flatbuf::Type_VIDEO: diff --git a/libsrc/flatbufserver/FlatBufferServer.cpp b/libsrc/flatbufserver/FlatBufferServer.cpp index ffef50f1..95694729 100644 --- a/libsrc/flatbufserver/FlatBufferServer.cpp +++ b/libsrc/flatbufserver/FlatBufferServer.cpp @@ -6,10 +6,6 @@ #include #include - -#include - - FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent) : QObject(parent) , _server(new QTcpServer(this)) @@ -28,7 +24,6 @@ FlatBufferServer::~FlatBufferServer() void FlatBufferServer::initServer() { - qDebug()<<"Thread in InitServer is"<thread(); connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection); // apply config @@ -37,7 +32,6 @@ void FlatBufferServer::initServer() void FlatBufferServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) { - qDebug()<<"Thread in handleSettingsUpdate is"<thread(); if(type == settings::FLATBUFSERVER) { const QJsonObject& obj = config.object(); @@ -60,7 +54,6 @@ void FlatBufferServer::handleSettingsUpdate(const settings::type& type, const QJ void FlatBufferServer::newConnection() { - qDebug()<<"Thread in newConnection is"<thread(); while(_server->hasPendingConnections()) { if(QTcpSocket* socket = _server->nextPendingConnection()) diff --git a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp index c1222ef7..54bcbf52 100644 --- a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp +++ b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp @@ -30,12 +30,19 @@ DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned int result = vc_dispmanx_display_get_info(_vc_display, &vc_info); // Keep compiler happy in 'release' mode (void)result; - assert(result == 0); - Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); - // Close the displaye + // Close the display vc_dispmanx_display_close(_vc_display); + if(result != 0) + { + Error(_log, "Failed to open display! Probably no permissions to access the capture interface"); + setEnabled(false); + return; + } + else + Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height); + // init the resource and capture rectangle setWidthHeight(width, height); } @@ -55,11 +62,12 @@ void DispmanxFrameGrabber::freeResources() vc_dispmanx_resource_delete(_vc_resource); } -void DispmanxFrameGrabber::setWidthHeight(int width, int height) +bool DispmanxFrameGrabber::setWidthHeight(int width, int height) { - if(_width != width || _height != height) + if(Grabber::setWidthHeight(width, height)) { - freeResources(); + if(_vc_resource != 0) + vc_dispmanx_resource_delete(_vc_resource); // Create the resources for capturing image uint32_t vc_nativeImageHandle; _vc_resource = vc_dispmanx_resource_create( @@ -71,7 +79,9 @@ void DispmanxFrameGrabber::setWidthHeight(int width, int height) // Define the capture rectangle with the same size vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); + return true; } + return false; } void DispmanxFrameGrabber::setFlags(const int vc_flags) diff --git a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp index 8a12ed27..0acf81d8 100644 --- a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp +++ b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp @@ -4,7 +4,7 @@ DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHe : GrabberWrapper("Dispmanx", &_grabber, grabWidth, grabHeight, updateRate_Hz) , _grabber(grabWidth, grabHeight) { - setImageProcessorEnabled(false); + } void DispmanxWrapper::action() diff --git a/libsrc/grabber/osx/OsxFrameGrabber.cpp b/libsrc/grabber/osx/OsxFrameGrabber.cpp index 37b384ef..7306b40a 100755 --- a/libsrc/grabber/osx/OsxFrameGrabber.cpp +++ b/libsrc/grabber/osx/OsxFrameGrabber.cpp @@ -82,7 +82,14 @@ void OsxFrameGrabber::setDisplayIndex(int index) } image = CGDisplayCreateImage(_display); - assert(image != NULL); + if(image == NULL) + { + Error(_log, "Failed to open main display, disable capture interface"); + setEnabled(false); + return; + } + else + setEnabled(true); Info(_log, "Display opened with resolution: %dx%d@%dbit", CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerPixel(image)); diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 64f4d418..b9b1c31e 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -18,29 +18,29 @@ #include #include +#include #include "grabber/V4L2Grabber.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) V4L2Grabber::V4L2Grabber(const QString & device - , int input , VideoStandard videoStandard , PixelFormat pixelFormat , int pixelDecimation ) : Grabber("V4L2:"+device) - , _deviceName(device) - , _input(input) + , _deviceName() + , _input(-1) , _videoStandard(videoStandard) , _ioMethod(IO_METHOD_MMAP) , _fileDescriptor(-1) , _buffers() , _pixelFormat(pixelFormat) - , _pixelDecimation(pixelDecimation) + , _pixelDecimation(-1) , _lineLength(-1) , _frameByteSize(-1) - , _noSignalCounterThreshold(50) + , _noSignalCounterThreshold(40) , _noSignalThresholdColor(ColorRgb{0,0,0}) , _signalDetectionEnabled(true) , _noSignalDetected(false) @@ -52,12 +52,17 @@ V4L2Grabber::V4L2Grabber(const QString & device , _streamNotifier(nullptr) , _initialized(false) , _deviceAutoDiscoverEnabled(false) - + , _readFrameAdaptTimer(new QTimer(this)) { - //_imageResampler.setHorizontalPixelDecimation(pixelDecimation); - //_imageResampler.setVerticalPixelDecimation(pixelDecimation); + // setup stream notify locker with 10hz + connect(_readFrameAdaptTimer, &QTimer::timeout, this, &V4L2Grabber::unlockReadFrame); + _readFrameAdaptTimer->setInterval(100); + setPixelDecimation(pixelDecimation); getV4Ldevices(); + + // init + setDeviceVideoStandard(device, videoStandard); } V4L2Grabber::~V4L2Grabber() @@ -67,10 +72,12 @@ V4L2Grabber::~V4L2Grabber() void V4L2Grabber::uninit() { - Debug(_log,"uninit grabber: %s", QSTRING_CSTR(_deviceName)); // stop if the grabber was not stopped if (_initialized) { + Debug(_log,"uninit grabber: %s", QSTRING_CSTR(_deviceName)); + + _readFrameAdaptTimer->stop(); stop(); uninit_device(); close_device(); @@ -78,7 +85,6 @@ void V4L2Grabber::uninit() } } - bool V4L2Grabber::init() { if (! _initialized) @@ -133,10 +139,15 @@ bool V4L2Grabber::init() bool opened = false; try { - open_device(); - opened = true; - init_device(_videoStandard, _input); - _initialized = true; + // do not init with unknown device + if(_deviceName != "unknown") + { + open_device(); + opened = true; + init_device(_videoStandard, _input); + _initialized = true; + _readFrameAdaptTimer->start(); + } } catch(std::exception& e) { @@ -529,13 +540,13 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) break; } +// TODO Does never accept own sizes? use always _imageResampler instead +/* + // calc the size based on pixelDecimation fmt.fmt.pix.width = fmt.fmt.pix.width / _pixelDecimation; fmt.fmt.pix.height = fmt.fmt.pix.height / _pixelDecimation; - // set the line length - _lineLength = fmt.fmt.pix.bytesperline; - // set the settings if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) { @@ -550,6 +561,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input) throw_errno_exception("VIDIOC_G_FMT"); return; } +*/ + // set the line length + _lineLength = fmt.fmt.pix.bytesperline; // store width & height _width = fmt.fmt.pix.width; @@ -701,6 +715,10 @@ void V4L2Grabber::stop_capturing() int V4L2Grabber::read_frame() { + // read_frame() is called with 25Hz, adapt to 10Hz. In the end it's up to the stream notifier if we get calls or not + if(!_readFrame) return -1; + _readFrame = false; + bool rc = false; try @@ -933,18 +951,30 @@ void V4L2Grabber::setPixelDecimation(int pixelDecimation) { if(_pixelDecimation != pixelDecimation) { + _pixelDecimation = pixelDecimation; uninit(); - init(); + // start if init is a success + if(init()) + start(); + _imageResampler.setHorizontalPixelDecimation(pixelDecimation); + _imageResampler.setVerticalPixelDecimation(pixelDecimation); } } -void V4L2Grabber::setInputVideoStandard(int input, VideoStandard videoStandard) +void V4L2Grabber::setDeviceVideoStandard(QString device, VideoStandard videoStandard) { - if(_input != input || _videoStandard != videoStandard) + if(_deviceName != device || _videoStandard != videoStandard) { - _input = input; - _videoStandard = videoStandard; + // extract input of device + QChar input = device.at(device.size() - 1); + _input = input.isNumber() ? input.digitValue() : -1; + uninit(); - init(); + _deviceName = device; + _videoStandard = videoStandard; + + // start if init is a success + if(init()) + start(); } } diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index 809b1e09..f6b161f5 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -6,13 +6,11 @@ #include V4L2Wrapper::V4L2Wrapper(const QString &device, - int input, VideoStandard videoStandard, PixelFormat pixelFormat, int pixelDecimation ) : GrabberWrapper("V4L2:"+device, &_grabber, 0, 0, 10) , _grabber(device, - input, videoStandard, pixelFormat, pixelDecimation) @@ -66,7 +64,7 @@ void V4L2Wrapper::readError(const char* err) void V4L2Wrapper::action() { - + // dummy as v4l get notifications from stream } void V4L2Wrapper::setSignalDetectionEnable(bool enable) diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp index f4bc62d0..4a3e3754 100755 --- a/libsrc/grabber/x11/X11Grabber.cpp +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -110,6 +110,7 @@ bool X11Grabber::Setup() bool result = (updateScreenDimensions(true) >=0); ErrorIf(!result, _log, "X11 Grabber start failed"); + setEnabled(result); return result; } @@ -278,11 +279,6 @@ void X11Grabber::setVideoMode(VideoMode mode) updateScreenDimensions(true); } -void X11Grabber::setWidthHeight(int width, int height) -{ - // empty overwrite -} - void X11Grabber::setPixelDecimation(int pixelDecimation) { if(_pixelDecimation != pixelDecimation) diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 428d7738..501f4501 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -17,6 +17,7 @@ target_link_libraries(hyperion hyperion-utils leddevice bonjour + boblightserver effectengine ${QT_LIBRARIES} ) diff --git a/libsrc/hyperion/CaptureCont.cpp b/libsrc/hyperion/CaptureCont.cpp index cc9a2a1f..f50ea338 100644 --- a/libsrc/hyperion/CaptureCont.cpp +++ b/libsrc/hyperion/CaptureCont.cpp @@ -1,12 +1,14 @@ #include #include +#include CaptureCont::CaptureCont(Hyperion* hyperion) : QObject() , _hyperion(hyperion) , _systemCaptEnabled(false) , _v4lCaptEnabled(false) + , _v4lInactiveTimer(new QTimer(this)) { // settings changes connect(_hyperion, &Hyperion::settingsChanged, this, &CaptureCont::handleSettingsUpdate); @@ -14,6 +16,11 @@ CaptureCont::CaptureCont(Hyperion* hyperion) // comp changes connect(_hyperion, &Hyperion::componentStateChanged, this, &CaptureCont::componentStateChanged); + // inactive timer v4l + connect(_v4lInactiveTimer, &QTimer::timeout, this, &CaptureCont::setV4lInactive); + _v4lInactiveTimer->setSingleShot(true); + _v4lInactiveTimer->setInterval(1000); + // init handleSettingsUpdate(settings::INSTCAPTURE, _hyperion->getSetting(settings::INSTCAPTURE)); } @@ -25,6 +32,7 @@ CaptureCont::~CaptureCont() void CaptureCont::handleV4lImage(const Image & image) { + _v4lInactiveTimer->start(); _hyperion->setInputImage(_v4lCaptPrio, image); } @@ -40,7 +48,7 @@ void CaptureCont::setSystemCaptureEnable(const bool& enable) { if(enable) { - _hyperion->registerInput(_systemCaptPrio, hyperion::COMP_GRABBER, "System", "DoNotKnow"); + _hyperion->registerInput(_systemCaptPrio, hyperion::COMP_GRABBER); connect(_hyperion, &Hyperion::systemImage, this, &CaptureCont::handleSystemImage); } else @@ -59,13 +67,14 @@ void CaptureCont::setV4LCaptureEnable(const bool& enable) { if(enable) { - _hyperion->registerInput(_v4lCaptPrio, hyperion::COMP_V4L, "System", "DoNotKnow"); + _hyperion->registerInput(_v4lCaptPrio, hyperion::COMP_V4L); connect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); } else { disconnect(_hyperion, &Hyperion::v4lImage, this, &CaptureCont::handleV4lImage); _hyperion->clear(_v4lCaptPrio); + _v4lInactiveTimer->stop(); } _v4lCaptEnabled = enable; _hyperion->getComponentRegister().componentStateChanged(hyperion::COMP_V4L, enable); @@ -104,3 +113,8 @@ void CaptureCont::componentStateChanged(const hyperion::Components component, bo setV4LCaptureEnable(enable); } } + +void CaptureCont::setV4lInactive() +{ + _hyperion->setInputInactive(_v4lCaptPrio); +} diff --git a/libsrc/hyperion/ComponentRegister.cpp b/libsrc/hyperion/ComponentRegister.cpp index baa573d7..4a686aeb 100644 --- a/libsrc/hyperion/ComponentRegister.cpp +++ b/libsrc/hyperion/ComponentRegister.cpp @@ -55,9 +55,9 @@ bool ComponentRegister::setHyperionEnable(const bool& state) return false; } -bool ComponentRegister::isComponentEnabled(const hyperion::Components& comp) const +int ComponentRegister::isComponentEnabled(const hyperion::Components& comp) const { - return _componentStates.at(comp); + return (_componentStates.count(comp)) ? _componentStates.at(comp) : -1; } void ComponentRegister::componentStateChanged(const hyperion::Components comp, const bool activated) diff --git a/libsrc/hyperion/Grabber.cpp b/libsrc/hyperion/Grabber.cpp index 9811f79f..e04a5770 100644 --- a/libsrc/hyperion/Grabber.cpp +++ b/libsrc/hyperion/Grabber.cpp @@ -24,12 +24,13 @@ Grabber::~Grabber() void Grabber::setEnabled(bool enable) { + Info(_log,"Capture interface is now %s", enable ? "enabled" : "disabled"); _enabled = enable; } void Grabber::setVideoMode(VideoMode mode) { - Debug(_log,"setvideomode %d", mode); + Debug(_log,"Set videomode to %d", mode); _videoMode = mode; if ( _useImageResampler ) { @@ -68,18 +69,20 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo } } -void Grabber::setWidthHeight(int width, int height) +bool Grabber::setWidthHeight(int width, int height) { // eval changes with crop - if (width>0 && height>0) + if ( (width>0 && height>0) && (_width != width || _height != height) ) { if (_cropLeft + _cropRight >= width || _cropTop + _cropBottom >= height) { Error(_log, "Rejecting invalid width/height values as it collides with image cropping: width: %d, height: %d", width, height); - return; + return false; } + Debug(_log, "Set new width: %d, height: %d for capture", width, height); _width = width; _height = height; + return true; } - + return false; } diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 08e77fa7..d41bd35e 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -3,19 +3,14 @@ #include #include -//forwarder -#include - // qt #include GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned width, unsigned height, const unsigned updateRate_Hz) : _grabberName(grabberName) - , _hyperion(Hyperion::getInstance()) , _timer(new QTimer(this)) , _updateInterval_ms(1000/updateRate_Hz) , _log(Logger::getInstance(grabberName)) - , _forward(true) , _ggrabber(ggrabber) , _image(0,0) { @@ -24,8 +19,6 @@ GrabberWrapper::GrabberWrapper(QString grabberName, Grabber * ggrabber, unsigned _image.resize(width, height); - _forward = _hyperion->getForwarder()->protoForwardingEnabled(); - connect(_timer, &QTimer::timeout, this, &GrabberWrapper::action); } @@ -105,7 +98,7 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso else obj = config.object(); - if(type == settings::SYSTEMCAPTURE) + if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L")) { // width/height _ggrabber->setWidthHeight(obj["width"].toInt(96), obj["height"].toInt(96)); @@ -138,7 +131,8 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso } } - if(type == settings::V4L2) + // v4l instances only! + if(type == settings::V4L2 && _grabberName.startsWith("V4L")) { // pixel decimation for v4l _ggrabber->setPixelDecimation(obj["sizeDecimation"].toInt(8)); @@ -160,8 +154,8 @@ void GrabberWrapper::handleSettingsUpdate(const settings::type& type, const QJso obj["redSignalThreshold"].toDouble(0.0)/100.0, obj["greenSignalThreshold"].toDouble(0.0)/100.0, obj["blueSignalThreshold"].toDouble(0.0)/100.0); - _ggrabber->setInputVideoStandard( - obj["input"].toInt(0), + _ggrabber->setDeviceVideoStandard( + obj["device"].toString("auto"), parseVideoStandard(obj["standard"].toString("no-change"))); } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 669994a1..12345e72 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -47,6 +47,9 @@ // CaptureControl (Daemon capture) #include +// Boblight +#include + Hyperion* Hyperion::_hyperion = nullptr; Hyperion* Hyperion::initInstance( HyperionDaemon* daemon, const quint8& instance, const QString configFile, const QString rootPath) @@ -122,7 +125,7 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString const QJsonObject color = getSetting(settings::COLOR).object(); // initialize leddevices - const QJsonObject ledDevice = getSetting(settings::DEVICE).object(); + QJsonObject ledDevice = getSetting(settings::DEVICE).object(); ledDevice["currentLedCount"] = int(_hwLedCount); // Inject led count info _device = LedDeviceFactory::construct(ledDevice); @@ -159,6 +162,11 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString // 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(); + + // boblight, can't live in global scope as it depends on layout + + _boblightServer = new BoblightServer(this, getSetting(settings::BOBLSERVER)); + connect(this, &Hyperion::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate); } Hyperion::~Hyperion() @@ -178,6 +186,7 @@ void Hyperion::freeObjects(bool emitCloseSignal) } // delete components on exit of hyperion core + delete _boblightServer; delete _captureCont; delete _effectEngine; //delete _deviceSmooth; @@ -249,7 +258,7 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum else if(type == settings::DEVICE) { _lockUpdate = true; - const QJsonObject dev = config.object(); + QJsonObject dev = config.object(); // handle hwLedCount update _hwLedCount = qMax(unsigned(dev["hardwareLedCount"].toInt(getLedCount())), getLedCount()); @@ -281,7 +290,7 @@ void Hyperion::handleSettingsUpdate(const settings::type& type, const QJsonDocum _deviceSmooth->startTimerDelayed(); _lockUpdate = false; } - // update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer + // update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color update(); } @@ -421,6 +430,11 @@ const bool Hyperion::setInputImage(const int priority, const Image& im return false; } +const 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) { // clear effect if this call does not come from an effect @@ -602,7 +616,7 @@ void Hyperion::update() // disable the black border detector for effects and ledmapping to 0 if(compChanged) { - _imageProcessor->setBlackbarDetectDisable((_prevCompId == hyperion::COMP_EFFECT || _prevCompId == hyperion::COMP_GRABBER)); + _imageProcessor->setBlackbarDetectDisable((_prevCompId == hyperion::COMP_EFFECT)); _imageProcessor->setHardLedMappingType((_prevCompId == hyperion::COMP_EFFECT) ? 0 : -1); } _imageProcessor->process(image, _ledBuffer); diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index c243de17..f5e8e5c6 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -228,7 +228,7 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, const bool& force) } _currentConfigId = cfg; //DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %d, interval: %d ms, settlingTime: %d ms, updateDelay: %d frames", _currentConfigId, _updateInterval, _settlingTime, _outputDelay ); - InfoIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); + DebugIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); return true; } diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index 79ed8ed1..40292939 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -240,6 +240,12 @@ const bool PriorityMuxer::setInputImage(const int priority, const Image image; + return setInputImage(priority, image, -100); +} + const bool PriorityMuxer::clearInput(const uint8_t priority) { if (priority < PriorityMuxer::LOWEST_PRIORITY && _activeInputs.remove(priority)) diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index 3eeec8d3..6de6929c 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -133,8 +133,6 @@ SettingsManager::~SettingsManager() const QJsonDocument SettingsManager::getSetting(const settings::type& type) { - //return _sTable->getSettingsRecord(settings::typeToString(type)); - QString key = settings::typeToString(type); if(_qconfig[key].isObject()) return QJsonDocument(_qconfig[key].toObject()); @@ -168,6 +166,23 @@ const bool SettingsManager::saveSettings(QJsonObject config, const bool& correct 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 _qconfig = config; diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index b49d2f44..864a0485 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -55,6 +55,10 @@ { "$ref": "schema-protoServer.json" }, + "flatbufServer": + { + "$ref": "schema-flatbufServer.json" + }, "boblightServer" : { "$ref": "schema-boblightServer.json" diff --git a/libsrc/hyperion/resource.qrc b/libsrc/hyperion/resource.qrc index 717d548a..ad3a1993 100644 --- a/libsrc/hyperion/resource.qrc +++ b/libsrc/hyperion/resource.qrc @@ -16,6 +16,7 @@ schema/schema-forwarder.json schema/schema-jsonServer.json schema/schema-protoServer.json + schema/schema-flatbufServer.json schema/schema-boblightServer.json schema/schema-udpListener.json schema/schema-webConfig.json diff --git a/libsrc/hyperion/schema/schema-device.json b/libsrc/hyperion/schema/schema-device.json index 53556c79..9785f3d2 100644 --- a/libsrc/hyperion/schema/schema-device.json +++ b/libsrc/hyperion/schema/schema-device.json @@ -2,18 +2,21 @@ "type" : "object", "title" : "edt_dev_general_heading_title", "required" : true, - "defaultProperties": ["ledCount","colorOrder","rewriteTime","minimumWriteTime"], + "defaultProperties": ["hardwareLedCount","colorOrder","rewriteTime"], "properties" : { "type" : { - "type" : "string" + "type" : "string", + "propertyOrder" : 1 }, - "ledCount" : + "hardwareLedCount" : { "type" : "integer", - "minimum" : 0, - "title" : "edt_dev_general_ledCount_title", + "title" : "edt_dev_general_hardwareLedCount_title", + "minimum" : 1, + "default" : 1, + "access" : "expert", "propertyOrder" : 2 }, "colorOrder" : diff --git a/libsrc/hyperion/schema/schema-grabberV4L2.json b/libsrc/hyperion/schema/schema-grabberV4L2.json index bd928614..01df9d8d 100644 --- a/libsrc/hyperion/schema/schema-grabberV4L2.json +++ b/libsrc/hyperion/schema/schema-grabberV4L2.json @@ -3,7 +3,7 @@ "required" : true, "title" : "edt_conf_v4l2_heading_title", "minItems": 1, - "maxItems": 2, + "maxItems": 1, "items": { "type" : "object", @@ -16,17 +16,9 @@ "type" : "string", "title" : "edt_conf_v4l2_device_title", "default" : "auto", + "minLength" : 4, "required" : true, - "propertyOrder" : 2 - }, - "input" : - { - "type" : "integer", - "title" : "edt_conf_v4l2_input_title", - "minimum" : 0, - "default" : 0, - "required" : true, - "propertyOrder" : 3 + "propertyOrder" : 1 }, "standard" : { @@ -38,7 +30,7 @@ "enum_titles" : ["edt_conf_enum_PAL", "edt_conf_enum_NTSC", "edt_conf_enum_SECAM", "edt_conf_enum_NO_CHANGE"] }, "required" : true, - "propertyOrder" : 4 + "propertyOrder" : 2 }, "sizeDecimation" : { @@ -48,7 +40,7 @@ "maximum" : 30, "default" : 6, "required" : true, - "propertyOrder" : 8 + "propertyOrder" : 3 }, "cropLeft" : { @@ -58,7 +50,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 11 + "propertyOrder" : 4 }, "cropRight" : { @@ -68,7 +60,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 12 + "propertyOrder" : 5 }, "cropTop" : { @@ -78,7 +70,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 13 + "propertyOrder" : 6 }, "cropBottom" : { @@ -88,7 +80,7 @@ "default" : 0, "append" : "edt_append_pixel", "required" : true, - "propertyOrder" : 14 + "propertyOrder" : 7 }, "signalDetection" : { @@ -96,7 +88,7 @@ "title" : "edt_conf_v4l2_signalDetection_title", "default" : false, "required" : true, - "propertyOrder" : 15 + "propertyOrder" : 8 }, "redSignalThreshold" : { @@ -112,7 +104,7 @@ } }, "required" : true, - "propertyOrder" : 16 + "propertyOrder" : 9 }, "greenSignalThreshold" : { @@ -128,7 +120,7 @@ } }, "required" : true, - "propertyOrder" : 17 + "propertyOrder" : 10 }, "blueSignalThreshold" : { @@ -144,7 +136,7 @@ } }, "required" : true, - "propertyOrder" : 18 + "propertyOrder" : 11 }, "sDVOffsetMin" : { @@ -160,7 +152,7 @@ } }, "required" : true, - "propertyOrder" : 19 + "propertyOrder" : 12 }, "sDVOffsetMax" : { @@ -176,7 +168,7 @@ } }, "required" : true, - "propertyOrder" : 20 + "propertyOrder" : 13 }, "sDHOffsetMin" : { @@ -192,7 +184,7 @@ } }, "required" : true, - "propertyOrder" : 21 + "propertyOrder" : 14 }, "sDHOffsetMax" : { @@ -208,7 +200,7 @@ } }, "required" : true, - "propertyOrder" : 22 + "propertyOrder" : 15 } }, "additionalProperties" : false diff --git a/libsrc/hyperion/schema/schema-instCapture.json b/libsrc/hyperion/schema/schema-instCapture.json index 2e59df10..608717c1 100644 --- a/libsrc/hyperion/schema/schema-instCapture.json +++ b/libsrc/hyperion/schema/schema-instCapture.json @@ -8,7 +8,7 @@ { "type" : "boolean", "required" : true, - "title" : "edt_conf_instC_systemEnable", + "title" : "edt_conf_instC_systemEnable_title", "default" : true, "propertyOrder" : 1 }, @@ -26,7 +26,7 @@ { "type" : "boolean", "required" : true, - "title" : "edt_conf_instC_v4lEnable", + "title" : "edt_conf_instC_v4lEnable_title", "default" : false, "propertyOrder" : 3 }, diff --git a/libsrc/hyperion/schema/schema-webConfig.json b/libsrc/hyperion/schema/schema-webConfig.json index 6440dc76..ea33a383 100644 --- a/libsrc/hyperion/schema/schema-webConfig.json +++ b/libsrc/hyperion/schema/schema-webConfig.json @@ -3,14 +3,6 @@ "title" : "edt_conf_webc_heading_title", "properties" : { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "default" : true, - "access" : "expert", - "propertyOrder" : 1 - }, "document_root" : { "type" : "string", diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 42deda01..ffea30d7 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -5,11 +5,8 @@ #include #include "JsonClientConnection.h" -// hyperion include -#include -#include +// bonjour include #include -#include // qt includes #include @@ -20,27 +17,16 @@ JsonServer::JsonServer(const QJsonDocument& config) : QObject() , _server(new QTcpServer(this)) - , _hyperion(Hyperion::getInstance()) , _openConnections() , _log(Logger::getInstance("JSONSERVER")) - , _componentRegister( & _hyperion->getComponentRegister()) { Debug(_log, "Created instance"); // Set trigger for incoming connections connect(_server, SIGNAL(newConnection()), this, SLOT(newConnection())); - // receive state of forwarder - connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonServer::componentStateChanged); - - // listen for component register changes - connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); - // init handleSettingsUpdate(settings::JSONSERVER, config); - - // set initial state of forwarding - componentStateChanged(hyperion::COMP_FORWARDER, _componentRegister->isComponentEnabled(hyperion::COMP_FORWARDER)); } JsonServer::~JsonServer() @@ -64,7 +50,13 @@ void JsonServer::start() if(_serviceRegister == nullptr) { - _serviceRegister = new BonjourServiceRegister(); + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-json._tcp", _port); + } + else if( _serviceRegister->getPort() != _port) + { + delete _serviceRegister; + _serviceRegister = new BonjourServiceRegister(this); _serviceRegister->registerService("_hyperiond-json._tcp", _port); } } @@ -123,38 +115,6 @@ void JsonServer::closedConnection(void) connection->deleteLater(); } -void JsonServer::componentStateChanged(const hyperion::Components component, bool enable) -{ - if (component == hyperion::COMP_FORWARDER) - { - if(enable) - { - connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); - } - else - { - disconnect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); - } - } -} - -void JsonServer::forwardJsonMessage(const QJsonObject &message) -{ - QTcpSocket client; - QStringList list = _hyperion->getForwarder()->getJsonSlaves(); - - for (const auto& entry : list) - { - QStringList splitted = entry.split(":"); - client.connectToHost(splitted[0], splitted[1].toInt()); - if ( client.waitForConnected(500) ) - { - sendMessage(message,&client); - client.close(); - } - } -} - void JsonServer::sendMessage(const QJsonObject & message, QTcpSocket * socket) { // serialize message diff --git a/libsrc/protoserver/ProtoClientConnection.cpp b/libsrc/protoserver/ProtoClientConnection.cpp index 2a3b6b22..f3c42c33 100644 --- a/libsrc/protoserver/ProtoClientConnection.cpp +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -16,6 +16,9 @@ // hyperion util includes #include "utils/ColorRgb.h" +// Hyperion includes +#include + // project includes #include "ProtoClientConnection.h" @@ -198,7 +201,7 @@ void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &messag int priority = message.priority(); // clear priority - _hyperion->clear(priority); + //_hyperion->clear(priority); // send reply sendSuccessReply(); } @@ -206,7 +209,7 @@ void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &messag void ProtoClientConnection::handleClearallCommand() { // clear priority - _hyperion->clearall(); + //_hyperion->clearall(); // send reply sendSuccessReply(); diff --git a/libsrc/protoserver/ProtoClientConnection.h b/libsrc/protoserver/ProtoClientConnection.h index f61cfed1..4f8c62ee 100644 --- a/libsrc/protoserver/ProtoClientConnection.h +++ b/libsrc/protoserver/ProtoClientConnection.h @@ -9,9 +9,6 @@ #include #include -// Hyperion includes -#include - //Utils includes #include @@ -19,6 +16,8 @@ #include "message.pb.h" #include "protoserver/ProtoConnection.h" +class Hyperion; + /// /// The Connection object created by a ProtoServer when a new connection is establshed /// diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp index 4504dda0..56e8ec1a 100644 --- a/libsrc/protoserver/ProtoServer.cpp +++ b/libsrc/protoserver/ProtoServer.cpp @@ -3,10 +3,9 @@ // qt incl #include +#include // project includes -#include -#include #include #include "protoserver/ProtoConnection.h" #include "ProtoClientConnection.h" @@ -15,30 +14,13 @@ ProtoServer::ProtoServer(const QJsonDocument& config) : QObject() - , _hyperion(Hyperion::getInstance()) , _server(new QTcpServer(this)) , _openConnections() , _log(Logger::getInstance("PROTOSERVER")) - , _componentRegister( & _hyperion->getComponentRegister()) { Debug(_log,"Instance created"); connect( _server, SIGNAL(newConnection()), this, SLOT(newConnection())); handleSettingsUpdate(settings::PROTOSERVER, config); - - QStringList slaves = _hyperion->getForwarder()->getProtoSlaves(); - - for (const auto& entry : slaves) - { - ProtoConnection* p = new ProtoConnection(entry.toLocal8Bit().constData()); - p->setSkipReply(true); - _proxy_connections << p; - } - - // listen for component changes - connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &ProtoServer::componentStateChanged); - - // get inital forwarder state - componentStateChanged(hyperion::COMP_FORWARDER, _componentRegister->isComponentEnabled(hyperion::COMP_FORWARDER)); } ProtoServer::~ProtoServer() @@ -46,9 +28,6 @@ ProtoServer::~ProtoServer() foreach (ProtoClientConnection * connection, _openConnections) { delete connection; } - - while (!_proxy_connections.isEmpty()) - delete _proxy_connections.takeFirst(); } void ProtoServer::start() @@ -65,7 +44,13 @@ void ProtoServer::start() if(_serviceRegister == nullptr) { - _serviceRegister = new BonjourServiceRegister(); + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-proto._tcp", _port); + } + else if( _serviceRegister->getPort() != _port) + { + delete _serviceRegister; + _serviceRegister = new BonjourServiceRegister(this); _serviceRegister->registerService("_hyperiond-proto._tcp", _port); } } @@ -110,37 +95,11 @@ void ProtoServer::newConnection() // register slot for cleaning up after the connection closed connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); - connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); - - // register forward signal for video mode - connect(this, SIGNAL(videoMode(VideoMode)), connection, SLOT(setVideoMode(VideoMode))); + //connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); } } } -void ProtoServer::newMessage(const proto::HyperionRequest * message) -{ - for (int i = 0; i < _proxy_connections.size(); ++i) - _proxy_connections.at(i)->sendMessage(*message); -} - -void ProtoServer::sendImageToProtoSlaves(int priority, const Image & image, int duration_ms) -{ - if ( _forwarder_enabled ) - { - for (int i = 0; i < _proxy_connections.size(); ++i) - _proxy_connections.at(i)->setImage(image, priority, duration_ms); - } -} - -void ProtoServer::componentStateChanged(const hyperion::Components component, bool enable) -{ - if (component == hyperion::COMP_FORWARDER) - { - _forwarder_enabled = enable; - } -} - void ProtoServer::closedConnection(ProtoClientConnection *connection) { Debug(_log, "Connection closed"); diff --git a/libsrc/python/CMakeLists.txt b/libsrc/python/CMakeLists.txt index 2f363d88..cbe8f641 100644 --- a/libsrc/python/CMakeLists.txt +++ b/libsrc/python/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(PythonLibs 3.4 REQUIRED) +find_package(PythonLibs 3.5 REQUIRED) # Include the python directory. Also include the parent (which is for example /usr/include) # which may be required when it is not includes by the (cross-) compiler by default. diff --git a/libsrc/udplistener/UDPListener.cpp b/libsrc/udplistener/UDPListener.cpp index 3674587b..4888ea52 100644 --- a/libsrc/udplistener/UDPListener.cpp +++ b/libsrc/udplistener/UDPListener.cpp @@ -1,24 +1,21 @@ // project includes #include -// hyperion includes -#include +// bonjour includes #include -// hyperion util includes -#include "utils/ColorRgb.h" +// hyperion includes #include "HyperionConfig.h" // qt includes #include +#include using namespace hyperion; UDPListener::UDPListener(const QJsonDocument& config) : QObject(), - _hyperion(Hyperion::getInstance()), _server(new QUdpSocket(this)), - _openConnections(), _priority(0), _timeout(0), _log(Logger::getInstance("UDPLISTENER")), @@ -26,10 +23,6 @@ UDPListener::UDPListener(const QJsonDocument& config) : _listenPort(0) { Debug(_log, "Instance created"); - // listen for comp changes - connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool))); - // Set trigger for incoming connections - connect(_server, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); // init handleSettingsUpdate(settings::UDPLISTENER, config); @@ -40,7 +33,6 @@ UDPListener::~UDPListener() // clear the current channel stop(); delete _server; - _hyperion->clear(_priority); } @@ -67,12 +59,17 @@ void UDPListener::start() WarningIf( ! joinGroupOK, _log, "Multicast failed"); } _isActive = true; - _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); - if(_bonjourService == nullptr) + if(_serviceRegister == nullptr) { - _bonjourService = new BonjourServiceRegister(); - _bonjourService->registerService("_hyperiond-udp._udp", _listenPort); + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-udp._udp", _listenPort); + } + else if( _serviceRegister->getPort() != _listenPort) + { + delete _serviceRegister; + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-udp._udp", _listenPort); } } } @@ -85,8 +82,7 @@ void UDPListener::stop() _server->close(); _isActive = false; Info(_log, "Stopped"); - _hyperion->clear(_priority); - _hyperion->getComponentRegister().componentStateChanged(COMP_UDPLISTENER, _isActive); + emit clearGlobalPriority(_priority, hyperion::COMP_UDPLISTENER); } void UDPListener::componentStateChanged(const hyperion::Components component, bool enable) @@ -124,20 +120,19 @@ void UDPListener::readPendingDatagrams() void UDPListener::processTheDatagram(const QByteArray * datagram, const QHostAddress * sender) { int packetLedCount = datagram->size()/3; - int hyperionLedCount = Hyperion::getInstance()->getLedCount(); - DebugIf( (packetLedCount != hyperionLedCount), _log, "packetLedCount (%d) != hyperionLedCount (%d)", packetLedCount, hyperionLedCount); + //DebugIf( (packetLedCount != hyperionLedCount), _log, "packetLedCount (%d) != hyperionLedCount (%d)", packetLedCount, hyperionLedCount); - std::vector _ledColors(Hyperion::getInstance()->getLedCount(), ColorRgb::BLACK); + std::vector _ledColors(packetLedCount, ColorRgb::BLACK); - for (int ledIndex=0; ledIndex < qMin(packetLedCount, hyperionLedCount); ledIndex++) { + for (int ledIndex=0; ledIndex < packetLedCount; ledIndex++) { ColorRgb & rgb = _ledColors[ledIndex]; rgb.red = datagram->at(ledIndex*3+0); rgb.green = datagram->at(ledIndex*3+1); rgb.blue = datagram->at(ledIndex*3+2); } // TODO provide a setInput with origin arg to overwrite senders smarter - _hyperion->registerInput(_priority, hyperion::COMP_UDPLISTENER, QString("UDPListener@%1").arg(sender->toString())); - _hyperion->setInput(_priority, _ledColors, _timeout); + emit registerGlobalInput(_priority, hyperion::COMP_UDPLISTENER, QString("UDPListener@%1").arg(sender->toString())); + emit setGlobalInput(_priority, _ledColors, _timeout); } void UDPListener::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config) diff --git a/libsrc/utils/Stats.cpp b/libsrc/utils/Stats.cpp index 039e8361..e31a5594 100644 --- a/libsrc/utils/Stats.cpp +++ b/libsrc/utils/Stats.cpp @@ -13,11 +13,15 @@ #include #include -Stats::Stats() +Stats* Stats::instance = nullptr; + +Stats::Stats(const QJsonObject& config) : QObject() , _log(Logger::getInstance("STATS")) , _hyperion(Hyperion::getInstance()) { + Stats::instance = this; + // generate hash foreach(QNetworkInterface interface, QNetworkInterface::allInterfaces()) { @@ -38,8 +42,31 @@ Stats::Stats() return; } + // prep data + handleDataUpdate(config); + + // QNetworkRequest Header + _req.setRawHeader("Content-Type", "application/json"); + _req.setRawHeader("Authorization", "Basic SHlwZXJpb25YbDQ5MlZrcXA6ZDQxZDhjZDk4ZjAwYjIw"); + + connect(&_mgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(resolveReply(QNetworkReply*))); + + // 7 days interval + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(sendHTTP())); + timer->start(604800000); + + // delay initial check + QTimer::singleShot(60000, this, SLOT(initialExec())); +} + +Stats::~Stats() +{ +} + +void Stats::handleDataUpdate(const QJsonObject& config) +{ // prepare content - QJsonObject config = _hyperion->getQJsonConfig(); SysInfo::HyperionSysInfo data = SysInfo::get(); QJsonObject system; @@ -63,25 +90,6 @@ Stats::Stats() QJsonDocument doc(system); _ba = doc.toJson(); - - // QNetworkRequest Header - _req.setRawHeader("Content-Type", "application/json"); - _req.setRawHeader("Authorization", "Basic SHlwZXJpb25YbDQ5MlZrcXA6ZDQxZDhjZDk4ZjAwYjIw"); - - connect(&_mgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(resolveReply(QNetworkReply*))); - - // 7 days interval - QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(sendHTTP())); - timer->start(604800000); - - //delay initial check - QTimer::singleShot(60000, this, SLOT(initialExec())); -} - -Stats::~Stats() -{ - } void Stats::initialExec() diff --git a/libsrc/webserver/CgiHandler.cpp b/libsrc/webserver/CgiHandler.cpp index a9ddc107..1824397c 100644 --- a/libsrc/webserver/CgiHandler.cpp +++ b/libsrc/webserver/CgiHandler.cpp @@ -13,11 +13,9 @@ #include #include -CgiHandler::CgiHandler (Hyperion * hyperion, QObject * parent) +CgiHandler::CgiHandler (QObject * parent) : QObject(parent) - , _hyperion(hyperion) , _args(QStringList()) - , _hyperionConfig(_hyperion->getQJsonConfig()) , _baseUrl() , _log(Logger::getInstance("WEBSERVER")) { @@ -57,11 +55,6 @@ void CgiHandler::cmd_cfg_jsonserver() if ( _args.at(0) == "cfg_jsonserver" ) { quint16 jsonPort = 19444; - if (_hyperionConfig.contains("jsonServer")) - { - const QJsonObject jsonConfig = _hyperionConfig["jsonServer"].toObject(); - jsonPort = jsonConfig["port"].toInt(jsonPort); - } // send result as reply _reply->addHeader ("Content-Type", "text/plain" ); diff --git a/libsrc/webserver/CgiHandler.h b/libsrc/webserver/CgiHandler.h index 0be79812..e48d9577 100644 --- a/libsrc/webserver/CgiHandler.h +++ b/libsrc/webserver/CgiHandler.h @@ -5,7 +5,6 @@ #include #include -#include #include #include "QtHttpReply.h" @@ -15,7 +14,7 @@ class CgiHandler : public QObject { Q_OBJECT public: - CgiHandler (Hyperion * hyperion, QObject * parent = NULL); + CgiHandler (QObject * parent = NULL); virtual ~CgiHandler (void); void setBaseUrl(const QString& url); @@ -26,11 +25,9 @@ public: void cmd_runscript (); private: - Hyperion* _hyperion; QtHttpReply * _reply; QtHttpRequest * _request; QStringList _args; - const QJsonObject & _hyperionConfig; QString _baseUrl; Logger * _log; }; diff --git a/libsrc/webserver/QtHttpServer.h b/libsrc/webserver/QtHttpServer.h index a14b191e..50264470 100644 --- a/libsrc/webserver/QtHttpServer.h +++ b/libsrc/webserver/QtHttpServer.h @@ -19,67 +19,69 @@ class QtHttpReply; class QtHttpClientWrapper; class QtHttpServerWrapper : public QTcpServer { - Q_OBJECT + Q_OBJECT public: - explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR); - virtual ~QtHttpServerWrapper (void); + explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR); + virtual ~QtHttpServerWrapper (void); - void setUseSecure (const bool ssl = true); + void setUseSecure (const bool ssl = true); protected: - void incomingConnection (qintptr handle) Q_DECL_OVERRIDE; + void incomingConnection (qintptr handle) Q_DECL_OVERRIDE; private: - bool m_useSsl; + bool m_useSsl; }; class QtHttpServer : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit QtHttpServer (QObject * parent = Q_NULLPTR); + explicit QtHttpServer (QObject * parent = Q_NULLPTR); - static const QString & HTTP_VERSION; + static const QString & HTTP_VERSION; - typedef void (QSslSocket::* SslErrorSignal) (const QList &); + typedef void (QSslSocket::* SslErrorSignal) (const QList &); - const QString & getServerName (void) const; + const QString & getServerName (void) const; - quint16 getServerPort (void) const; - QString getErrorString (void) const; + quint16 getServerPort (void) const; + QString getErrorString (void) const; + +// const bool isListening(void) { return m_sockServer->isListening(); }; public slots: - void start (quint16 port = 0); - void stop (void); - void setServerName (const QString & serverName); - void setUseSecure (const bool ssl = true); - void setPrivateKey (const QSslKey & key); - void setCertificates (const QList & certs); + void start (quint16 port = 0); + void stop (void); + void setServerName (const QString & serverName); + void setUseSecure (const bool ssl = true); + void setPrivateKey (const QSslKey & key); + void setCertificates (const QList & certs); signals: - void started (quint16 port); - void stopped (void); - void error (const QString & msg); - void clientConnected (const QString & guid); - void clientDisconnected (const QString & guid); - void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); + void started (quint16 port); + void stopped (void); + void error (const QString & msg); + void clientConnected (const QString & guid); + void clientDisconnected (const QString & guid); + void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); private slots: - void onClientConnected (void); - void onClientDisconnected (void); - void onClientSslEncrypted (void); - void onClientSslPeerVerifyError (const QSslError & err); - void onClientSslErrors (const QList & errors); - void onClientSslModeChanged (QSslSocket::SslMode mode); + void onClientConnected (void); + void onClientDisconnected (void); + void onClientSslEncrypted (void); + void onClientSslPeerVerifyError (const QSslError & err); + void onClientSslErrors (const QList & errors); + void onClientSslModeChanged (QSslSocket::SslMode mode); private: - bool m_useSsl; - QSslKey m_sslKey; - QList m_sslCerts; - QString m_serverName; - QtHttpServerWrapper * m_sockServer; - QHash m_socksClientsHash; + bool m_useSsl; + QSslKey m_sslKey; + QList m_sslCerts; + QString m_serverName; + QtHttpServerWrapper * m_sockServer; + QHash m_socksClientsHash; }; #endif // QTHTTPSERVER_H diff --git a/libsrc/webserver/StaticFileServing.cpp b/libsrc/webserver/StaticFileServing.cpp index 85004470..bf11cbc5 100644 --- a/libsrc/webserver/StaticFileServing.cpp +++ b/libsrc/webserver/StaticFileServing.cpp @@ -10,11 +10,10 @@ #include #include -StaticFileServing::StaticFileServing (Hyperion *hyperion, QObject * parent) +StaticFileServing::StaticFileServing (QObject * parent) : QObject (parent) - , _hyperion(hyperion) , _baseUrl () - , _cgi(hyperion, this) + , _cgi(this) , _log(Logger::getInstance("WEBSERVER")) { Q_INIT_RESOURCE(WebConfig); diff --git a/libsrc/webserver/StaticFileServing.h b/libsrc/webserver/StaticFileServing.h index 875bad4d..88821cbf 100644 --- a/libsrc/webserver/StaticFileServing.h +++ b/libsrc/webserver/StaticFileServing.h @@ -1,22 +1,23 @@ #ifndef STATICFILESERVING_H #define STATICFILESERVING_H -#include +// locales includes +#include "CgiHandler.h" -//#include "QtHttpServer.h" +// qt includes +#include #include "QtHttpRequest.h" #include "QtHttpReply.h" #include "QtHttpHeader.h" -#include "CgiHandler.h" -#include +//utils includes #include class StaticFileServing : public QObject { Q_OBJECT public: - explicit StaticFileServing (Hyperion *hyperion, QObject * parent = nullptr); + explicit StaticFileServing (QObject * parent = nullptr); virtual ~StaticFileServing (void); void setBaseUrl(const QString& url); @@ -25,7 +26,6 @@ public slots: void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); private: - Hyperion * _hyperion; QString _baseUrl; QMimeDatabase * _mimeDb; CgiHandler _cgi; diff --git a/libsrc/webserver/WebJsonRpc.h b/libsrc/webserver/WebJsonRpc.h index d074dd2f..06adb33b 100644 --- a/libsrc/webserver/WebJsonRpc.h +++ b/libsrc/webserver/WebJsonRpc.h @@ -1,7 +1,11 @@ #pragma once +// utils includes #include +// qt includes +#include + class QtHttpServer; class QtHttpRequest; class QtHttpClientWrapper; diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp index 2298d25d..90be5709 100644 --- a/libsrc/webserver/WebServer.cpp +++ b/libsrc/webserver/WebServer.cpp @@ -1,17 +1,21 @@ #include "webserver/WebServer.h" #include "StaticFileServing.h" -#include "QtHttpServer.h" -// bonjour +// qt includes +#include "QtHttpServer.h" +#include +#include + +// bonjour includes #include #include -#include +// utils includes +#include WebServer::WebServer(const QJsonDocument& config, QObject * parent) : QObject(parent) , _log(Logger::getInstance("WEBSERVER")) - , _hyperion(Hyperion::getInstance()) , _server(new QtHttpServer (this)) { _server->setServerName (QStringLiteral ("Hyperion Webserver")); @@ -21,7 +25,7 @@ WebServer::WebServer(const QJsonDocument& config, QObject * parent) connect (_server, &QtHttpServer::error, this, &WebServer::onServerError); // create StaticFileServing - _staticFileServing = new StaticFileServing (_hyperion, this); + _staticFileServing = new StaticFileServing (this); connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply); Debug(_log, "Instance created"); @@ -38,8 +42,17 @@ void WebServer::onServerStarted (quint16 port) { Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); - BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister(); - bonjourRegister_http->registerService("_hyperiond-http._tcp", port); + if(_serviceRegister == nullptr) + { + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-http._tcp", port); + } + else if( _serviceRegister->getPort() != port) + { + delete _serviceRegister; + _serviceRegister = new BonjourServiceRegister(this); + _serviceRegister->registerService("_hyperiond-http._tcp", port); + } } void WebServer::onServerStopped () { @@ -57,10 +70,8 @@ void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocu { const QJsonObject& obj = config.object(); - bool webconfigEnable = obj["enable"].toBool(true); _baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH); - if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) { QFileInfo info(_baseUrl); @@ -81,10 +92,10 @@ void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocu _port = obj["port"].toInt(WEBSERVER_DEFAULT_PORT); stop(); } - if ( webconfigEnable ) - { - start(); - } + + // eval if the port is available, will be incremented if not + NetUtils::portAvailable(_port, _log); + start(); } } diff --git a/libsrc/webserver/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp index 7ee64530..938175a6 100644 --- a/libsrc/webserver/WebSocketClient.cpp +++ b/libsrc/webserver/WebSocketClient.cpp @@ -1,13 +1,18 @@ #include "WebSocketClient.h" -#include "QtHttpRequest.h" -#include "QtHttpHeader.h" +// hyperion includes #include + +// JsonAPI includes #include +// qt includes +#include "QtHttpRequest.h" +#include "QtHttpHeader.h" #include #include #include +#include WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent) : QObject(parent) diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index e343a340..045a3cf4 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -56,10 +56,9 @@ int main(int argc, char** argv) // create the option parser and initialize all parameters Parser parser("V4L capture application for Hyperion"); - Option & argDevice = parser.add