84L2qL>XY=r`*VoDVbU#
zG}gYs1Pn@I`ulLL^i%ywWf5ykkHNDw9l`V&?+S6
zq2AUPG8#nwgwuSU_2faCfHn)l6NRzI^;bRYmOoMWL%VGChnClwC$68m-F`wJi|02g
z4c4kbs0T7B?Uw@*4Y>bsV^*igj~o|z5F~76E4>q%TLV13+Np+aSkGGDbv>OYulz(a
z*$i1|dAia0?N26u#u?{YcI+^5Zbmrig2O+;3hQqE@cCqck^9}1a}a42bov{)Wie2f
zI+OW&`g^R8rZ$Yn^OZcEFpxL8Zt}DyF)6_DA%a_p-iRHKot$VyWDl#zfO8zO#$wyD
zm{?^?O7?{5zAkd>ww}}xOlcobM1FdRJao-QVo-QtPu&SYpO5!IpQu|AC;8KrtQ^_z
z=^y?$THIsaR!v|$;Ucp9c9C9TFL&-{TR5X6|6+w}XGXcO@p;bAR9MNj?
zy^;-U-3?~~R2TRdE|5)CxahdxWT{59$S9l^QFKh9rLNc&n6>q!wd4wGqTG2Pcn~IR
zMf1LeZFIPHX5RGJ&I7FTGwx^)xz-0@9o}_Ft3)l#f9KhJzMQ{4<#2QywCPAe4QALv
zH?-D#XxHB6zt#!roNu+h3rRs^?0??@k|!mmJaR@_iXIfHyHqLJfuPA
z=88(vVq=e!$FxTK4Pg_`ZZ`V3{I7P(eI~3{?zj7eB)9MQ9YHSSdbYycK?NWSHAi-~C)Yqv
z+!-YfYj`&8cdqT9cpBGG9_|=A06a~U*AEZCu-W6hfc)n(}@q0fk6R9?bY=v;7zUup}AgD}|C%hf=d&ZV22%M1~2?Aa0ygb3Q|rD8y?|FsqwP=Cw!`3U({Av!`1ffuON?
zKaC?V5B{I*2z)@6j}2X(1VK)sW>@3{5X8cQP?~SwON4#0ZEO
zJp4w*ZF>d!eXYrCWk86%g4?K{e<@Rm_SaQ~9*hOifRN}0xQ)QarBNTsgSk829TaTZ
zyX<(4XtQv%TN<-Z5(jOpWKQOW+It^Qn-e=pGtu9pyk}#9dJ=oryhDa8h
zfO6sfE2$o%fwXc#2UR@rsBYCfL@vYPclI|%zBz@t#t!8r*C!xZ)(X0Q6QcN)Aw7aCGA6
zZzrV}?GZWI?-Y@u0Cw_YWMQ`Zps=fv3Rii!br)#Tdlgs2t5}HeAMn+8VnS~F*kVr2
zcea{NFuf1vo-G$dPh3TOSyVN#`yIuDV_jN@LP@zz>Gs4`EQ)5Q;p^6O)6_N*;wws-
zdr<%7;Uy{T-cTe<{NfPn_F=aZl!{J~dQ2vl&E~ZD*T-{QtXd4Ms(AWgsozGF5;VJi
zy!oQ=-VCO3Z-?KhNDI5kxel4mjDYGN;ca7P{6k4Cj-vhDp)o*C-Y#`O$yAB*?9AMx
z7(UA4Bl>c^c&VQf)Z~A=L}@ni>7RJ~s>PPIU}tg0V`J%^ZF=pKYZA6EAY0oM9Vu9Q
zHc$ocFxd?P{qR}MV#!B8`K$G)#A{cUutmyW}^32OHdX?FdbTdh!#B`A#>zxA(_@K2I*b?U+3M+dw=v
zld93C)bv*mcDq5a!;3vj#a2=Fw)_j*aLm9xYxqXvZC)I
zf4d7GNC}P*$Mz1iqUTKiqs9bxhXe|N*Ht$boGq96E&y5NwF1%=Zo9&{)_NO3`9{by5D6mw
zqiE^7xYB%Sha5*l9SUBke}3lv-@G#CjG+55p+k{dFQo9pK?NT$5%Spg>^;y2(75uG
z!0FTHooAGw+c%*5EnL9^TuVR0?{Z)J?|y}X!7kHJMNW_?!mKaREfg3a&0VcW+^
zzh0XBHg^8g#;J1Qy;^k{IOv$V^ao=Qg$w(EBxXINSCVq$N~@yQ>_fLXDX>KTQ?SuJAhYTujL__XufW7_5cwlqpBjwc&1u$Ffh~5GhC|Xyo2x$){{%IJAU+V}
zn!-PsXq4Wy4>ne*N==i
zj@BF13zTU6kiC#Ke3kw6uaUfoHSF4>?C*e`J*2?SrITh2CA;0Z^p0>-ca2rf^mMz1
z4F<@jeS1srJr17IiMFe}zuS75H~JQSN*r{t^Mr47t%wbE`hb7|jc0Nm5eZW8GR|l$Ud5chtNksfW0m
z5p#{Ll|A*EmtZKiUqffu1upscS2(DR5Eh?)CZ9Jmw15(;{PNJvy)-S`hs@KKIk&N=
zw+CTaHoDADP_)(eo%dFFMtZ;tr*5^-@3^;iDFiiTh~Y8BCOaaqY0QED9_{88nzi8gI>N`9?A4q5oMdFCUa)f8p|{2CpX6@gn#B8%@;y*KZvNm#
zTs0~cJ^n(n``Nf+yf8WKgPG8VNT##MtQ2h8?((cgyPJbO-+3ioKZF%qHhcjg*8CpR
z!lz_r_|0GDLzuW-bf=;MEzY}l0#v10h43*a2aCIv7q$PE!4u7nF7MJ%Vl12)%G4|L
zn>x1mE>gY05@%<iQ;@aOhG--%`5t6sn85*b(l#FOV(^UWRagA1}s`6yfE)CLEnJ
zg=C+o`h8{ibO
z&flj1gTycKdb1=g_%W6MJDw4DT^XAqCgAIUQ*rGnomoN`0i6Y_
z@W_AytZGYhLxTG2p@i^>2hKnn`5~}Z+DehYFWKlV)|HWvu!j?vExQG1*|N3B9cfA-
z*=PQR5x*Fp25d1^EBz-3$Hx74)>2FP*-@zfaW2qSPd8&P&nr93M;E1IHRl?@=E)>+
za}abPaQ3md0YIOCs>IRFc0}d=DrL7AU7UX}v}T*h9n@yC
zI`eIJE9hp9sntDsKY7ZPm5CP(kle$;6X#h=vDF2LEXE*O*-4YbMUCHEQ(z4$iFh~!
zwnTKs#j@l<+};c?ysUtZ6Es4~2M{KdKC7z|e0D{H16gr$UwxtnglEKOqge8%E^Vzk
z%cC+tl~WP+@WC5mNmIXzAB&%NuU~^Pg)CM!Q)b#QX-Q9rQdeLasqB@^!gcfy&V61b
z%<18g|1vL;_TgymLj6?aoG&qOzZ$yuIm~PpSiN!hB)(S_1~LV%r{su|^+^nrFgWy>
zrKTqlrtN80C(fB)`BUPPS!=^rkF(f|C4>AV0~caZ@qaT2%8HL5|ET@6nDL}fVI-)^
zjVk+*+1wcC+=N)DY7HLedP7w#uMx*=Oed%AhR!(wuboQmur7-LQ>R<%@*2)brElJo
zCEKm**%>g`Zk3eudnx)ho95F~3e43_DRa&SCwKi-dd%+-%Kk-=v!IOwx
zom}KNz4o3E*SY6q5Jtl>PuFoi&}Bn&Z>i?JAa66fvU+1xc0&zCHu_oxoLSe3k0vK>j~-^~?491c@OXY4+d)WrnjmJQNSMoQM=@9UMc-%
z9#}HikF{^Hyvgzt%`tppnrG%#ZNpKpOuB~a}
z4~466)rxbT36~-RrvH>rhi-&q9L}_@D4vXCc#n{!6jKGp^nHU~tm~^sWubBt%G={i
zAZ*auWth*lei#*@-Q3f;QWE7@KcbHS6Fe*zl|mkw=+TL9t|p$wSxM#ustc(CaRWm$
zrXAL{2TsQ21txWD)~02b(e$uoGX@bHqaLgeidPeeJW$506*dVFztd9s&C;S6|E%Ka
z5z(HW)j)JGTS?!^+`1fPSAhEKE`9nd%}@_230T&LE?JNdc5j5ez3_}=PtByVMg=a~
zm-%mG@O*oH(I3-(yK8i=l%Z8mFX#X4APEI9OzpI8uBNY?$=AMG8cB%<;z}C&VV!ug
zDWt0Yjk`lFIb|6Oi-ZXNqs5_9-nbpTOlGZHtK9Z@u}pQGBU30Xyv_6?`z-&qKR7b7
zGa~HBx_9H?!;cTX^K*xEYlOcsM4f5>b<2GGAC=`A+;t!*|BINw*PB|gD0=@gqoKqS
z|KqA=uV1eJmq$(!qw)PO;xfWWy+ie;lZz-1$MI5{&Wt}Jw>F>MBoTyq4_!Fm&|cdW
zZjWhPce$uE?8%Z^c(ZVnwE%
zsT2s*l1=OhcLC!-xnkkt6IXBvzA@;fW2V$A@dTS4%+vR1P!VA3_<=X=&lapFPtjQd
z6D}8;Z!nIFGCXzmO#BOW<=!5VOY$+7f%zYcn^Iq}X{M$#qYl)%8U(n%?Tx)SgAc{s
z>ZD*F;@~$e#S6dlK~8+|f#}O4WZ$eVOZ{0%DTW5VqzKj3U86HNwl(Y_<<;tuo*Lr!
ziTPc0>CUBAk-hFmqz-aN>6ZQ6lM&hxCIlFnV<1SoV<>swI-K-!aueKbSj%}J$*d+NFblIT
z*b*UjWSgzG+WKnhKeMbh>A2X)@uPv2t=Lij*s#<`nNEB}iL^Y6nJhH*gh$!5oSwgR
z*-vsf*`7r>&8sXX$xAo^+Gte2#PBz1m99*>9`)^w_wQE&E1IKeJXqypVu>xm{p7j+
zqEoJq&A5mMxdbr1_8#dpP62M+8+S&|LNR0Rm6-Mh)FOPbBv|_d4;OkW8GlpEhdD
zYEtib6fwfaN?fUW372OuPlt$pRA#lilO)RFFE5LA66KiIhx9kn?!DoX4_|P^9NVDT
z_l{fWqR>*OgKO;OZ~uxs8fBopk$1%3RSImdFFdJ%l5+@VAJbUTg(@y&ezoj?Wc_MO
z&B~XKJ7Z`Q+AqyAZ!79!8m9p=Mc-_5%P>wA=U>`f#CkU+-BVF2^j@(EnB*;jAJt>h
zj(ZzzMXOEX7TVP!eNag4PQh7eYvHrL9XfS&V^CV*;-qN$M(%doMxdN{45`MhR`pVu
z1>n1|XtlbPRF>21G?1Z#`}q`cJSKZs`4bHFcRt+M5~mXLd|0T==B2;l+`6M+
zm8Q92ed1PAN5J5BJ)=}2AJNLfr7FXEt!kDEquKn`1d@IWQxXf3>sN&BQJIHY*;KcZ
zu}#HWiOblNam?|vYU5=C7m`h*FEV?MhIVrHk*yDjD@k8$u2hwe2NS28!f`~gIDQf<
zOg0uK?e{g!N8zADQBx%JElm=qgI&M@WheC$ZdNAQr_lXUJ&Td{fh4{uH@nu|0j3FF
zCyzE2!e}ci5DAHHk7sj~T7r(#Mb#OKZrC=_>zs)PDr_$(ERoszE5%V7x
zTuT9UTyf+Zw^~OOS7n4U_6-vAvkZWwB2(g5=amv%uf1&1%5}Dz?6GVTyh!1IEZAM-
z^og#T84JQbHQLD(jpc?2n_}!hpWx%tfpEfdjgRsp7xht}@QnD?=DiLnYOpvrYh}^)
zw_H33V>g?P!K{%s7-adu-BTs#t)=nw#4ef%#@=8eVk-2)q58k;ii?BG?_8?P4L=!^
zQyD7ltQ&NNB->*-U=`2?cvC|xr>W86&8j%*+}*Z2#o-5UMq}Db{rnV(1{Kp7p?R^#
ztV2I#DZv`*Jttd!9iF;BH^qHmM~^yMz=IpfURtnESoZxauF)iw$e*weV{Yo(Yb~lX
zheU8JEdBY5bHI2L^BgYmtLoX4T&rB1gQ(h9H-yFa7H8(rHWx#ldgv7wXLa>VyG-ao
zN}5$JhUq73RW!l@VUXiz+x9Q&$YYc1Gt8C*$W*x&EUf&puTJnzE&eAL@8a54cSou6O<@9wN~N2isxm)9=^;wcRONOS9N?I!P^@|$5YGhUXoTu2Y{M2I
zo0f%fldP)ha21@B0ye0VcjNiuqty+(PKl{9&b;>N7`CT0j_F$NSD>_i=(|)u{vDi+
z{pXy!Wa+qud7fI=>eNQ7G(wuEYOMSa?`o%z_4P#Cs%YV~>l8-{@#>fDZo>bZ=OoBrpi#TH0KoUzH_XOAGWP}2
zmjN2Zm$SB3?4=3KDQfhv7YxO#kmi0GA*7T)9b!bW@Uz+N1F@T!YX{CG%LdMIi>*49d@q9JppKOjvqK$z6eFeX3kx
zs@yDga&dO;Sn@8`z0)590Gg)DBVAf*Ts%2S6@ahVIL3r~Z0WPq_KmdqsCCN>Q+1oN
zDZVdOh8!@JPRW#BE*^HA@$DyEjML3th-sBN>l}fW$|Gl6W~2)f1Y0$mn9$1TljJ|%
zbx9|Zo!oJRH;j1zX5SBG)tZ5aDnFd?A)8AChv@^~XwI<42
zJ#DCMm)U$rK=p^~mfGe@pLL1VTi544BPcAd@d>QN6X^~eTLm>
z=8JeqhObmQWQtU;y@fZ_?uU6@e!4FkJ+AG^sI{mal(F(Zxc?U6SJid6^+_Ac_cD$l
z5l&YdvWT&29s#c`pRWm#>JHxx0ux8uVssQQ%b%Hx_32+R8%t*{`S63M;01!jR?5im
z*n3Jyl)P*E28%Vo2HkdRxhl?%9;~&u
zP%9={f%mRVAAWNkS(ZE6ZPK(02TA1(mLxZ6G}Yif9qh##w6o
zaJldT%bw~9TwgOjcDe@)XE`9s&SG^kss;Z^Za=Wa<4j^atd{IWP%qw=d^o|`1wzE8c>d^c*m@y8#V
zD7JX2dsi$LNPt_^ZcQ>hYh$pF*zq6e1^y|i1Qpt%PskKIba1Zp>p)3Wa>|ZIgN=5b
z#llI6@w^kN60~vB%~C?~i`$-sFR!8$wdDLb`R(3?v@1HoMfRIIaRqig
zj#9p6=9J|2JN>9I*jK3)#U3rR^eh?=yF5k}g*3f`ULXF?_2o%B$>P-1t?vnWm_J^Q
zpAHrdb#5@Qx?L5v@uZXMQhNb-c{W8zsGM>84{lh^r*xCaUFz3T9(TY4Z*n|buH{k@
zOK_rB6K55ayTUkaQ;N$@sH3|=dt9j8E80t^6gEZMO=@Y^o)*i0SWnl&Ca;V{k6Fmk
zlNKkr$&F1#^r%m+9G4dAF7LE>+OJp-O-Ve1lE;M7C-q^8H-IL|@#mIaeUd#9>1S$t
z78g1*M>hG_0j`3qKIQBRPpbZd5l0g9b=3!`gx8$p##d&pBF8ySQtE3AE`HX-8es}n
zyU^~2R==*pfS@=I0Ex#-9f5_|{x}R-E_@g+`$uKjh|T`b_Q`>j=Cr
z^Am@x57uSXI47Rf3QxiJX5I$6c{<{MCiXUzl9cA?e0}&t
zJK4R|?AKuFit(!K!Fzg<7`OF>nefI5L#`ir^w?#(U(dOJkhW0VKfEHGUE)89XLd32
z-4iCvdhP+Wq!5QHn;cPkzc;S(r`Q&AI@-odWwJ16IURZC~fWp*8RlMxSqSXO#&yQw`pA%1YA!S~I
zY-2GU%?%Z~?^>#Kneroc(9V9G4E$LH^0J|R!f=7$;(L_7x7DWxZ-RvuvV6~e
z(H&F{d`hyifhEYq&2iiKHq`j}&Pfk2XF8KzPt^f#ShP|TFTdCP`LZYL%`MWA?Pq@E
zEV?4jfL2k`XMX+Ko06Y^s>0)pg(1oRU0u1iBFa4#pI+SWn=I5o
zd-Fq0ag3wlnfo0nBubair`<#I@jc{3x}iI44U}vPiqg6>iAm2_zh7u=Lw&rm6Mx*OrmC%
z$zY;kA|lodOe)HIJU)EebI0qN2JEZ9-JAHNknxV=9#20jJiq4Ek%Q=?`n_~ZcbFqM
z`+F>DPvln&GaAm)NgUhQqS)@(4UDLjj5-h2FkHPnpJ
z+H_&YJS|O2pDnO@)c36~v=AjC{oZBbLNcY-4;aVX+9&n;B5L|1v6q{}iE^Jzwya9>
zUW(1nqaqu%%zR1R031
zd!~BcOwumH=@gvh=>fglzi7e`9W9eXC^EqqPG`LgidjR?;-ek
zAQye-yZ>I`@8SIf{bEWS!$5yKTf)Ko6C(lafo{%q$)@8?=4xbguphBX4xp
zmv)D7h384^F4#He!=Xf%62=g_)zsk1Ds$`ZO5&dyiZA~M;QrPN7^$@E-d+xTdao=F
zGr=vKsEf6?xpZNv15C;se4#!eumuMTOul&do^nihADSU!Xx1qwv9~tY^*IPr@mK?l
ziMDWEH+gA>eR8ux`##UTLp{{8h+Uq5EAfM8PWld}HnEPbVibAZEDrjxpyk3_nlw&n
z@{|0#YK6KG(o?I?bm9mCANPj(^*LQ`+Y5rlDe=7;k4+4}0jn^(QuBi~iw2y`hWUI6)}4is@GGDk~7bh^Fo{$TfmIN&ZZSMN^Rz|+x-=DoN<0)
zv-Ok38Lsk%lLh(LTiP_PvrA0gnrfky*?SqvM<(Wb1xdO}OX~83!X6oSjbk&p7
zaN=D|m1V)=DIR%CES<8ADEHWhg4xb-!EL<)X8Gv^0QCF#!+Ao2f)-BuQci1P^VG2O
zw$5sfE9ICFTbE!VWL0GU_+bt6aQMc^&(y--MFw)o_@o;=#Ul?-)SEp$@7$Xu>@R=9
zk4}s{RKsby|2m!M7{aFw9vNR;;LS}4Z(wvYogQT38bYl{e1vfBSjwcEGn{BOawcIz
zHVM@K25as)#hq3k`_%Y{MCa^O8+*a6%Zds*$J1@eOOa|t%w9pVcW!3tT0{#1Q*)QE
z8ea)#t7pesj$G&rH?CD;>nf+MmU=5*mc%FHGksTId^Ox2!{kz^Sz|l+^pK+@Q+nAo
zcZe8?lZPTSl1bzUK}b&8hO<8-fI`?j*SSQgDXYu7G<-x+{PJqDS^(qDAJ@hj(O+H=
zLhYcPL*VSSuet!?o=j~HcT4Wdjh$Ls^+Qx#*)E`E>U_D3scLGl4>()bo7uQO#Q&%V
zT#ET!W4q=*HC%I&+c_i(_XE4myB}*cinC+S2&7}KGd#=(v{h`*uQ|Ds8?Fox&+WJgPv!tLL{%8cQI$c
z2%TZQVDagMwy8>Q#mi_Lg4N3Xq{T$D$poJv2Z4C2C&kvqn=Ctq8)bx3`BH{To8n#K
zNY)W(1@BPlFbws)7U+?xUmq;#IerE?*ve>eTr!k?>{|rRUjO2Q;JGlu6tBw*
zNQxnXu<~c`T(`ZNCX}qjb@a^`3ul}`p|^SAkE3747F{v2SzKsrF_x8Mj^43VH$LOK
zutR73;dAWKE0?r8hzr
zz*)(jo++`pm7bW$-QqI4{{?z|Jz6JQdcwO-D
a^uXgYU)APpf`WpABK=iC0xWLe|9=4L5QP8$
literal 0
HcmV?d00001
diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml
new file mode 100644
index 00000000000..7ca10a423d7
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.3.0-SNAPSHOT
+
+
+ org.openhab.binding.huesync
+
+ openHAB Add-ons :: Bundles :: Hue Sync Box Binding
+
+
diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml
new file mode 100644
index 00000000000..3d292e7237a
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml
@@ -0,0 +1,10 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ openhab-transport-mdns
+ mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java
new file mode 100644
index 00000000000..cb3355b1293
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HdmiChannels.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HdmiChannels {
+ public String name;
+ public String type;
+ public String mode;
+ public String status;
+
+ public HdmiChannels(String name, String type, String mode, String status) {
+ this.name = name;
+ this.type = type;
+ this.mode = mode;
+ this.status = status;
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java
new file mode 100644
index 00000000000..2c59c18b759
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link HueSyncConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncConstants {
+ public static class ENDPOINTS {
+ public static final String DEVICE = "device";
+ public static final String REGISTRATIONS = "registrations";
+ public static final String HDMI = "hdmi";
+ public static final String EXECUTION = "execution";
+
+ public static class COMMANDS {
+ public static final String MODE = "mode";
+ public static final String SYNC = "syncActive";
+ public static final String HDMI = "hdmiActive";
+ public static final String SOURCE = "hdmiSource";
+ public static final String BRIGHTNESS = "brightness";
+ }
+ }
+
+ public static class CHANNELS {
+ public static class DEVICE {
+ public static class INFORMATION {
+ public static final String FIRMWARE = "device-firmware#firmware";
+ public static final String FIRMWARE_AVAILABLE = "device-firmware#available-firmware";
+ }
+ }
+
+ public static class COMMANDS {
+ public static final String MODE = "device-commands#mode";
+ public static final String SYNC = "device-commands#sync-active";
+ public static final String HDMI = "device-commands#hdmi-active";
+ public static final String SOURCE = "device-commands#hdmi-source";
+ public static final String BRIGHTNESS = "device-commands#brightness";
+ }
+
+ public static class HDMI {
+ public static final HdmiChannels IN_1 = new HdmiChannels("device-hdmi-in-1#name", "device-hdmi-in-1#type",
+ "device-hdmi-in-1#mode", "device-hdmi-in-1#status");
+ public static final HdmiChannels IN_2 = new HdmiChannels("device-hdmi-in-2#name", "device-hdmi-in-2#type",
+ "device-hdmi-in-2#mode", "device-hdmi-in-2#status");
+ public static final HdmiChannels IN_3 = new HdmiChannels("device-hdmi-in-3#name", "device-hdmi-in-3#type",
+ "device-hdmi-in-3#mode", "device-hdmi-in-3#status");
+ public static final HdmiChannels IN_4 = new HdmiChannels("device-hdmi-in-4#name", "device-hdmi-in-4#type",
+ "device-hdmi-in-4#mode", "device-hdmi-in-4#status");
+
+ public static final HdmiChannels OUT = new HdmiChannels("device-hdmi-out#name", "device-hdmi-out#type",
+ "device-hdmi-out#mode", "device-hdmi-out#status");
+ }
+ }
+
+ public static final String APPLICATION_NAME = "openHAB";
+
+ /** Minimal API Version required. Only apiLevel >= 7 is supported. */
+ public static final Integer MINIMAL_API_VERSION = 7;
+
+ public static final String BINDING_ID = "huesync";
+ public static final String THING_TYPE_ID = "box";
+ public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
+
+ public static final String PARAMETER_HOST = "host";
+ public static final String PARAMETER_PORT = "port";
+
+ public static final Integer REGISTRATION_INITIAL_DELAY = 3;
+ public static final Integer REGISTRATION_INTERVAL = 1;
+
+ public static final String REGISTRATION_ID = "registrationId";
+ public static final String API_TOKEN = "apiAccessToken";
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java
new file mode 100644
index 00000000000..57a5ce5b3ed
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.device;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * HDMI Sync Box Device Information
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ * @see Hue
+ * HDMI Sync Box API
+ */
+@NonNullByDefault
+public class HueSyncDevice {
+ /** Friendly name of the device */
+ public @Nullable String name;
+ /** Device Type identifier */
+ public @Nullable String deviceType;
+ /**
+ * Capitalized hex string of the 6 byte / 12 characters device id without
+ * delimiters. Used as unique id on label, certificate common name, hostname
+ * etc.
+ */
+ public @Nullable String uniqueId;
+ /**
+ * Increased between firmware versions when api changes. Only apiLevel >= 7 is
+ * supported.
+ */
+ public int apiLevel = 0;
+ /**
+ * User readable version of the device firmware, starting with decimal major
+ * .minor .maintenance format e.g. “1.12.3”
+ */
+ public @Nullable String firmwareVersion;
+ /**
+ * Build number of the firmware. Unique for every build with newer builds
+ * guaranteed a higher number than older.
+ */
+ public int buildNumber = 0;
+
+ public boolean termsAgreed;
+
+ /** uninitialized, disconnected, lan, wan */
+ public @Nullable String wifiState;
+ public @Nullable String ipAddress;
+
+ public @Nullable HueSyncDeviceCapabilitiesInfo capabilities;
+
+ public boolean beta;
+ public boolean overheating;
+ public boolean bluetooth;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java
new file mode 100644
index 00000000000..8049f1557db
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.device;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * HDMI Sync Box Device Information Capabilities
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ * @see Hue
+ * HDMI Sync Box API
+ */
+@NonNullByDefault
+public class HueSyncDeviceCapabilitiesInfo {
+ /** The total number of IR codes configurable */
+ public int maxIrCodes;
+ /** The total number of Presets configurable */
+ public int maxPresets;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java
new file mode 100644
index 00000000000..12bb1ff358c
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.device;
+
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * HDMI Sync Box Device Information - Extended information (only available
+ * to registered clients)
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ * @see Hue
+ * HDMI Sync Box API
+ */
+@NonNullByDefault
+public class HueSyncDeviceDetailed extends HueSyncDevice {
+ public @Nullable HueSyncDeviceDetailedWifiInfo wifi;
+ public @Nullable HueSyncDeviceDetailedUpdateInfo update;
+
+ /** UTC time when last check for update was performed. */
+ public @Nullable Date lastCheckedUpdate;
+ /**
+ * Build number that is available to update to. Item is set to null when there
+ * is no update available.
+ */
+ public int updatableBuildNumber;
+ /**
+ * User readable version of the firmware the device can upgrade to. Item is set
+ * to null when there is no update available.
+ */
+ public @Nullable String updatableFirmwareVersion;
+ /**
+ * 1 = regular;
+ * 0 = off in powersave, passthrough or sync mode;
+ * 2 = dimmed in powersave or passthrough mode and off in sync mode
+ */
+ public int ledMode = -1;
+
+ /** none, doSoftwareRestart, doFirmwareUpdate */
+ public @Nullable String action;
+ public @Nullable String pushlink;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java
new file mode 100644
index 00000000000..73c99bb466b
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.device;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * HDMI Sync Box Device Information - Automatic Firmware update
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ * @see Hue
+ * HDMI Sync Box API
+ */
+@NonNullByDefault
+public class HueSyncDeviceDetailedUpdateInfo {
+ /**
+ * Sync Box checks daily for a firmware update. If true, an available update
+ * will automatically be installed. This will be postponed if Sync Box is
+ * passing through content to the TV and being used.
+ */
+ public boolean autoUpdateEnabled;
+ /**
+ * TC hour when the automatic update will check and execute, values 0 – 23.
+ * Default is 10. Ideally this value should be set to 3AM according to user’s
+ * timezone.
+ */
+ public int autoUpdateTime;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java
new file mode 100644
index 00000000000..0c99f86acb5
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.device;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * HDMI Sync Box Device Information - Wifi connection information
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ * @see Hue
+ * HDMI Sync Box API
+ */
+@NonNullByDefault
+public class HueSyncDeviceDetailedWifiInfo {
+ /** Wifi SSID */
+ public @Nullable String ssid;
+ /**
+ * 0 = not connected;
+ * 1 = weak;
+ * 2 = fair;
+ * 3 = good;
+ * 4 = excellent
+ */
+ public int strength;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java
new file mode 100644
index 00000000000..88a51b78b00
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.execution;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Root object for execution resource
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncExecution {
+ private final Logger logger = LoggerFactory.getLogger(HueSyncExecution.class);
+
+ public static final List KNOWN_MODES = Collections
+ .unmodifiableList(Arrays.asList("powersave", "passthrough", "video", "game", "music"));
+
+ private @Nullable String mode;
+
+ /**
+ *
+ * @return powersave, passthrough, video, game, music
+ */
+ @JsonProperty("mode")
+ public @Nullable String getMode() {
+ return this.mode;
+ }
+
+ /**
+ *
+ * @apiNote More modes can be added in the future, so clients must gracefully
+ * handle modes they don’t recognize. If an unknown mode is received, a
+ * warning will be logged and mode will fallback to "unknown"
+ *
+ * @param mode powersave, passthrough, video, game, music
+ */
+ public void setMode(String mode) {
+ if (!HueSyncExecution.KNOWN_MODES.contains(mode)) {
+ logger.warn(
+ "device mode [{}] is not known by this version of the binding. Please open an issue to notify the maintainer(s). Fallback will be used. ",
+ mode);
+ }
+
+ this.mode = HueSyncExecution.KNOWN_MODES.contains(mode) ? mode : "unknown";
+ }
+
+ /**
+ * Reports `false` in case of `powersave` or `passthrough` mode, and `true` in case of `video`, `game`, or `music`
+ * mode.
+ * When changed from false to true, it will start syncing in last used mode for current source.
+ * When changed from true to false, will set passthrough mode.
+ */
+ public boolean syncActive;
+ /**
+ * Reports `false` in case of `powersave mode`, and true in case of `passthrough`, `video`, `game`, `music` mode.
+ * When changed from false to true, it will set passthrough mode. When changed from `true` to `false`, will set
+ * powersave mode.
+ */
+ public boolean hdmiActive;
+
+ /**
+ * Currently selected hdmi input: `input1`, `input2`, `input3,` `input4`
+ */
+ public @Nullable String hdmiSource;
+
+ public @Nullable String hueTarget;
+ public @Nullable String lastSyncMode;
+ public @Nullable String preset;
+
+ /**
+ * brightness:
+ * - Get, Put
+ * - number, uint
+ * - 0 ... 200 (100 = no brightness reduction/boost compared to input, 0 = max reduction, 200 = max boost)
+ */
+ public int brightness;
+
+ public @Nullable HueSyncExecutionVideo video;
+ public @Nullable HueSyncExecutionGame game;
+ public @Nullable HueSyncExecutionMusic music;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java
new file mode 100644
index 00000000000..f806d2efa42
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.execution;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncExecutionGame {
+ public @Nullable String intensity;
+
+ public boolean backgroundLighting;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java
new file mode 100644
index 00000000000..d56a92782d4
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.execution;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncExecutionMusic {
+ public @Nullable String intensity;
+ public @Nullable String palette;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java
new file mode 100644
index 00000000000..46cb01e574a
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.execution;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncExecutionVideo {
+ public @Nullable String intensity;
+
+ public boolean backgroundLighting;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java
new file mode 100644
index 00000000000..d4457528b43
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.hdmi;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncHdmi {
+ public @Nullable HueSyncHdmiConnectionInfo input1;
+ public @Nullable HueSyncHdmiConnectionInfo input2;
+ public @Nullable HueSyncHdmiConnectionInfo input3;
+ public @Nullable HueSyncHdmiConnectionInfo input4;
+
+ public @Nullable HueSyncHdmiConnectionInfo output;
+
+ /** x @ – */
+ public @Nullable String contentSpecs;
+
+ /** Current content specs supported for video sync (video/game mode) */
+ public boolean videoSyncSupported;
+ /** Current content specs supported for audio sync (music mode) */
+ public boolean audioSyncSupported;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java
new file mode 100644
index 00000000000..1dac4c657e8
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.hdmi;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class HueSyncHdmiConnectionInfo {
+ /** Friendly name, not empty */
+ public @Nullable String name;
+ /**
+ * Friendly type:
+ * generic,
+ * video,
+ * game,
+ * music,
+ * xbox,
+ * playstation,
+ * nintendoswitch,
+ * phone,
+ * desktop,
+ * laptop,
+ * appletv,
+ * roku,
+ * shield,
+ * chromecast,
+ * firetv,
+ * diskplayer,
+ * settopbox,
+ * satellite,
+ * avreceiver,
+ * soundbar,
+ * hdmiswitch
+ */
+ public @Nullable String type;
+ /**
+ * unplugged,
+ * plugged,
+ * linked,
+ * unknown
+ */
+ public @Nullable String status;
+ /**
+ * video,
+ * game,
+ * music
+ */
+ public @Nullable String lastSyncMode;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java
new file mode 100644
index 00000000000..89b2343dab4
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.registration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncRegistration {
+ public String registrationId = "";
+ public String accessToken = "";
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java
new file mode 100644
index 00000000000..d7651a6872f
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.api.dto.registration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncRegistrationRequest {
+ /** User recognizable name of registered application */
+ public @Nullable String appName;
+ /** User recognizable name of application instance. */
+ public @Nullable String instanceName;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java
new file mode 100644
index 00000000000..08c87729b21
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Binding configuration parameters,
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncConfiguration {
+ public String registrationId = "";
+ public String apiAccessToken = "";
+ public String host = "";
+ public Integer port = 443;
+ public Integer statusUpdateInterval = 10;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java
new file mode 100644
index 00000000000..d33081d86af
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.connection;
+
+import java.net.URI;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.Authentication.Result;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncAuthenticationResult implements Result {
+ private final String token;
+ private final URI uri;
+
+ public HueSyncAuthenticationResult(URI uri, String token) {
+ this.uri = uri;
+ this.token = token;
+ }
+
+ public String getToken() {
+ return this.token;
+ }
+
+ @Override
+ public URI getURI() {
+ return this.uri;
+ }
+
+ @Override
+ public void apply(@Nullable Request request) {
+ if (request != null && !request.getHeaders().contains(HttpHeader.AUTHORIZATION)) {
+ request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.token);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java
new file mode 100644
index 00000000000..afc670b3644
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java
@@ -0,0 +1,248 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.connection;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.cert.CertificateException;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS;
+import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncConnection {
+ public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ /**
+ * Request format: The Sync Box API can be accessed locally via HTTPS on root level (port 443,
+ * /api/v1), resource level /api/v1/ and in some cases sub-resource level
+ * /api/v1//.
+ */
+ private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s";
+ private static final String API = "api/v1";
+ private final Logger logger = LoggerFactory.getLogger(HueSyncConnection.class);
+
+ private final Integer port;
+ private final String host;
+
+ private final ServiceRegistration> tlsProviderService;
+ private final HttpClient httpClient;
+ private final URI deviceUri;
+
+ private Optional authentication = Optional.empty();
+
+ protected String registrationId = "";
+
+ public HueSyncConnection(HttpClient httpClient, String host, Integer port)
+ throws CertificateException, IOException, URISyntaxException {
+ this.host = host;
+ this.port = port;
+
+ this.deviceUri = new URI(String.format("https://%s:%s", this.host, this.port));
+
+ HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port);
+ BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+ this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider,
+ null);
+ this.httpClient = httpClient;
+ }
+
+ public void updateAuthentication(String id, String token) {
+ this.removeAuthentication();
+
+ if (!id.isBlank() && !token.isBlank()) {
+ this.registrationId = id;
+
+ this.authentication = Optional.of(new HueSyncAuthenticationResult(this.deviceUri, token));
+ this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication.get());
+ }
+ }
+
+ // #region protected
+ protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload,
+ @Nullable Class type) {
+ try {
+ return this.processedResponse(this.executeRequest(method, endpoint, payload), type);
+ } catch (ExecutionException e) {
+ this.handleExecutionException(e);
+ } catch (InterruptedException | TimeoutException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+
+ return null;
+ }
+
+ protected @Nullable T executeGetRequest(String endpoint, Class type) {
+ try {
+ return this.processedResponse(this.executeGetRequest(endpoint), type);
+ } catch (ExecutionException e) {
+ this.handleExecutionException(e);
+ } catch (InterruptedException | TimeoutException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+
+ return null;
+ }
+
+ protected boolean isRegistered() {
+ return this.authentication.isPresent();
+ }
+
+ protected void unregisterDevice() {
+ if (this.isRegistered()) {
+ try {
+ String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId;
+ ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint);
+
+ if (response.getStatus() == HttpStatus.OK_200) {
+ this.removeAuthentication();
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+ }
+ }
+
+ protected void dispose() {
+ this.tlsProviderService.unregister();
+ }
+ // #endregion
+
+ // #region private
+ private @Nullable T processedResponse(Response response, @Nullable Class type) {
+ int status = response.getStatus();
+ try {
+ /*
+ * 400 Invalid State: Registration in progress
+ *
+ * 401 Authentication failed: If credentials are missing or invalid, errors out. If
+ * credentials are missing, continues on to GET only the Configuration state when
+ * unauthenticated, to allow for device identification.
+ *
+ * 404 Invalid URI Path: Accessing URI path which is not supported
+ *
+ * 500 Internal: Internal errors like out of memory
+ */
+ switch (status) {
+ case HttpStatus.OK_200 -> {
+ return (type != null && (response instanceof ContentResponse))
+ ? this.deserialize(((ContentResponse) response).getContentAsString(), type)
+ : null;
+ }
+ case HttpStatus.BAD_REQUEST_400 -> this.logger.debug("registration in progress: no token received yet");
+ case HttpStatus.UNAUTHORIZED_401 -> {
+ this.authentication = Optional.empty();
+ throw new HueSyncConnectionException("@text/connection.invalid-login");
+ }
+ case HttpStatus.NOT_FOUND_404 -> this.logger.warn("invalid device URI or API endpoint");
+ case HttpStatus.INTERNAL_SERVER_ERROR_500 -> this.logger.warn("hue sync box server problem");
+ default -> this.logger.warn("unexpected HTTP status: {}", status);
+ }
+ } catch (HueSyncConnectionException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+ return null;
+ }
+
+ private @Nullable T deserialize(String json, Class type) {
+ try {
+ return OBJECT_MAPPER.readValue(json, type);
+ } catch (JsonProcessingException | NoClassDefFoundError e) {
+ this.logger.error("{}", e.getMessage());
+
+ return null;
+ }
+ }
+
+ private ContentResponse executeRequest(HttpMethod method, String endpoint)
+ throws InterruptedException, TimeoutException, ExecutionException {
+ return this.executeRequest(method, endpoint, "");
+ }
+
+ private ContentResponse executeGetRequest(String endpoint)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint);
+
+ return httpClient.GET(uri);
+ }
+
+ private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload)
+ throws InterruptedException, TimeoutException, ExecutionException {
+ String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint);
+
+ Request request = this.httpClient.newRequest(uri).method(method);
+
+ this.logger.trace("uri: {}", uri);
+ this.logger.trace("method: {}", method);
+ this.logger.trace("payload: {}", payload);
+
+ if (!payload.isBlank()) {
+ request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.toString())
+ .content(new StringContentProvider(payload));
+ }
+
+ return request.send();
+ }
+
+ private void handleExecutionException(ExecutionException e) {
+ this.logger.warn("{}", e.getMessage());
+
+ Throwable cause = e.getCause();
+ if (cause != null && cause instanceof HttpResponseException) {
+ processedResponse(((HttpResponseException) cause).getResponse(), null);
+ }
+ }
+
+ private void removeAuthentication() {
+ AuthenticationStore store = this.httpClient.getAuthenticationStore();
+ store.clearAuthenticationResults();
+ this.httpClient.setAuthenticationStore(store);
+
+ this.registrationId = "";
+ this.authentication = Optional.empty();
+ }
+
+ // #endregion
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java
new file mode 100644
index 00000000000..c6e590dcc5d
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java
@@ -0,0 +1,197 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.connection;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.cert.CertificateException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.huesync.internal.HueSyncConstants;
+import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed;
+import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution;
+import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi;
+import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration;
+import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest;
+import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
+import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+/**
+ * Handles the connection to a Hue HDMI Sync Box using the official API.
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncDeviceConnection {
+ private final Logger logger = LoggerFactory.getLogger(HueSyncDeviceConnection.class);
+
+ private final HueSyncConnection connection;
+
+ private final Map> deviceCommandExecutors = new HashMap<>();
+
+ public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration)
+ throws CertificateException, IOException, URISyntaxException {
+ this.connection = new HueSyncConnection(httpClient, configuration.host, configuration.port);
+
+ registerCommandHandlers();
+ }
+
+ // #region private
+
+ private void registerCommandHandlers() {
+ this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.MODE,
+ defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.MODE));
+ this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE,
+ defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SOURCE));
+ this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS,
+ defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.BRIGHTNESS));
+ this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC,
+ defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SYNC));
+ this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI,
+ defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.HDMI));
+ }
+
+ private Consumer defaultHandler(String endpoint) {
+ return command -> {
+ execute(endpoint, command);
+ };
+ }
+
+ private void execute(String key, Command command) {
+ this.logger.debug("Command executor: {} - {}", key, command);
+
+ if (!this.connection.isRegistered()) {
+ this.logger.warn("Device is not registered - ignoring command: {}", command);
+ return;
+ }
+
+ String value;
+
+ if (command instanceof QuantityType quantityCommand) {
+ value = Integer.toString(quantityCommand.intValue());
+ } else if (command instanceof OnOffType) {
+ value = command == OnOffType.ON ? "true" : "false";
+ } else if (command instanceof StringType) {
+ value = '"' + command.toString() + '"';
+ } else {
+ this.logger.warn("Type [{}] not supported by this connection", command.getClass().getCanonicalName());
+ return;
+ }
+
+ String json = String.format("{ \"%s\": %s }", key, value);
+
+ this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null);
+ }
+
+ // #endregion
+
+ public void executeCommand(Channel channel, Command command) {
+ String uid = channel.getUID().getAsString();
+ String commandId = channel.getUID().getId();
+
+ this.logger.debug("Channel UID: {} - Command: {}", uid, command.toFullString());
+
+ if (RefreshType.REFRESH.equals(command)) {
+ return;
+ }
+
+ if (this.deviceCommandExecutors.containsKey(commandId)) {
+ Objects.requireNonNull(this.deviceCommandExecutors.get(commandId)).accept(command);
+ } else {
+ this.logger.error("No executor registered for command {} - please report this as an issue", commandId);
+ }
+ }
+
+ public @Nullable HueSyncDevice getDeviceInfo() {
+ return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDevice.class);
+ }
+
+ public @Nullable HueSyncDeviceDetailed getDetailedDeviceInfo() {
+ return this.connection.isRegistered()
+ ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDetailed.class)
+ : null;
+ }
+
+ public @Nullable HueSyncHdmi getHdmiInfo() {
+ return this.connection.isRegistered()
+ ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmi.class)
+ : null;
+ }
+
+ public @Nullable HueSyncExecution getExecutionInfo() {
+ return this.connection.isRegistered()
+ ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecution.class)
+ : null;
+ }
+
+ public @Nullable HueSyncRegistration registerDevice(String id) throws HueSyncConnectionException {
+ if (!id.isBlank()) {
+ try {
+ HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();
+ dto.appName = HueSyncConstants.APPLICATION_NAME;
+ dto.instanceName = id;
+
+ String payload = HueSyncConnection.OBJECT_MAPPER.writeValueAsString(dto);
+
+ HueSyncRegistration registration = this.connection.executeRequest(HttpMethod.POST,
+ ENDPOINTS.REGISTRATIONS, payload, HueSyncRegistration.class);
+ if (registration != null) {
+ this.connection.updateAuthentication(id, registration.accessToken);
+
+ return registration;
+ }
+ } catch (JsonProcessingException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ public boolean isRegistered() {
+ return this.connection.isRegistered();
+ }
+
+ public void unregisterDevice() {
+ this.connection.unregisterDevice();
+ }
+
+ public void dispose() {
+ this.connection.dispose();
+ }
+
+ public void updateConfiguration(HueSyncConfiguration config) {
+ this.logger.debug("Connection configuration update for device {}:{} - Registration Id [{}]", config.host,
+ config.port, config.registrationId);
+
+ this.connection.updateAuthentication(config.registrationId, config.apiAccessToken);
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java
new file mode 100644
index 00000000000..a6ad57a9dce
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.connection;
+
+import java.io.IOException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.X509ExtendedTrustManager;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.PEMTrustManager;
+import org.openhab.core.io.net.http.TlsTrustManagerProvider;
+
+/**
+ * Provides a {@link PEMTrustManager} to allow secure connections to a Hue HDMI
+ * Sync Box
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncTrustManagerProvider implements TlsTrustManagerProvider {
+ private final String host;
+ private final Integer port;
+
+ private final X509ExtendedTrustManager trustManager;
+
+ public HueSyncTrustManagerProvider(String host, Integer port) throws IOException, CertificateException {
+ this.trustManager = PEMTrustManager.getInstanceFromServer("https://" + host);
+ this.port = port;
+ this.host = host;
+ }
+
+ @Override
+ public String getHostName() {
+ return this.host + ":" + this.port;
+ }
+
+ @Override
+ public X509ExtendedTrustManager getTrustManager() {
+ return this.trustManager;
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java
new file mode 100644
index 00000000000..2365a98b173
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.discovery;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.huesync.internal.HueSyncConstants;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.ThingRegistry;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HueSyncDiscoveryParticipant} is responsible for discovering
+ * the remote huesync.boxes using mDNS discovery service.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync")
+public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant {
+ private final Logger logger = LoggerFactory.getLogger(HueSyncDiscoveryParticipant.class);
+
+ /**
+ *
+ * Match the hostname + identifier of the discovered huesync-box.
+ * Input is like "HueSyncBox-XXXXXXXXXXXX._huesync._tcp.local."
+ *
+ * @see·
+ * Service·Name·and·Transport·Protocol·Port·Number·Registry
+ */
+ private static final String SERVICE_TYPE = "_huesync._tcp.local.";
+
+ private boolean autoDiscoveryEnabled = true;
+
+ protected final ThingRegistry thingRegistry;
+
+ @Activate
+ public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) {
+ this.thingRegistry = thingRegistry;
+ }
+
+ @Override
+ public Set getSupportedThingTypeUIDs() {
+ return Collections.singleton(HueSyncConstants.THING_TYPE_UID);
+ }
+
+ @Override
+ public String getServiceType() {
+ return SERVICE_TYPE;
+ }
+
+ @Override
+ public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+ if (this.autoDiscoveryEnabled) {
+ ThingUID uid = getThingUID(service);
+ if (uid != null) {
+ try {
+ logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(),
+ service.getHostAddresses()[0], service.getPort());
+
+ Map properties = new HashMap<>();
+
+ properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]);
+ properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort());
+
+ return DiscoveryResultBuilder.create(uid).withLabel(service.getName()).withProperties(properties)
+ .build();
+ } catch (Exception e) {
+ logger.debug("Unable to query device information for {}: {}", service.getQualifiedName(),
+ e.getMessage());
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public @Nullable ThingUID getThingUID(ServiceInfo service) {
+ String id = service.getName();
+ String[] addresses = service.getHostAddresses();
+
+ if (addresses.length == 0 || id == null || id.isBlank()) {
+ logger.debug("Incomplete mDNS device discovery information - {} ignored.",
+ id == null ? "[name: null]" : id);
+ return null;
+ }
+
+ return new ThingUID(HueSyncConstants.THING_TYPE_UID, id);
+ }
+
+ @Activate
+ protected void activate(ComponentContext componentContext) {
+ updateService(componentContext);
+ }
+
+ @Modified
+ protected void modified(ComponentContext componentContext) {
+ updateService(componentContext);
+ }
+
+ private void updateService(ComponentContext componentContext) {
+ String autoDiscoveryPropertyValue = (String) componentContext.getProperties()
+ .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY);
+
+ if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) {
+ boolean value = Boolean.parseBoolean(autoDiscoveryPropertyValue);
+ if (value != this.autoDiscoveryEnabled) {
+ logger.debug("{} update: {} - {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY,
+ autoDiscoveryPropertyValue, value);
+ this.autoDiscoveryEnabled = value;
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java
new file mode 100644
index 00000000000..c4096dfec7d
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncApiException extends HueSyncException {
+ private static final long serialVersionUID = 0L;
+
+ public HueSyncApiException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java
new file mode 100644
index 00000000000..b42393814da
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncConnectionException extends HueSyncException {
+ private static final long serialVersionUID = 0L;
+
+ public HueSyncConnectionException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java
new file mode 100644
index 00000000000..583d169e8b8
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer;
+
+/**
+ * Base class for all HueSyncExceptions
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public abstract class HueSyncException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ public HueSyncException(String message) {
+ super(message.startsWith("@text") ? HueSyncLocalizer.getResourceString(message) : message);
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java
new file mode 100644
index 00000000000..c3c288ff7bb
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncTaskException extends HueSyncException {
+ private static final long serialVersionUID = 0L;
+
+ public HueSyncTaskException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java
new file mode 100644
index 00000000000..5ce343649ba
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.factory;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.huesync.internal.HueSyncConstants;
+import org.openhab.binding.huesync.internal.handler.HueSyncHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HueSyncHandlerFactory} is responsible for creating things and
+ * thing
+ * handlers.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class)
+public class HueSyncHandlerFactory extends BaseThingHandlerFactory {
+
+ private final HttpClientFactory httpClientFactory;
+ private final Logger logger = LoggerFactory.getLogger(HueSyncHandlerFactory.class);
+
+ @Activate
+ public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception {
+ this.httpClientFactory = httpClientFactory;
+ }
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Collections
+ .singleton(HueSyncConstants.THING_TYPE_UID);
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) {
+ try {
+ return new HueSyncHandler(thing, this.httpClientFactory);
+ } catch (IOException | URISyntaxException | CertificateException e) {
+ this.logger.warn("It was not possible to create a handler for {}: {}", thingTypeUID.getId(),
+ e.getMessage());
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java
new file mode 100644
index 00000000000..22ba24b5963
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java
@@ -0,0 +1,349 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.handler;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.cert.CertificateException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.huesync.internal.HdmiChannels;
+import org.openhab.binding.huesync.internal.HueSyncConstants;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed;
+import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution;
+import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi;
+import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmiConnectionInfo;
+import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration;
+import org.openhab.binding.huesync.internal.config.HueSyncConfiguration;
+import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
+import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException;
+import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask;
+import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask;
+import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTaskResult;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HueSyncHandler} is responsible for handling commands, which are sent to one of the
+ * channels.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncHandler extends BaseThingHandler {
+ private static final String REGISTER = "Registration";
+ private static final String POLL = "Update";
+
+ private static final String PROPERTY_API_VERSION = "apiVersion";
+
+ private final Logger logger = LoggerFactory.getLogger(HueSyncHandler.class);
+
+ Map> tasks = new HashMap<>();
+
+ private Optional deviceInfo = Optional.empty();
+
+ private final HueSyncDeviceConnection connection;
+ private final HttpClient httpClient;
+
+ public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory)
+ throws CertificateException, IOException, URISyntaxException {
+ super(thing);
+
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+
+ this.connection = new HueSyncDeviceConnection(this.httpClient, this.getConfigAs(HueSyncConfiguration.class));
+ }
+
+ // #region private
+ private Runnable initializeConnection() {
+ return () -> {
+ this.deviceInfo = Optional.ofNullable(this.connection.getDeviceInfo());
+ this.deviceInfo.ifPresent(info -> {
+ setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId != null ? info.uniqueId : "");
+ setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType);
+ setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion);
+
+ setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel));
+
+ try {
+ this.checkCompatibility();
+ } catch (HueSyncApiException e) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ } finally {
+ this.startTasks();
+ }
+ });
+ };
+ }
+
+ private void stopTask(@Nullable ScheduledFuture> task) {
+ if (task == null || task.isCancelled() || task.isDone()) {
+ return;
+ }
+
+ task.cancel(true);
+ }
+
+ private @Nullable ScheduledFuture> executeTask(Runnable task, long initialDelay, long interval) {
+ return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS);
+ }
+
+ private void startTasks() {
+ this.stopTasks();
+
+ this.connection.updateConfiguration(this.getConfigAs(HueSyncConfiguration.class));
+
+ Runnable task = null;
+ String id = this.connection.isRegistered() ? POLL : REGISTER;
+
+ this.logger.debug("startTasks - [{}]", id);
+
+ long initialDelay = 0;
+ long interval = 0;
+
+ switch (id) {
+ case POLL -> {
+ initialDelay = 0;
+ interval = this.getConfigAs(HueSyncConfiguration.class).statusUpdateInterval;
+
+ this.updateStatus(ThingStatus.ONLINE);
+
+ task = new HueSyncUpdateTask(this.connection, this.deviceInfo.get(),
+ deviceStatus -> this.handleUpdate(deviceStatus));
+ }
+ case REGISTER -> {
+ initialDelay = HueSyncConstants.REGISTRATION_INITIAL_DELAY;
+ interval = HueSyncConstants.REGISTRATION_INTERVAL;
+
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+ "@text/thing.config.huesync.box.registration");
+
+ task = new HueSyncRegistrationTask(this.connection, this.deviceInfo.get(),
+ registration -> this.handleRegistration(registration));
+ }
+ }
+
+ if (task != null) {
+ logger.debug("Starting task [{}]", id);
+ this.tasks.put(id, this.executeTask(task, initialDelay, interval));
+ }
+ }
+
+ private void stopTasks() {
+ logger.debug("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet()));
+
+ this.tasks.values().forEach(task -> this.stopTask(task));
+ this.tasks.clear();
+
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+ "@text/thing.config.huesync.box.registration");
+ }
+
+ private void handleUpdate(@Nullable HueSyncUpdateTaskResult dto) {
+ try {
+ HueSyncUpdateTaskResult update = Optional.ofNullable(dto).get();
+
+ try {
+ this.updateFirmwareInformation(Optional.ofNullable(update.deviceStatus).get());
+ } catch (NoSuchElementException e) {
+ this.logMissingUpdateInformation("device");
+ }
+
+ this.updateHdmiInformation(Optional.ofNullable(update.hdmiStatus).get());
+ this.updateExecutionInformation(Optional.ofNullable(update.execution).get());
+ } catch (NoSuchElementException e) {
+ Configuration configuration = this.editConfiguration();
+
+ configuration.put(HueSyncConstants.REGISTRATION_ID, "");
+ configuration.put(HueSyncConstants.API_TOKEN, "");
+
+ this.updateConfiguration(configuration);
+
+ this.startTasks();
+ }
+ }
+
+ private void logMissingUpdateInformation(String api) {
+ this.logger.warn("Device information - {} status missing", api);
+ }
+
+ private void updateHdmiInformation(HueSyncHdmi hdmiStatus) {
+ updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_1, hdmiStatus.input1);
+ updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_2, hdmiStatus.input2);
+ updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_3, hdmiStatus.input3);
+ updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_4, hdmiStatus.input4);
+
+ updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.OUT, hdmiStatus.output);
+ }
+
+ private void updateHdmiStatus(HdmiChannels channels, @Nullable HueSyncHdmiConnectionInfo hdmiStatusInfo) {
+ if (hdmiStatusInfo != null) {
+ this.updateState(channels.name, new StringType(hdmiStatusInfo.name));
+ this.updateState(channels.type, new StringType(hdmiStatusInfo.type));
+ this.updateState(channels.mode, new StringType(hdmiStatusInfo.lastSyncMode));
+ this.updateState(channels.status, new StringType(hdmiStatusInfo.status));
+ }
+ }
+
+ private void updateFirmwareInformation(HueSyncDeviceDetailed deviceStatus) {
+ State firmwareState = new StringType(deviceStatus.firmwareVersion);
+ State firmwareAvailableState = new StringType(
+ deviceStatus.updatableFirmwareVersion != null ? deviceStatus.updatableFirmwareVersion
+ : deviceStatus.firmwareVersion);
+
+ setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceStatus.firmwareVersion);
+ setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceStatus.apiLevel));
+
+ this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState);
+ this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState);
+ }
+
+ private void updateExecutionInformation(HueSyncExecution executionStatus) {
+ this.updateState(HueSyncConstants.CHANNELS.COMMANDS.MODE, new StringType(executionStatus.getMode()));
+ this.updateState(HueSyncConstants.CHANNELS.COMMANDS.SYNC,
+ executionStatus.syncActive ? OnOffType.ON : OnOffType.OFF);
+ this.updateState(HueSyncConstants.CHANNELS.COMMANDS.HDMI,
+ executionStatus.hdmiActive ? OnOffType.ON : OnOffType.OFF);
+ this.updateState(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, new StringType(executionStatus.hdmiSource));
+ this.updateState(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, new DecimalType(executionStatus.brightness));
+ }
+
+ private void handleRegistration(HueSyncRegistration registration) {
+ this.stopTasks();
+
+ setProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId);
+
+ Configuration configuration = this.editConfiguration();
+
+ configuration.put(HueSyncConstants.REGISTRATION_ID, registration.registrationId);
+ configuration.put(HueSyncConstants.API_TOKEN, registration.accessToken);
+
+ this.updateConfiguration(configuration);
+
+ this.startTasks();
+ }
+
+ private void checkCompatibility() throws HueSyncApiException {
+ try {
+ HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow();
+
+ if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) {
+ throw new HueSyncApiException("@text/api.minimal-version");
+ }
+ } catch (NoSuchElementException e) {
+ throw new HueSyncApiException("@text/api.communication-problem");
+ }
+ }
+
+ private void setProperty(String key, @Nullable String value) {
+ if (value != null) {
+ Map properties = this.editProperties();
+
+ if (properties.containsKey(key)) {
+ @Nullable
+ String currentValue = properties.get(key);
+ if (!(value.equals(currentValue))) {
+ saveProperty(key, value, properties);
+ }
+ } else {
+ saveProperty(key, value, properties);
+ }
+ }
+ }
+
+ private void saveProperty(String key, String value, Map properties) {
+ properties.put(key, value);
+ this.updateProperties(properties);
+ }
+
+ // #endregion
+
+ // #region Override
+ @Override
+ public void initialize() {
+ try {
+ updateStatus(ThingStatus.UNKNOWN);
+
+ this.stopTasks();
+
+ scheduler.execute(initializeConnection());
+ } catch (Exception e) {
+ this.logger.warn("{}", e.getMessage());
+
+ this.updateStatus(ThingStatus.OFFLINE);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (thing.getStatus() != ThingStatus.ONLINE) {
+ this.logger.warn("Device status: {} - Command {} for chanel {} will be ignored",
+ thing.getStatus().toString(), command.toFullString(), channelUID.toString());
+ return;
+ }
+
+ Channel channel = thing.getChannel(channelUID);
+
+ if (channel == null) {
+ logger.error("Channel UID:{} does not exist - please report this as an issue", channelUID);
+ return;
+ }
+
+ this.connection.executeCommand(channel, command);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ try {
+ this.stopTasks();
+ this.connection.dispose();
+ } catch (Exception e) {
+ this.logger.warn("{}", e.getMessage());
+ } finally {
+ this.logger.debug("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID());
+ }
+ }
+
+ @Override
+ public void handleRemoval() {
+ super.handleRemoval();
+
+ this.connection.unregisterDevice();
+ }
+
+ // #endregion
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java
new file mode 100644
index 00000000000..5b6d2c27bb7
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.handler.tasks;
+
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
+import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration;
+import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
+import org.openhab.binding.huesync.internal.exceptions.HueSyncConnectionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Task to handle device registration.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncRegistrationTask implements Runnable {
+ private final Logger logger = LoggerFactory.getLogger(HueSyncRegistrationTask.class);
+
+ private final HueSyncDeviceConnection connection;
+ private final HueSyncDevice deviceInfo;
+ private final Consumer action;
+
+ public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
+ Consumer action) {
+ this.connection = connection;
+ this.deviceInfo = deviceInfo;
+ this.action = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ String id = this.deviceInfo.uniqueId;
+
+ if (this.connection.isRegistered() || id == null) {
+ return;
+ }
+
+ this.logger.debug("Listening for device registration - {} {}:{}", this.deviceInfo.name,
+ this.deviceInfo.deviceType, id);
+
+ HueSyncRegistration registration = this.connection.registerDevice(id);
+
+ if (registration != null) {
+ this.logger.debug("API token for {} received", this.deviceInfo.name);
+
+ this.action.accept(registration);
+ }
+ } catch (HueSyncConnectionException e) {
+ this.logger.warn("{}", e.getMessage());
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java
new file mode 100644
index 00000000000..18067fa39f6
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.handler.tasks;
+
+import java.util.function.Consumer;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice;
+import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Task to handle device information update.
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncUpdateTask implements Runnable {
+
+ private final Logger logger = LoggerFactory.getLogger(HueSyncUpdateTask.class);
+
+ private final HueSyncDeviceConnection connection;
+ private final HueSyncDevice deviceInfo;
+
+ private final Consumer<@Nullable HueSyncUpdateTaskResult> action;
+
+ public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
+ Consumer<@Nullable HueSyncUpdateTaskResult> action) {
+ this.connection = connection;
+ this.deviceInfo = deviceInfo;
+
+ this.action = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.logger.debug("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType,
+ this.deviceInfo.uniqueId);
+
+ if (!this.connection.isRegistered()) {
+ this.action.accept(null);
+ }
+
+ HueSyncUpdateTaskResult updateInfo = new HueSyncUpdateTaskResult();
+
+ updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo();
+ updateInfo.hdmiStatus = this.connection.getHdmiInfo();
+ updateInfo.execution = this.connection.getExecutionInfo();
+
+ this.action.accept(updateInfo);
+ } catch (Exception e) {
+ this.logger.debug("{}", e.getMessage());
+ this.action.accept(null);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java
new file mode 100644
index 00000000000..7f44a863127
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResult.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.handler.tasks;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed;
+import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution;
+import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial contribution
+ */
+@NonNullByDefault
+public class HueSyncUpdateTaskResult {
+ public @Nullable HueSyncDeviceDetailed deviceStatus;
+ public @Nullable HueSyncHdmi hdmiStatus;
+ public @Nullable HueSyncExecution execution;
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java
new file mode 100644
index 00000000000..d6c2eebfd06
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.huesync.internal.i18n;
+
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.i18n.TranslationProvider;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+
+/**
+ *
+ * @author Patrik Gfeller - Initial Contribution
+ */
+@NonNullByDefault
+public class HueSyncLocalizer {
+ private static final Locale LOCALE = Locale.ENGLISH;
+ private static final BundleContext BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class)
+ .getBundleContext();
+ private static final ServiceReference SERVICE_REFERENCE = BUNDLE_CONTEXT
+ .getServiceReference(TranslationProvider.class);
+ private static final Bundle BUNDLE = BUNDLE_CONTEXT.getBundle();
+
+ public static String getResourceString(String key) {
+ String lookupKey = key.replace("@text/", "");
+
+ String missingKey = "Missing Translation: " + key;
+
+ String result = (BUNDLE_CONTEXT
+ .getService(SERVICE_REFERENCE) instanceof TranslationProvider translationProvider)
+ ? translationProvider.getText(BUNDLE, lookupKey, missingKey, LOCALE)
+ : missingKey;
+
+ return result == null ? missingKey : result;
+ }
+}
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 00000000000..b9afa2d7f4b
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,23 @@
+
+
+
+ binding
+ Hue HDMI Sync Box Binding
+ Binding for the Hue HDMI Sync Box.
+ local
+
+
+
+ mdns
+
+
+ mdnsServiceType
+ _huesync._tcp.local.
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml
new file mode 100644
index 00000000000..55c40173299
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Connection
+
+
+
+ network-address
+ Address
+ Network address of the HDMI Sync Box.
+ true
+
+
+
+ Port
+ Port of the HDMI Sync Box.
+ true
+ 443
+ true
+
+
+
+ Registration Id
+ The id of the API registration.
+ true
+
+
+ password
+ Access Token
+ To enable the binding to communicate with the device, a registration is required. Once the registration
+ process is completed, the acquired token will authorize the binding to interact with the device. After initial
+ discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3
+ seconds to grant the binding the required permissions.
+ true
+
+
+ Update Interval
+ Seconds between fetching values from the Hue Sync Box.
+ true
+ true
+ 10
+ Seconds
+
+
+
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties
new file mode 100644
index 00000000000..8b4a0f04aab
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties
@@ -0,0 +1,110 @@
+# add-on
+
+addon.huesync.name = Hue HDMI Sync Box Binding
+addon.huesync.description = Binding for the Hue HDMI Sync Box.
+
+# thing types
+
+thing-type.huesync.box.label = HDMI Sync Box
+thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to.
+
+# thing types config
+
+thing-type.config.box.thing.apiAccessToken.label = Access Token
+thing-type.config.box.thing.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions.
+thing-type.config.box.thing.group.connection.label = Connection
+thing-type.config.box.thing.host.label = Address
+thing-type.config.box.thing.host.description = Network address of the HDMI Sync Box.
+thing-type.config.box.thing.port.label = Port
+thing-type.config.box.thing.port.description = Port of the HDMI Sync Box.
+thing-type.config.box.thing.registrationId.label = Registration Id
+thing-type.config.box.thing.registrationId.description = The id of the API registration.
+thing-type.config.box.thing.statusUpdateInterval.label = Update Interval
+thing-type.config.box.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box.
+
+# channel group types
+
+channel-group-type.huesync.device-commands.label = Commands
+channel-group-type.huesync.device-commands.description = Commands are used to control the real-time behavior of the hue sync box. These commands allow you to influence how the lights react to your entertainment.
+channel-group-type.huesync.device-firmware.label = Firmware
+channel-group-type.huesync.device-firmware.description = Information about the installed device firmware and available updates.
+channel-group-type.huesync.device-hdmi-connection-in.label = HDMI Input
+channel-group-type.huesync.device-hdmi-connection-in.description = HDMI connection
+channel-group-type.huesync.device-hdmi-connection-out.label = HDMI Output
+channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connection
+
+# channel types
+
+channel-type.huesync.connection-last-sync-mode.label = Last Mode
+channel-type.huesync.connection-last-sync-mode.description = Last sync mode used for this channel
+channel-type.huesync.connection-last-sync-mode.command.option.video = Video
+channel-type.huesync.connection-last-sync-mode.command.option.game = Game
+channel-type.huesync.connection-last-sync-mode.command.option.music = Music
+channel-type.huesync.connection-name.label = HDMI Name
+channel-type.huesync.connection-name.description = Friendly name of the HDMI connection
+channel-type.huesync.connection-status.label = HDMI Status
+channel-type.huesync.connection-status.description = Status of the HDMI input
+channel-type.huesync.connection-status.command.option.unplugged = Unplugged
+channel-type.huesync.connection-status.command.option.plugged = Plugged
+channel-type.huesync.connection-status.command.option.linked = Linked
+channel-type.huesync.connection-status.command.option.unknown = Unknown
+channel-type.huesync.connection-type.label = HDMI Type
+channel-type.huesync.connection-type.description = Type of the connected HDMI device
+channel-type.huesync.connection-type.command.option.generic = Generic
+channel-type.huesync.connection-type.command.option.video = Video
+channel-type.huesync.connection-type.command.option.game = Game
+channel-type.huesync.connection-type.command.option.music = Music
+channel-type.huesync.connection-type.command.option.xbox = XBox
+channel-type.huesync.connection-type.command.option.playstation = PlayStation
+channel-type.huesync.connection-type.command.option.nintendoswitch = Nintendo Switch
+channel-type.huesync.connection-type.command.option.phone = Phone
+channel-type.huesync.connection-type.command.option.desktop = Desktop
+channel-type.huesync.connection-type.command.option.laptop = Laptop
+channel-type.huesync.connection-type.command.option.appletv = Apple TV
+channel-type.huesync.connection-type.command.option.roku = Roku
+channel-type.huesync.connection-type.command.option.shield = Nvidia Shield
+channel-type.huesync.connection-type.command.option.chromecast = Chromecast
+channel-type.huesync.connection-type.command.option.firetv = Amazon Fire TV
+channel-type.huesync.connection-type.command.option.diskplayer = Disk Player
+channel-type.huesync.connection-type.command.option.settopbox = Set-top box
+channel-type.huesync.connection-type.command.option.satellite = Satellite
+channel-type.huesync.connection-type.command.option.avreceiver = AV receiver
+channel-type.huesync.connection-type.command.option.soundbar = Soundbar
+channel-type.huesync.connection-type.command.option.hdmiswitch = HDMI switch
+channel-type.huesync.device-info-firmware-available.label = Latest Firmware
+channel-type.huesync.device-info-firmware-available.description = Latest available firmware version
+channel-type.huesync.device-info-firmware.label = Firmware
+channel-type.huesync.device-info-firmware.description = Installed firmware version
+channel-type.huesync.execution-brightness.label = Brightness
+channel-type.huesync.execution-brightness.description = 0 ... 200
0 = max reduction 100 = no brightness reduction/boost compared to input 200 = max boost
+channel-type.huesync.execution-hdmi-active.label = HDMI Active
+channel-type.huesync.execution-hdmi-active.description = OFF in case of powersave mode and ON in case of passthrough , video , game or music mode.
When changed from OFF to ON , it will set passthrough mode. When changed from ON to OFF , will set powersave mode.
+channel-type.huesync.execution-hdmi-source.label = HDMI Input
+channel-type.huesync.execution-hdmi-source.description =
input1 input2 input3 input4
+channel-type.huesync.execution-mode.label = Mode
+channel-type.huesync.execution-mode.description =
"Video": Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience.
"Music": Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.
"Game": Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.
"Passthrough" "Powersave"
+channel-type.huesync.execution-mode.command.option.powersave = Powersave
+channel-type.huesync.execution-mode.command.option.passthrough = Passthrough
+channel-type.huesync.execution-mode.command.option.video = Video
+channel-type.huesync.execution-mode.command.option.game = Game
+channel-type.huesync.execution-mode.command.option.music = Music
+channel-type.huesync.execution-sync-active.label = Synchronization Active
+channel-type.huesync.execution-sync-active.description = OFF in case of powersave or passthrough mode, and ON in case of video , game or music mode.
When changed from OFF to ON , it will start syncing in last used mode for current source. When changed from ON to OFF , will set passthrough mode.
+
+# *** exceptions ***
+
+exception.generic.connection = "Unable to connect to device."
+
+# api & connection exceptions
+
+api.minimal-version = Only devices with API level >= 7 are supported
+api.communication-problem = Communication problem with the device
+connection.invalid-login = Invalid or missing credentials
+
+# registration
+
+thing.config.huesync.box.registration = Device registration pending. Please press the HDMI Sync Box device button for 3 seconds.
+
+# logger (to keep text in sync with on-screen messages, log messages will always be in locale.english)
+
+logger.initialization-problem = Unable to initialize handler for {} ({}): {}
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml
new file mode 100644
index 00000000000..0f423c075b6
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml
@@ -0,0 +1,225 @@
+
+
+
+
+ String
+ Firmware
+ Installed firmware version
+ text
+
+
+
+
+ String
+ Latest Firmware
+ Latest available firmware version
+ text
+
+
+
+
+ String
+ HDMI Name
+ Friendly name of the HDMI connection
+ text
+
+
+
+
+ String
+ HDMI Status
+ Status of the HDMI input
+ status
+
+
+
+ Unplugged
+ Plugged
+ Linked
+ Unknown
+
+
+
+
+
+ String
+ HDMI Type
+ Type of the connected HDMI device
+ text
+
+
+
+ Generic
+ Video
+ Game
+ Music
+ XBox
+ PlayStation
+ Nintendo Switch
+ Phone
+ Desktop
+ Laptop
+ Apple TV
+ Roku
+ Nvidia Shield
+ Chromecast
+ Amazon Fire TV
+ Disk Player
+ Set-top box
+ Satellite
+ AV receiver
+ Soundbar
+ HDMI switch
+
+
+
+
+
+ String
+ Last Mode
+ Last sync mode used for this channel
+ text
+
+
+
+ Video
+ Game
+ Music
+
+
+
+
+
+ String
+ Mode
+
+
+
+
+ "Video":
+
+ Analyzes the on-screen visuals, translating colors and brightness into corresponding light
+ effects for an immersive movie-watching experience.
+
+
+
+ "Music":
+
+ Analyzes the rhythm and beat of your music, creating
+ dynamic light along to your tunes.
+
+
+
+ "Game":
+
+ Reacts to the action on your screen, intensifying the in-game atmosphere
+ with bursts of light that correspond to explosions, gunfire, and other gameplay events.
+
+ "Passthrough"
+ "Powersave"
+
+
+ ]]>
+
+ text
+
+
+ Powersave
+ Passthrough
+ Video
+ Game
+ Music
+
+
+
+
+
+ Switch
+ Synchronization Active
+
+
+ OFF in case of powersave or passthrough mode, and ON in case of video , game or music mode.
+
+
+ When changed from OFF to ON , it will start syncing in last used mode for current source.
+ When changed from ON to OFF , will set passthrough mode.
+
+ ]]>
+
+ switch
+
+
+
+ Switch
+ HDMI Active
+
+
+ OFF in case of powersave mode and ON in case of passthrough , video , game or music mode.
+
+
+ When changed from OFF to ON , it will set passthrough mode.
+ When changed from ON to OFF , will set powersave mode.
+
+ ]]>
+
+ switch
+
+
+
+ String
+ HDMI Input
+
+
+
+ input1
+ input2
+ input3
+ input4
+
+
+ ]]>
+
+ receiver
+
+
+
+
+
+
+
+
+
+
+
+
+ Number:Dimensionless
+ Brightness
+
+
+ 0 ... 200
+
+ 0 = max reduction
+ 100 = no brightness reduction/boost compared to input
+ 200 = max boost
+
+
+ ]]>
+
+ slider
+
+
+
+
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 00000000000..043dc019c8f
--- /dev/null
+++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,84 @@
+
+
+
+
+ HDMI Sync Box
+
+ Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI
+ inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful
+ smart light that responds to and reflects the content you watch or listen to.
+
+
+ receiver
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Philips
+
+
+ host
+
+
+
+
+
+ Firmware
+ Information about the installed device firmware and available updates.
+ text
+
+
+
+
+
+
+ HDMI Input
+ HDMI connection
+ settings
+
+
+
+
+
+
+
+
+ HDMI Output
+ HDMI connection
+ settings
+
+
+
+
+
+
+
+
+ Commands
+ Commands are used to control the real-time behavior of the hue sync box. These
+ commands allow you to
+ influence how the lights react to your entertainment.
+ settings
+
+
+
+
+
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 61ebb03ef13..df87fecff26 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -195,6 +195,7 @@
org.openhab.binding.hpprinter
org.openhab.binding.http
org.openhab.binding.hue
+ org.openhab.binding.huesync
org.openhab.binding.hydrawise
org.openhab.binding.hyperion
org.openhab.binding.iammeter