From 95f7f9af191b0d7fb12e5aedef2d689ef17c18e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 5 Nov 2024 09:03:07 +0000 Subject: [PATCH] Deployed 7c378069e to docs-develop with MkDocs 1.5.3 and mike 1.1.2 --- .../core-services/images/position-service.png | Bin 250156 -> 186022 bytes .../core-services/position-service/index.html | 13 +++++++++++++ docs-develop/search/search_index.json | 2 +- docs-develop/sitemap.xml.gz | Bin 127 -> 127 bytes 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs-develop/core-services/images/position-service.png b/docs-develop/core-services/images/position-service.png index aeca82e58dba3acc78a7e2bd0583265e6a2b1f8e..6b8a0839e2c77a2e18fc67966894d604846a92c7 100644 GIT binary patch literal 186022 zcmd42byOUE6D~+XfZ)Mhf(Ccj1Shz=yAAGxgy0_B-QC@S1$TFM1{($#(lY-(=K2{{8rVGznM3kDntZZ$_IKVmr|Omq41bpDy@c-Tb?_oy7m{P>ztsr2X$X zio&Rz{!e19&8bk#B>|LN0J#3g9DLuV>ke|*t9IgagIcl=XRwkQ%MS4>1@Am<39`)tRVcy z;sI0DdI2X~9b)>m9hDU{JX(_U0kas8;ACR>XS-RET*6Ibd&RwEX3l#CDQbq>?=&V_ ztQLIsTj)m$PRWxBjrKdTb0KJg$AXvvg=3vBLk@=yF*I^D{rz)EG+@RV!=Ay-CIo!& z40S%KWShr~N?kF7fk8EAh4rnpEF&6`rHdYK$EnhZcQqNJY2SX;xZs=7*i3GLWWp#X zU{6-;NT7yr1+rlC{6w-1dY|GG7p13HmvnTbXEuc>q@kW^wZ4w1$sn{7+W%;LYOyH- zWgYEW6WjWqwz(O4>|WM3*9?Z%nTt<3)_L9A{H`yKkc4QqV_Nm5egyOTc5K_oL>Gs%R=9Z*zO>ks=b#MRF;vfBqp|+E@#HdsgNnCU+2oK9~>1SXy#scUg?Kk zgS^5P0m37+bROu`s`$J;d%P}-5{@^rge_eBfJl*a$rH;N3hLG?tV#h+9n(L&R?DIldwJ6!8L!j=qD z+_s_MZSTN*)aV&0Dn>KONM7U zV|O^tx_`%xx%_&@cKGI#V5X$GH#Xfze8njpvKemcCV2~XnD3Vqq4g|3-R}h7faz(b5PAfo2YDa zJERx!uT4YrnVX0_X*|Zp2lHsN?|sM^n8pYggks4rG-dAhUC=WU>daF>lA06@Y6=djyqQ}Gd~ z<#WwHz5%>~ggy^df~hZK_Y2=2C`U6pc##UiVk0s_t&r!I20xjf{EY2EPMS4cd3iw_ zdifMfsx*Y}=V^)1@Y*Kx4F^5@1QepX>*nWgIowZjnPRMr+G8_#f`7Pz#Y<84rz1B6~C3e--+FWeB@7DMAsqZ=TT%+t8 zkBE+(^C-r4fB2{N(>`h1~!h{SA1_-_^;{Vx^!yVZg42x~6T2QOhCN}r6% zs&S~I%{ZLJvnz6>%|Y3jURuB*VU!MRC}haIYI64!$9-$s$ljS|nx*HAT~dK%<990D z60iGQh@_vo^`U15)*G?O?iwC%(Brq^ct8t>yUYi?xv@EGQTz0i??y|p0{CtGb){SN z|4Ff)A4re0TIZ*g`~++RC9TWWT8Sg5G#F9$BKqF1!H^{@mPS#=oxZRDMZ&fZpY|Am z<*<~EY{czGzlYSKcL0sWDbz-lBSYp(;gv@6Hp1rW4huioQfg`sT=|pLg*?sG-0T7x zUD{Gq%kUoWk96^)`O_7TfAf%3!BFOjI2bJh%6>8mPZ5AOJ*e%2i(%dLEefu_IYDELG%dm34ZhM8uR9M zIG$bmenuxD)bwoqP|%3eq0Mq2lf0R_aio!}c-;oI4;UI>_lyL<>%-Ct2Gokg)X_-> zC4GlI^jk@#(IMX<#u_3$*dBE8ZD=9AU8Qb#Q$t#fs&545k*E@-B}5x2{;XfymJa4@ zwYAIlu{XFN9y`2{Szpx1T??~tg_ol`(y1)1d-UbIy^*X6)T*NGk*f(rhIhAl-H*1H zWF`%x$f-T2Ttxs^-N{0ndxL-xfk7>j;$lbnrN!rqxfkM#597ojb1B*Il#7b zVY7|`U(uIy`?9z3I;Pnl2+!^%9k<0%N`B9_0{3vCnq=Dp_Zgj!@-FYyQHGhVrwQ## zkBUFvTd?OAS)kgoGgDzXtD$o>0M-aHe69+yw86Hs%}6+}h_nYvR6b=<{-^5AnM%jA z4AUbmjB&uw%b2S@9+`KIZslfKk?503LTG*= z4;L(3mK*FQxCJf9Yjm~+_AvDoj%>RpX0J)2CNEB+kZ%9rk~tSiey^ppSyIjy2%A`d z5C|4q_W0AgvUfc_nXQxLiqCSq@_fBD(Bxj^28*ZDSu<5!^SKBFioUkXMsv~z$NN6HNidD42LA1pXfkGR<|lFNkxggdpZ*<2{!b$@ct0NM zq~N8dir(QSR#->wXt38I(Q7nGNzr2r=Sv^!3Ni3B)w`5@KRf5Gb2tczb>n=K+%h`j zvf9laxCD+^CE_%}t~w)=CKB?BzGa=FRHu+_s&-h z5cF`lTWi7M6d)8k+)8HBX)xCTzwf~U$~iAa<&{UFL7o-}SS#6JD*_OOWJV5mlFUgO zmh!(w2356nfk)sukFQ8((k;PKj|WN=Qrxu8X18A5T&Jak(}`&;K1x*q{Mh0NDvitI z-rCl64RaPODSJ6>iEg>}N~Wvu@9@JreA9Be`6gK`QGc=sjV+Qc)He$~#a`S&DIvpd zJSt%yY|LKm=`F>o`J5{&u%@5Bta;GXg91pSU({cPECaccj#q~wp7+-gh(UG33|--_ zy+$~F>pK|Z6PwPQ>1qxVu)hWn|LGB*X%p?-Wry2++o^oqM>`X%P|`>w>$*x&{~DNNcU(!(Qe5`4cLxA|oq6S!2A6RLDTaT+y| zGM8!&$A-iTaqA6}|o^~>Huz;<~WAknS2!pByUSf?LBim{a-A&S1Bzwd)i zLX1VfE*t9gW`+LU&t@ zPIP{O{TC6iMB^1y@)2uuJM|Cu$qzR0q0K~;m<~>NuEbJ``lc@;Gd}?WAJS1ISrnx5hHn(|mt@jj4>h~FrgyZ7AE*b!&XA)AY`&JhT zq2uOClXsaqDcq%cXQf&lf8_u1&&SWC^`X$jNH=PoMGnRLPwkZHKgUz%(0;m*%Lgg! z$wktL&g1y$WBeYM4`0@BD$?)8>b!|S7qiJ=wNVKV8}a|!E)lOK+KlQz^v48uWWU?8 zY7Nxd8y-^8;3IC3NEE_HHR)S>#gd`*=+dwGZT3eE(x&q9B#QBoE`E<~8=7PwDacm-b8p6%3N7~V({hlW)ebp8_XCUALFcjZ!X(^Y!egdUPVlo z;y^C1ZEC4|*Zl981$_xNYnh4#_5RMU7}s{Qdmzca1OL})ObL*E*kjNMnJFp9=9|Ay zZ?5IhDKhbb;odPFPdcBibZmsl7#uR=sOIk)-2IdyKK{h7!}d{mw5hxT=>ffqC?mR| z>|6wVtWiQDG|qODG}$7jvg$z2j{9_4rzo4oi)QZ)6A^o%1NQCIhS6&wNv6F;UobF!oz=$aVkYjL z`PaF6VOSff>;7P5IUmYsQAJc`sT!xNU*WR)uv(Yangfep(sa5Au^NFQ85D}QyJv_G z3w7J0@wS;Vuxmrno^7aKwr?gIDtT*+Rkc2loGly0C;sUsIr*EjtmeuYZF}K`tyR`^$_J@?$hn_89#op$RLmBRL<`Y75E>;OJUZ!eV&RvF46W zQ$aeAYQ@_NTHbolCozkG-80vWxU6_Rg~q&tedY%rY?V|uoOW;RI-nE^6fGdSnQ)^M zsnzyaE3$86%GpYu9-(`A;Aiszt3lMhQ9gV|J1YJT91?m6Lbmz1VR)sN4}(+pL2Vc5 zNOC`&mKtCnb8!+Pf|uA{Oje z8t7Q9a;y?P%J*i>{Qex>6l{Nc>qdHUb~`>jq%BgKm15cvBG_OmA`*JqMu<7A4toAY z!PNBRzV96E1ax2FXvdHih*H(G)I1mq4QF8-2@yzeX3U8X;Jw6o= zS|p7+H%yRB;~5+Eg0cceF}igy`ns!*_{!#XHVe_FP;JLWOMZ;KyB(l^x5=;ZlID@W&v-_19!y_D;pOZ{_i~I`Z49nkJ*N7tz*o6zX@k-4?5Y z*_mS=&}@F6y)X#hk>yXCfcio?Dz6?MQEX$y>#i{qo#Ew_0qzwT;Dw-7L3=qmkD(JsGy-cr2fi-nym`K2Azu>A(NRAd7pzB9*C2Vw%&_oXwLO_f~ z!OLU*vlFgegfS>Y>lQEq$8CC7%YV29#`hwn26r>2_Da!Dg{id*7T(mJa;sjN(73=;x>1cfbC(Iq|wC=F!0c`2RX6 z|Kq+w=D$$)Es^5CCC10k;&c=;|9=$!^^Ny>(tp?cQJK@3R^dPQ<;Tym7&5#6H8~$U zp6dT3jt~7cKrpv-?VXT4&i~4~`1zn8?B6Z;(rWAc7WMOb zPSv2Z@yq8ei2A;7$lv^>I1}4yQncaEc4)eIJ*nFL;2E_zQU4L(CEk-e^mH@pr7GM+y#9-9j&EP(*I;YK)z0 z_;JkZfMp%qVREH)_RD39PwkKI3EmO>tkXU}QfxUiq5U52y#Uj;8RC)s8w;>y_l;%D z#DDiU9t!aT7T)wev{`?{L%X@KaA9 zx7+&(`ea{V@dOKoU^ixhLXXUL*3upH!D9dH{EJBRRL{r!Q%d52*K_QcguvgY1b)2cdOn`e=>_>p6iB{* zht#?y_T1ii2QoNyl{KiVQpYs{-jw_56-JY@uHk{b2zN2I5!b>C`ckealHI=E1hTsH z6Ir@0OZ3#cpnGk>76?~u%C_7Dz)r(T+W%dccjWhXTek*1f9L%RV2zv6h+;HqC zH9S@&%3tIO!!e+b2YUpaTz!FL$*#jiR16-aG6fcg{)BTLH_*EY!5OZG{y*0SGSqgS zSt*v5)8?m3oeS-l_qgFdK{~%n=7w*xFFl0G)Gqt56J>(17Ib)prrm^X2wf9xk4J*+ z)AHNey*c}Z+Z3`&hS==oOz8!R<9(g*#=0L9H4%odPs;JE42C-GjIU4RC=g0Sh7o9k zydJa48f#1=<()r-`ejT?VGSpA`_i$@1Ah`h{&C8$Uzjs$Rf<6o8t4bTmMSIMd^_sg z+KR@DDPPql-uUOmOv8Eh{8xG#B$d)RW5zGWEZJ1G@49)ok*0O_!m>rk?3-R2i zeR^mj>V&^|VXf(K69ES3CH4Hv^Bj=A%TCQ z9TGZQ5yWFq1w4M<7w_J58hf@lysBd=2Fsm2a1$|laPZW^);tsYFHP6~82$J{9~ZE+ zRA+k08-jlRHMG}Ava2UZ09JqULLj2-L}>_asjn(i2o24F^O8FlUDz*N0kr|Nfj7^c z*AR3pXpK`*;+hu((e=IaWq=3ze%90to;Xub0vWbiX=T}lMX+aDfDxu6)S3NA| z6GafavQ~tPS-JfMBeK74Hi9ojL6#*AG@L=t_^N&u-x~VHUu`iGaxT9*8X>d`b7gHY zTbMOjrYJN#n6F5;Hxcx%oAQlb#&x|O3{Q+1W)ex4bXT#KK9V9M8V>{gPi%N@MeEUkvXc7MlWHx6 z^^s>Dc+B&q_bec^J3l{MqMMcCPR`?6Hx5XGdii4SiX?(LNvR4~1IV=^HL3HoDesxR z2t5m^GtKSDO7@y<3PlRL?r8Rbe*fT_Iatr7ioae$n+5xWfY*uOb6MJ&;s*hXSToUF zKFRmq^7|PR*V^{?b4DzZERR71u)c`+P2rp%#9oxb1utVCb7l3krdEKuw_b{; z_fX*=%3gcBS!u8?O~!s=2B=Gm9JX>i{159SdvM@3k6r(lEe?FBoyZSZfoYEi-}}J~ zG7hhn4cFKDw#g~1zjxkEjGox#-@K*s#18wu%ld37(kaKma&Fkq)ytj?MoPp+ap_v1 z{xr2uxcdq)A#irtRP*g-WKfQ1H7lm7Ykc^bM+3u~h+XdN@)UB5_Qs&xlQZ1)bn|?l zmDx{sTR|)8E%Nol|KtL+n`3^C9KKHM;W4-n;MgrVug3}3)8Y%?5qi14W`kH8zGApd z8AtP=PwgBC`yF~mr}oEqnqERc7`H=0swW40XKV}x@{YzrM&AOOeVR`$#D_)&jX zM7JTLGP(Nu6MAvrLRS&to4#-L5(kQZxn{;w*|u>`xjbG%k}$PgmEWK)9LkI8AVZNH zPjdOSJ+#b&XI;s=C{frs>#0=AhwBnb^q6eA&S?O*m{ZMt05pBmEqB=nKV9-HQX7ES zsEv`HRweDo)+EYyG|P6)RV5MDO?OU~8Y0lYT^g1b%*dw6A)ekm@yrJ-l9f-K$d-}R zl#@?s+mxutX601DFJ+ec$xJd*9{B_(VsMva*sQq6r9VQLiV{_d&u-_xOp)3w8sC=x zPPtd^zMv^5L*MAM%)njbE;)q%k`wUt^u=(htd%z?;>j%+>9kQRt84C_UX96j_ItFJ zr;Jxej1<*!0}AiqgLMxXmOTp(~vY}%QgUIXntAq ze;~~jwaZjRJk;ecpcc)(m&39ywnH%4t3}-D@k`#Sw=SLAt0`CMHGUHneFLgmI?`in z)+xJlL4+#a{I7RR^ADiiB;~2vr!KX8~5ze94{=XQLEtw zs|pQIs0#EasO7(IZjFi1Yh3ttBp_Y#C z1Z8(2;%+xkOSR!cK*hvK@bH7aduO??j(0^K2U^A&_#T7HBR7TcxexAd5MB2Ol{^D! z%92ukC53>uR_TnI)Q28!uy- zFHIJjmx6-q=5@BhPtIP0OL-9&9P^(NN4C95XAky|Vc>BIxY zrsJu#swVwuDXW>zZ0+{3I)c!j`a(B~Uh;*zvW<|RN3dc8bS_2^BC=31u~u5m!l+uJ zGG5nZnhpYzpv%+C+xtzZf_)(SV&N%2o}zY1GuWN)HfWNRw*3-rwURXRhBLlBm3Hj@ zPVGRwmDT)wT48Xpu$|_!&?FudA=D-sZMP=V^{6SE2drLwOFms*HH?0k@9ZH!mHLmT zEqk~jXz<|B^i@`9Ci&f5@44{yzT85KwLkd!P?ui)pXeVhB@MJ!t67>rCQI8?aPxBV ziSfQvZ+Ouuv-UsisVT1LvT+w>;Bp-@{wEq=57EHdImxFhD`{-$C0kSKDVW)3`!Op& zpf?@&S)DiY9Civk3%`4M(w|;ra1wqM@1ptUW;-P7-%m`ilsUXhm_MBx87-VuTMty6 zZ`gdfjjf6zIo*hJy!(*5;z0a3M&EvLGU~ZA8ZPat4N)h%5nM+4Wkmf5ZBFhNi+UcWNZ9^~WGQx(UD^VjBs-^D z{>~$h){@|g5UyMopI;b4Ssx8e4J=i@Vd84UEGKhp$yaW=(TqX5Bt+TZ6LB@*kDW?C ziGz%8VK7Y(7Ckk(`@qPD-{~(E{YU37ou1igKIKdXrLi8U`e`+VMuxALV=r=ZBUMIS zoN(l#;m~XCtmk1hQE=CBxlnw(SjajTTEpBjsBSyW?h}4B0El(u1zGFNAR(mOM?Gy* zO1jokXI(tkt)@4dW)IG-2%-ilrCDK=lWdH6IQv;CwU{Q;-Y=uIEsZ0X)`L1P5h zURvHrdMPy#;cm5Hu=r{oxb!E!(TTmLX9QX7dUAOnSzj`$$!Ayp?2{DtMjPy>-!ajI zgdX-8XMA6ix?_c)=T^}1gn4>o!j57T?{dDyG|kJ#yozPtdRSOP`=A6gPIIEG{nE*0 z&}wh<{_bEem;pnn0Jq6L0++~R1SBV)Rr$ z&_T}y$h%NywcqCjPZ)fA<;oXoj)^w5KY1x9yG&aXxO$w_vft`${y9u`iv$}UZk@Qm zXIunpw|?bg%0z?zD_s8+fl@J(q|vw;I$epD*N9wpQw)@allb(1B! zFbz12cbLVn5=Ay>E;7ps) zf+}SzvASo}(J+5ELEse6FvfJ^-_q^!Qv70dg0Hs@O04nr`0ho}ZpMnZbcHE^KV&Ym z!~1B?T8r~|)MbL+_$U_^Qs)bunvz3w9$ft<^+?-3S3TrLp=hSaksApJ0OhV~!KM$% zmzk>wV$2UdKO3(ca+W2??I(R?k5~a=w_kX5Gcd^O#P8J<0>9_UEpmeR0{*%e9zmzM z{7p1A6rJ7x>69bF$8J<6NGu$Z;X?vIx+u6A2(FH6ARU`_v*SPNPcU8Ewg5;3hfG~B z(qf(H=;N#&y+3x~qIN9w@^7fMksTW%%xA_GR#jb|%%Yn2i7*&G!;X;{OX>U zGnVXQ*i?whQoTODzLc?72zz$Z?RxFBKl<1ZtkA4H>ynj;bT$^Ls0gRe6M|wJrm9VX z#?&!0xuDk^u619%8LSGayQ>03!jl~6c8k7oAMrKvpI38P$J*~pn<2EL+lB?<659y& ze=-cMgdj&ee(XH?l*`+FV6>Pu-=A@Bx>ASCbW3bn^ZG(-da%ANy)OPtm^)&`1=r@o%43Pdu%yO6JMI-}PP~W32N-nDx6hC%^={k!RuzrO8tb z^etnrL!3Eoax0vuQ)XiNtE?*&1EoJ8ZKkr*~9y@h;O)tb>JE+{G;Cs_HUmyT|f0P z2M9xNlV>o*X*#yGg`fg;6*$t4OiD9rt;tSVadifjlMNG9fB`XKMJ}t^tTHWuj%zuE zNws+r3Sk%Q*kE&<@{Y8(s_5u#?_UB{ww5VXb)~ZAOKX{3=~2n>u9y42BiGy$SW5+B z@-mhaw)<;)>)S>fTXDpGZU!&%Ss>R|>V(p#o5B@yB!-Do7GI3B_F;KfA z2w-u&Vq7&-?##DSH0x3RM2dM3kYW{K=pSx=FLeu&`Gf@dpgW%*zgwKt`Mu5( zl*;)AY;QZ#M7GU5Yc+2nVpenFc4nnXQ@h|&AwE-KfTjkGY-Ejc)3!R1*a#n{w)BSQ z)=kyFE_`-9S&VWQzOXvl3f^u`LINIE)uz64LE&Fw^r=yYF;ePS20DKz$k`n70Ch`c z;DE67#${BUhuB4DqvaVNoT&w-w!~}Rp@bKfk)pfe*3&bu^yRuPYGsHfD0@bkdO+G6 z&C8YehQ<076ed%5t1;l_WsvtkHAmObyzf+IR-&e&HEzYtJCn1Eyz13ydr;Z^0kWLy z3q#trbQ^;&l(;r&cX4Ti+FAm}Dca}>NipmvXo()9K*njaRA}K3Xx2K?hP$)B=3scw zuY0D)CLaaD0@wrk11s3EUnJl%_B{^cW_Nv;SoO{P7w=Bb(GCxiv2EqN;LtWS1yaecbA3t zQXpH=OYKb+N>gXx5{v`s<$Bu(Wu@`qFC-HJP!kSso(_)ZtA^#_X8p;=m%>N=m+$)r zAJvNJwU6HGtXpmN6UQNt_P(;bdI&XAp^4(u-a9jin7#=1P$(K1*&~_V)nHl0KF*d_ zXL8_>Ao-6jQ1tsUNT>>j(T%;}yZdtJx?fsApD`+R1Va>4M85Y^Eaug+M~qKK54}2Y z-h(Dwu(0^9ltngEyE7HKSzzE=CM0?j_f`y<99)leQY6MaoUHXY+yR~6UYOst!g$p< zogg2hFtXdQnLQ%WaDLh4#;T>=GkZP!(RZIOC+s%6qCvDQ`%4Oj9H5KC;LT_TMvL5SVffsrJcd2onBvH01f1W{ z+red>R+cU(bW$eaot4XT7G*NW@^HQ~2j9x&_ijVe?vB36rQ>@O#G?Y{kRPUZGDU!F>9>`a&+#dC`sbQP zgb!NU>7ph7<%hbq8{U#yz~gSt77? ze-PPII(NyElQoXAj>oRqE?G6WeuhXpej6zn7|JOlIb!OWFW?^EZk{e0elen=x5 zwpNb029!Lm*CXq*>xOH*aPIkwZA^9o9T{D;t2;}Nmy=`1`;w>~72=sA!nVIQXqQfO`nm*%ou^slGrhX<0fw3HrN?R!{wPT6=PwODhPh-ALFJe`(Pxa74i2=7jRNx#04FyWZW9rc|5 z{h2axj4!8pGVH)&J&Qe(6n4>1$V$~upoghF4p`KhNkrOYyRF;+uU_Rs)`lFZSY35= zJ94|64f$PAjSIH5mQj#xAI3T1%x9J|dnuCBY$Mgg9UgU0EZb2CCR+M9#zw5^!W1RF zUTVQe`Hg|PDdfCY%&TY$`W}^ zRVXvOoz&$c{n7G)9i?!ytwA28j;9HtL$Z2YKpa^vmXVi6o8Q|smm<9=m_Ak%Jme`H zRkjp9Ugx{sgwIIdz9)`<6hmOW41~#^JcP(uPJOa*0@)*?BM#PE1G394W(T{ z2{6rHVr>5mT(7e0Es4D^caIY4=iYeJmWhSg)EoamX<}pSXoVcA$LNsZ-tNTt z((e{V|EGIPSgt_V&HUV9i(-kVW|4&u-=uUZmbhnNrW zaCvV@k=$Szfm@4SFHfdsXn>8YRum$|uc@d%?j|T79x(=a&?&CxXm7=@E;8$Jzupb5 zM3xR7?8F5;2hH&|gUNlGNeAdlkqsVwsXbOX1aAS980UshSh_5N>o11SZxF3typU~L zFNz(WZs^YYa7k8i&~61=mzVuy@NW^{8aH>)*D9ilw4Qq;Yth{Wb4Hy6@hn;al_c@z z&XsWEn(DoArd!sahdIkI&epxtbW4dv^IAqCvH4)^*|`V!RI@UUz|PhwufWmsL{W0!BO2bEPGg>n=3upiVZ*IA zdw6_D>f20*9dBMAslWGq^I>nxRxKR`Y!PN#?IB5sTh8R_dq3sdhgHA|o^7_V!^>>d zF_CMbys)|MuA(FQVBg@DGb8+;u!HR-k@~521d_E3#ndLwTro>2w3^rS7ft@{t&>YE z>u7bN&b6T%Q2j!t0%yy_QiWo?&d{?20nt5q>#Nh^xR%tiJ^2AehA`<_(?fHO|wpv;vMB57am&+ z5al8a{o|MKxDHZ#YefDXCUzjPh({E6R$EaI8b6;=JrNT=AAU?Ra~^seXY=MSY^r*= z&D~KM9GOTyTRTBshZi&6^8*t8Kl?4m@ibM$lIxhyNn5aB61oH&%&VO&{IQW><9!aV z7QWh!mg4VXvipyF45D=FVfjtDC5nG#^Q$?couv<{w|+&dCJ^0N9hhyx;Du`{5}s76 zG}+ht^Ug#r4!FygR$WY1&aQh5Z=gQT zPb!FOk=uK>MlwFp*OXp7|B*mF?rM*|UMGWyazP)Nq>2Ls4 zx$Hw!gvr<7`Z+_TVfazK>>`HWC$7Kj5OjL;YYVZi6+)Mp6v?q@Eyd^h8CHT(3UA89 zR91p9-*jSRrJsLkq+kH(^$t8f_MW!W2kPvzw8XgXj~pEM&*PV(H&zQ!TZbYCH@GNm z%$=K2P(rRfur(o4yN#(s9y!trm0#z><1goP-Q-KhJo(t?;a0Va^`P{bF|F3!_jPnm zdU_m@=soLFKgBTaD~{!Tp_@}H9JxsG{iZUzoYg^|kTcr46~PcALl%BPu9UfK!0pcW zCXjy?bARf!I(gYx$XwrW=p-eN(K$zkM`&w)Utj5J#dE#)%Y)Zs1q z#9k?=#J;h0XRC_7@eKex2}*u=nmz~y zTOkOFsbYt?x=`@mWW-)^0H>~)nXXO+lR*x5#eQmyeMpg7PJkB*&aaC>%@l&zlD0 zp+X?8yZ&?=;A*}h@6VX-EH8&Lt}KyMIToqgd$gBReVqZnUKkOwRX3l|4Kyq-%Mh}I zh`KIxhC`<7y}^}!$4##A*H?qayUVFWSX9sJ$Q@4l{rA4grp2wUq%5qr`ijiVgj3`4 zEo@#W7SGZ|&0YB7u_f?qUR|`X?Z?v<&r`UJqpl|NiN)f~$MOO_uW~x7!CxC_W(zhQ zaNW};af1S{^cfy}JoaBS>tWyqAG#E(M+*@!&zqu0fSuM$V9FRx|DZBc+k(Mphnh~& zs>p$Zu9 z6d92+Y4vx=*vL8DVyLbF+zS0#<3LrNx@M61z#y}wvYxQov6_bu?1m?aZU+wtyAs0k z`~N*hZRpc1vm*rq0DULO1G?vAi^dXTAhj>{_t^-& zwV}cEHJ!-8d*SU{%`86tF7ykX$2m~d3vVnmDg}6XIn~&%(0Tuwpml+-fVX8NIk||% z+6k9Tb8BBqG3Yx+&ObnQs3%F@2YIspL*MTSnj?>52$`!8LzjHZJr+C@3u(p9i_(>P zusqOEC|Wn&*!54BqyvAEVu*Z0hdcGll-l9Kd(xB-Wbf+|J^%+ z{FV0NFMESUoq2}C^_!r=o9Zx8&^y<|c$f(BS3=P6lmo+~*UgLJHtx6QH?Y>_zeL(F zxs;ymG>srH4^?by8rXOQyxM0`bwc<>&vh=WZ_iRf^DFFsUAQTD$ioG%h)yhZP)M9A7*PiBLvhu;K7Mn;xb zR)j8Xo{ttvmG_)9zy8ZmGy27XZRj)^31SF`P3_@+@)LOyF?JNywl4SkA8|BuD{-Tvp$w*thV499IVPM_FK&vkW- z|B$J6{Ua7J4UU)MPLs}Iq^DTxY{n#FvSWOX%{?GBF$GyMIZIjo$L2yG-_oe6CDncM zO;p>m(v`QJHTaP?Nmnux=TR(P9X})nig&-smI@OG+4bGl>$+JG)gq^OCO8Sz`~YU$sXX2rV&&-fD3EzD-|pzj6m)zq*<;>|F1z06_M>YjrRr70#yhb!*QA9J2ln_2 zEy5#Z1inzH^<9}Hj1H%Bv7snxGg-`j&W0xF@o@rylGjm?5GER~RqcxyOAq04a-G@A z6Eb?!WwHjL_mev9zAFkmso(D}|8eK5zhjNb*u%SM=Pqs+lQTVkf5AK)7)xf_tNwHj zk19%`{(%w^QrONa4V0%WkwRqFUoYWi}i`B)B^knp=&`6iHY-JRAluOcKb-tr^#7Z=r^@1t7{H0Rv z)vq00j5cdZ>J4s7ZKnQgqCRY|^&f+W5SC!l;6E(oXLfNNoL3PnKoD_Xab^2;aE{58 zu%hAlz14SqFAVt5R9SmdBFcnR_UvJyJlne^1@<*fYXz{UB~&^1Em-SxyeEtfso+u3 z>eyqi;{n5X^0*DQXj2oPSIWpKm>$KKR^}BQ5WC%-CCUZKf&8QRpW%Ob1uY$1;-*88dyA`op^_IbfzF534MQV#)j^BO-+AgW` z`aI&Ypxb;Snvy-|vlGs#(w+#syr|ey*$nxK4GUEms)Z_!d+j zPG2}fPOIJ3zF1~~bqaKFc!7_LVlvw>_c>vQ3eT;4DF;P?(e;?T0b!|YdL8jA32p~ok_%Cum4phuAw_g1YtKYU_ zTXn5}W=0P-C`iEA*)w@nOX<54cTcWu5$_L+OwLq?BTo?VZ-7_YxkfRj>yf@MW?8Ow zirKAuMCWasDb;nNW{^9w}#j+W8K#1wZk8CF?~{b9YKnk9yg6P4o3 zJs&PZxh$nwwpVHvqxE=+NEJsfP^zVhKBeToiQHcbbVkrS?LBy0cAa#^iGGZW`}97~ zV}3d^KY(0fusx!p+E#Z%-%V40$t}`$>80&70*8S-R~k#HJ>bK~xDS6(Rn(jI;&2j# zqRsF4Ke+(0k)q^DjHRNb#N%)@Bq#U$%6c#Vp_>X*TB{cN;=oLB);hei0% z@}L)?M`KOWH3;s_m3)`<6f-q-K`NR1fvUDOEoU!Q#lm(C1Vzw(1I8cXvSR-CT42N@ z|8@*`Sx%7u_97G(ekE|>*O6a;I=A@O7Zk$uacr;@XFvl3uO}CQ^!A}m#W9Ont|d5h zCnAsi>EdZ5arT5uA~nxbRmIG~C}F=-4`AN_sc* z?k}<43w4)tM{|BQ%-_npMEs zzZ%g9OkPWorD>dct#whi2#ckVTC<#WVvEfsY04pirhP-fSl%=GTytZAaq&;xHHQ2@ zXzKQjo=Y|LVNL=BlS@e*UopKt3evhb3}9m|`sER`L>=F}zUH*mrKxLE@T2hSm+V)d zxfJ=`fT#J<;UW@#wb`92ZuZpnQmuQXq-5Jv3e`I8Lql>#rq7FoEYAkz#aN39ndk(Q z5!tWAmrF6OGsxUrTc-41X(>A11bSs`%?8^}1$xWK0Zx+Tu<* z+Ynb-uJYSE+pW(Jt|l{&(3Fi)VvNiXO9kOMi>YEXCSo+Kz9uxIztgS1=s)>!3I#_E zh@2l%IU^oi!{^9;^Whcao8>&*{@t6j(aMtlMz9uPk>;_<*We=@V(Pg1en-Sz3X0`M zSnNT#G(^iX+bmyZ&*ng{FXV4@K+1MCDcT*Ocv~lNKYw!|61h*SP;8>gP6BIQJ_MJE z9nz3sxe~$-Z64J;zU=14lL~A-zH-kgY)IWte@w8Y#citd%*aSf9Kdb>pikW^-^O^I zvFwGXkLk6x^7i*1)g}@lj#x_<@Yo&8CkuJ=IZF%;bQl!kIZ-Z8=3zMb9gxCg2N3*L z>@V7eaU!`TXz3X33UuXr8$R+1)tJn+&!gNv?y zks1TrQ<%2mV{(+yAC9&Dd^Txvcx6-LrX!Bk z-8D7W7#9_3CBSR>F8cr{4lfoX+2D*|h$XL=%(J4~h<>(0v-uEq@g@5uKq(tg zkZm3=?_~>mEHF(Vc-T+(8`xN}iH@^VW$g2wVbf_tnI%HYMn>|}CJz-n22s%)Ga+di)HPb2xPe)Wf0#NGDlhbE0{mX@rvvZ``XW~ENg_eFqotfcVnYO0x zimrifwupT^$91y`tmgIKypLF>`qPFe7J6SGx87#pwwj)Z{HQATD5mZ74*oIj4_Cwb zz4Bs(|288c&3?l2M`P*NrJk+-8KI1fL6}tf-)3RBYGHq$NaRHK*y(R8dd_Zccv8zbj`d!k4n)hlC~?XiCTZ;u=6zg_>@KBE8q=gH8i z{2f(y@Z6|XUP(#G*)qXD6lRt7_xD>R0g9hsuO1$P>WaVqLmZEQfB?rU{~5Z`p&{G| z*=ioyza5MwcCB3>1q(;?d@&`nZf^Rg@z}~dUZf4H^1shu?GFF@d&869|E~=C?*sm? z>@3w}{_CfI)6MVIHnO;_Cmf zr++MYC{%b-UG9dI>uiCgc;2Z$+DFGjZu#bghw(T8;fu}MM+j$kYU%rH_PbeRpssBd zE8+g=6n5rn^w2{zR>&L}}pX(o-oIXx3$nVRC!NmIk6y>w{Jd@%}cu1}A>qHa4;##I#phNt6Y0V+9ZiAP)xM}EJEDMYe^|L3#*>crOZt{od^j+{f;NAukaoQ! z^ePr8B>V}~>(J-xZWe&1drROl#gXQVi_z!yBcrR4m6MZ((4}kVb{6`ksf9AE`FTTL zG6m*e?T^g@BoR1CIv0mSf5B)>Tvx-tL3hRzxv?#1Z^6)9z=beL2z%}Ort_CH8MV*L z%Y@FRB;_XaciHZ}XYF`Z=R@XStLiR~E-_#II1xvj3oq!YeA0L=Hn;MH8uxK`>rcqP znmCkq6wdci@s?H0E%6~0@WfSdTdw}2G5hn+1za>Eh0xavz+d=bO%{)nx@%Y_GUeO4 z+AC(ctW_1wvvyjXIQRg8pU{2vJ=&289hFUjJgzkZ;8w|Jc@CI!?e->stA5o+I2wkhXxlG357el+Ef_%Zn}b zy%xU%tKmKtujAcn9z|eW#??6s;_Ba4Z07LWnKDlYOIBEIeiX%^_(17c^NFq;u+tHC ztwiFV5I#MgsMyzoc5!4`?P)OBz$T^JX2Rqkc|wvWy)<{S!>(n`Xr7vdV(&r-XuOal%Zc&HVmcfev#`j8^6V5anR1Q1yNOWo6 zS6W{lJ06lKkbE`acs&m5AibVUplK)=-P$BwF%OOH=kWJQC08RTsZ)e+W?Az#NK^Ze zj;%-7Q#anvhF_<=*T~`i$7e?IDNWj;HaAsORpC5-+{w+4{r;~N-LOl^S5Q+Uij9p` zCY9*jl>gTcf>i$3eC+=j7xIq-Fz6rS?IypKSfB5MRS-kd_ndWb=dZVjTke&2ygsI+ zt461~`5uRA>!dCp+Z@i_p^b^xeg?qS#D#0lbu&S1;e#&ayI*Wej8k{3vzqp0$Bxg% z#EM$y@c-z0!W3i?6v~I=^+2fg+Ynhc=r@ zjhTz?mc_q00v81i=ItQsFM#hEXn*#AViL*dP!Sr?aZ$YFRAUzG{T@YZ53I7uk5_-cXt9G@&;#|K@P*1S95gW-nEsq@LC|l zBQe+l=*GE@)7%e!jwPMmrT2E>*QNy?#cQc#+vt__sQ9sfGwStpI~lz6f%EPMOUi|n zq|S_9-XaN?{VWZGy)P;=P2?cLC51sLyVd%i4>0%_HB@=!&5fPkj%1M9G{Y zL517Ac$;V6oAy9xSXCxu^b7~6dhS)KhMMDzg1%bmbba~kY+Ic=58B#0Gbi+tP>*VG z9p$48jee!l&yHLV_HAy3?V^6FvJhVe+61gG<9fOb5K^%_Gc??pl%?`~(NQIyEL{J6 z17|m?PTJ3Ln?o#2n$LOmUJ`Tw?p!kc9mFgNJog4N&Rfs)zMyr9_lJxnR-1XCxg8R@ z(g=LFxsyuA=TNp9`10IN1>Z&P5rkF;CMIgK+B?rQ8#!y`Y}|xDeOz%>w;?OAlYsUCPa^lk1I>>!e

HF+1^=#7GOWr7CuN9}g)cQtIPsmPEy+o1N!t6z~WfOr90SLO&d}h;34lIo>WaKFH7e z8mWmi9axZlmYP{}Bp6_$Ew*Tr+GwXQ9P_=|nYFlXjW)CW2U{Osp&^6m@MO#jf8z(f z$4n+0o{!zzy0@t1Ux+@O%pBRm#MMoUleQ_v2hTX+(FmoyIbctgdp>WwRpod$V8ktw6-V5o4Pt{L27RKYTWk9uFF=f?x*cQV>6fIY;2Ak`a_g_%j zKF>^Wm>cduIR9D_<+d1fGguh?qE?j@iI_QWIeaQwC+e=^P+P)`lPj}#Ma}UkQ zxZR&JGTG9Hs0mns3YqzCX)KASHJ$&cx z+qEzVGME;L!Ee6XSroz(>Ke_gHLjYp#ev?|be3|b9J%l3)s(S73skL_+FwVP+{RJY z-fhp02j(CZZd6Krg`BQuN^7Cn9ZUDOw%a=%3Huzj z^R?h;tt4gh-rTirkvN@3c0wfGqafXC_#uQgcELbcE@;yEgfzh_IQp5*^2Cd zZ?hkvyq_HRL5P|wVWpW!o<3VWsC!XyciW@Oa7oydby@1N`ZE6FnJbly-TUX*{yJTw zc$Ag#Wzf_P`{Sx<@Z(w%rn6VImUlw4>Yzwx;^kT2eM z5TWwICLG(Be%pF~m92~v{7|OAu)W*Y;Nr@L$EyoJikTzk4c9+P9-LFmcCyRr(;m1# z-=4gw*(tEv`6S`#BG$$Lgx1;i@iKM<`mw<(i<(&ca>QRQ)<++CY?x4U2d4EBn!dmK z6t`7MCwZg3{^iv?0H&<yh%}qy5!ON?U->Q~1LgmV6CPTxrYlo4d5x)Z-1wCiq%218ic0L0j z%+|SYWV9<>I_fv-D3wZ#nZ?=wk*dfy=i8ddYphvn@wSp2f6JUjoLVaS%|*t`4>CN! zUROE7J=M9NOzaC=s^#~kP9DWCT%gPoY*OK0AN9fdB*_~wpUeni(5AWUepGCueWXV7 ztjt)o4EefAR#>de{mMD<5IB@K>)K&^^r=p(+eNpWo4uzPemp|$8QB=6U#^fFv8a+b z0tufl5IXopDS7nAao?x?`t|Gh>dL|0D+!ra?GGdS@@ir}%iKh7u>Y-?lw(13O>{Wj z>Tlr0UL;h5eX-YFT;gIxk6P}E4eQC8bq`6xYpI0b^>lG1Me=4aBv7?{91jRZ%a3ZG z<1wI!9~;dr;j8N+iSPf-i8fb~DRUmpP?8@O_Y{3EKpF9kv@g$GY|R?I3-IN>%V)Kj zE8I|Xw8p9!hZ(5j(di2iVe&WC8=U)ILpugEZ(u*w@ez&r>HmSor%rScYK2CB6E{cq z#CR+OTf0<%s5>UCa|ol0<4fTs$-C%hLdExq6*NnOmo2O}#?!&8=C@)WKawt8F;(7f z@B9RFzYOpC8AvwI@<6DaWGY?_{cd2nGVP0b5623iF*nrZyqn^w^lkgocW1lYN~%wp z0mJC^%umvMuP#dT^(NhxiEaCCiAU-k;N$ZHX4xe`aSq5W-Ho zz*N&)vZ#3Kj_s6$&(u5nHGnoY=jo0?nw0Z9YB|g2wfXp}4ltezTfX!(nmYk#HkGo& zUNQ|0W880X=KAjA z-Zn8`KNyYB=^~L?jWM0AF|A%`M-z}Ef(N^8Ii<}I=@_hktLZi3}Y!ZbCY?mZb#*y`1*C12EOv+m#qB9G5DhQDQm|JtaIK;A|zuSLI10o^mI&;vqU>)q%}-xvYoO5<=V4-mMZkxD;Cj&UPaVZS?n-h(iUa+ zKAtPC5c5_$gEAkp2v5b+#xyg$NH$@JkL$eA-c_b4IL<#>uEN5ab|`4B&XDus48G*8 zT0x>ivZ_>E)dW_1cJ?jGL9qohG`HnlDj6J+oD4uK-*n6z{-pJVC!!&X(MM^f_>9~? z5+lES+Vv(dwdnL=_uS;Nvmur5Yw)}|-&aZFQ?juSQ(L(fe1I29+IG*9xM&Al{k&kl zyf+RK<~vl(Mf6MXP7qdkAhk%qw*ra_(Xa)5tS2)QN1-+hzpVK)kB%5e#)CQcEj@l= zpJ*`bWg$^q=;R%4e+{eAZ&xQ(3SH?inW`!eKjYjGUm|8jO}O*V2H&viWc%<47i;qy z&?_@_kyVnOTu0*K9P|vcLEZ1EstU&vj|11kah(vJrSq(5HaW|M5FVGe(xkH-n;`|I zv>LQoo(Q8VVygEbyEJPa=1-Z3_c(iS$%HL0pY3jPnroow;{J-$&52L4%FFhja-ZBP zVm6OBHcj^NfZp7RCTVBkdC2S41U-s|yOD#Mu~cCdSU-1H<9?YBIq~>XlU>k7W6Dm_ z4b;W!glMn+NdT(!9bQnxmR$m9OYn-SU~Nl7KjwzZ5M zl9d!-puC!XRqBhy0U z(TH$;r$lKF&2f^fNwVJwb#mR7VPsAgTyV3KR@0(ro{U+go=uq&655 zcU?PiWb((A(X9RA&c4rZ+H(`C(up-lIGBQ;rGh-Ocu&2``igj>+qtEdar+0#M8`RE zzUE+RMwMU+X1&l0#vmf2kTI!nXnkRR6GF7D_8K&*K&`2-1S+BgnbMeOsTo=JqW`eT(X60NY!L_fY6N!a^{)sN&!yO=PyV$s&XtXzurBzo0C%g$5C=9U z@v^&<)wSuDA*)LlcKydc{e1VfrZQEzqSW~AHLw;$c+Q;a7dw>Qu*)`fDSvWZ)N8mb zp53sW>?{a&-xRbpPO~1 z?cDd%wWI)mDxp(HtM_=56=0}MOezoklVZH?<$Xj;vvB=DU77^wx{1_LxD*FZOx7ks zCk5CusU`JSF#b8V06g0QZTrPd(CLh`x_bT-&Clao%qzrJs^>!6qpLXJ zP5?oiB9ZfzE2*WcnvIQn>5I**-&2ipoPr{#rPJO>#n9xnFI-XlqLjsyI@Dzop9iDm zga{%oNn4Mtrv}3*SY)EH^Mu)0!DiRSo7l|D4^+c+{o^bYV{o6?fe z^ZANC(kLR&NF|Eu%+K%`@^4+4&z-P+?z~kb+O(Hw$M@~01e{LiJNq0b4_17u%>Y>= zH>sz;XpJwIqtxmnE#4QRk6zx6S5g`lOPEAh;+Tu_8;s(Rbxhe7Cxfp!jC9naIDRm+ z7N|nwOo)YV|D9+)rRo%gbw*uXT?|Z2!*gxCzbUt{2Hr{jm#<#Y^H#yD_wQNqHU25A zAPoL13mSid+W%QC>VKFgGi#p17tTsNaJF7~MJWP7pMQ-JMEF^uYd`W(&!sZWW9IU3 zSy5g6DLFa$hYufCp-|}$AD*>(U(gE**6F$>%i+H~0<`BG0sAIyy|%;`!Q-bzT&f!# z=vRDKjccCiF&R((WVB+>*8Q2g0jEEOb{1Dtceve|YaV^>VydtZYq^MydJeEhxb==R z@A25G!!Oyy{Fqr*9qvgfR)SbWUwPHs_dPdrydMy$EmAGvL zVq49+F<-~Szmqa+Kfk--_y9PVEgMl-RJ7KCO`GeFyuXIS(N$n5EsHn(v zJX=3nwe4Vs@AtW$#J8)e(I56|h&|Ku6J{PqzmtN30wXiC zV6h!)=27?720*vwba^dA;`p?E^3B-y;oC*a1TNpN)?2~c@Y-PCs}XUdOs$dG81Uf4 z*PAH*QcK_4UC8{lKk1QwZR$~*cf_Jt>G~$D?dR0zxaa;`9@Ti9$TO-$zv{3n|HTmd zG0AR!_0kcD?jK<#vJp%obP|Dd&Cyh_!_OaFip-)?eE&l~ ze$!qIR{d%ANm--E1G+=z5N&Jb6;TSXo8om@3;ZP~14H2T^|jUasF)ZQ28Nt@H}q@c zdsPVpg3w@jg_0d4VE*dgk5 zbwo0HN~vT)=s$S*B3#QEGs)W$tW|E7qJ*7K@s4d)Ce>fPdKC>ppM54NDT%sG)x4Cq zQWhOT-jU$e(fpVKepZvNWlD&yy%^Hnr;AMXqE@6y8Q8G(=Ji}P!D5QGn)=~rXAv7~ z*jL6e%eN8lZM!DPO}RanE%>)K{C3hnvRhU-OKP63=ERIzDq}P3Bb=Gax-F{g%^Noj z_}zjJxv$=?3x6{KEiLWf;9%34)B0pO>82`13qnSgh1$eZp zzESPlz*U~YHC~#)^r0j>k3>lpEf4Wh=ILnSql{`8>3b_DmG9?3+9)s}II<-^Uu6N8 z^_AUxpmKJ0_FDx78?E_-glDMxRKxnNw=DL}ft=1zXEc9V`w{9Cx=HcO7%ppro#h28k$t3+GZondQ7K#^#tg?jk{N_NBHxbEr9_(2 zs+We7eweFT!+Wm@8@}8hf%~%x4onbyw9eBnhwwvbla@fVY+0TGJMqQ^ns2S;mVE`5 z+g9GWQR`4=G;Rj>75QFDF8PfZ7di$#T|RgPI=fP+apCtDwY0NiN7?@2OhEJaEFS7B z?ebuen38gFzp3;Rr}utWtIhw$ri|M9r{?+Q()nEcdHqUH);)*~^|lz^a~<6@s5%Hf zOX;qnosL|6o&2UH*5$EdRp*;{Qx%BNfQfB0O^5Ac(Jyb7k5;lu5cE z&*;iV;|9fq@St_D$u!Ls{!erFojS~;$*P%a2=>A;eqhU*oZR`jjTd@di_BK~L}K|r zMaqc>i#@>hU@MW9M7I>T-vwTq)QQU_M*4D5u<)^mw@Q{)e6x~eFCtEdm!GvYMj|>X z#OaxzJeejK6I|gadHdJkY+!WpD_7^SSz1~WJ$n{pN=BNk%waZ@TeVTEoziMXO1_DO z3U$d{FUlK^ddo2UZ@If@7vEk~T6yaioOTcT?6h5J+#lts>ZZg=H!=l{-j%^J<@3i! zPIF5bS0jH(ojzv6uH+&&*k~GqobEA4?mSY?y zkva}DWp}tms&=_eqm52Ef;eCw$)?a>2u;<&TfA}C{VPR1{>jW2%72Pwv zzwr`XVL05V2e+mT@L=oQ`xBl`WWU=LRfTC2(?7tWapb#_5d8A7k7A_(DNAyIw;E>* z=m<2w8@N7vp6SIIPB9js)G8F)FG{SxO?K#M$K2JWI@a*r7TN8m%5I{4@j8D+B;M52 zNB2+9NvbX}Nlwk@JZ26!fP4SNS^(>X0c%jwc{h> zt|4wKFYD-P7Gq$>%l&Rsh0L`VEx}|*BE%k3|BP%!I)S{xpNE93-EPS}epmfcpz~NL z!@iiKthe=u8-Z|F)BB(j5i0%y2Wi8j_2M9I z7Nkm1g1m2WUfFD|H{OIx&Z-|2c5vghP-{+23A8GQ#*TJOi7x%h4&U9t68?eO<8uR@ zGp7_D7c3_)kNy4X39iW)+t#|O#Ph$Ah&JjFfZKzL7g_&9Sw0&Tl~hK8lJllhqD#7St9>{-hR8jhf77^h<|JfN7Cr?d^KrVEeh;c zKHMkTs;BGvXS(OTjM?^i`$moNyYYh*wWC?s6Yd)Jucw$a8W{5dv6tef)L0>R34?o| ziawca51YB?`bB2wjoner9h6M5Cyz-UwMF=3>3Jb)G5%3M(l3gt1+F@0x~H>?UY7FD zq^*n;&1~a44PS1E;OX_bx1&3H`mCCEwjDt>yJGsrbKJyC2yNJ`t1D^=^(EeQkyxo4 zUZdM;MKfoEx$X(#f2H^M9+wH3_Zr9^D=gMb1%DIvBC7_?wGV1%(X$q?O+)TNgBO>! zribUj|{Z@n*~4+qXaCE>b_&RsI` zj=B{fT$)UJ!In%EAY5l>0qby`&7x zIXS*i^E8;E`8%IP+8Uag!C9JG5zraCBmj4DJ{QsfV{b!yzW#|s6 zEV;Zx3Lx_e9IM>U^Vjz-2zYpbr%%^-#4`T_DuKQ0Rl@QL{@VhVLe<0SM0PP!$sW+h zurBA%Oi0f-LcuiaTF}J`X3KrOdtX+bZsUKV`W}v3bd0Yme3YnMOu$9MWS>97Q~o}W<>I&G$eDZ$U@D%Z%#-V{jWG=BOR zYULODhgi@cYB7}}%XaNJV`V4J?6Q~Oj^Q(!|HVFxLNV_eQ90e= z1=kt>*6){o-cM{w!TY|V#&?dNkoO76i8&uMrQe4NV)&#($}fXOIUl&`D#FLaD1{g9 zc|%k(UpJeuT~;a8mFxD-I#B1HMvFC+)IxP^D2dRmc>z2RJcvK-2U95+^=6A4X%KsI z5odFJ0Ny1Nk}mG>tXVO4W3Jn4#nn47;gb0F_a$OCT=16T*8Fw1NNP^n^-qNR43L?O zJvn#!hs4M2eBMuHl}@Y_jb5y#zq?w=sw3ttCAKWCkJU5^UL)>1wu#>+Jw&ki|B(yC zjgR`-uMURfnlmT$o!8PfoouJz)D|SQVwSdic1B+QwAkfVWtPq9L&c_A>R^om7Zr} z$e){aceCy2UXqW2>oa(QReTnoKbjgX3q_dITO-0N7%W}W?Y1;Jfg=elYO^le5hfvr7#HzkX# zoV28>VNP3jJL7}mE7pV%JL@&RtoX<`rx0Z4ArT>W-8tsUXh#1LP@%mv+@4DBhW+Rn zJ~Xv`xkQQMM`1}~MHz9jpa=~T>(ctN5=1u5ndOG`PVsv*x#Q_5pdiwdpbaPH^L;gm z6=Fi6X;Gds)LHA(Uslq!Mg3;!^GPJ_j_fJr#qHHk@{@+|%+(ehkL##ddx4%4ue_gV zt5Ci3aUjMM6GiHHEI$I5A*3#OwS<&bYC^O%SUvpmjgsG0(n)h*2S$qCs(cAG-#pWd3 z7Ls8|E7?Q8mm$v2l&#yh?@`bAZBYg+Hf&cnoYD6yL*#?E>~FUXON^4+^z{1}=dSh} zhA9Gl&OK6JMYWBKB|n$Wa45;b*O~G0np43x1&f`)Q%eyPTkIP(C z<>vGK6yt$tM4ulKw0{~fh4o?p)8Ul#E$yKY8l6YPZIDKDL>ebHey%M z$Yd9jkt-~|`+kIcp_q=Yi+#R`3`%JSIG{c_p~8C2$M+M)9gjY@ zjXN6x`98_c8X6Lm@m8scRnlq`iiwOCa)d>)E}`;IJIzN`+}m{d7ZQFq);WG!4PVA2 zRC+y@4#X$8Fmg=z&Q|V&8YQkAZymGbE(>N)ZaCyr^AV>nSn`C$A4C?H!CbvEC=_q>|+Etbgak6i(UA zbjCRjrzvD&-dD@=cUgd7n%wguT!`gB>>C4*qXXA-z`Ly~5BTbx*R>>n@IBvA;Ho7fc$`r#*H2k+)GGbGY|OVbWbNQ zCQ}LB1?kHzZh{1#ar%D;-(SeyU#!5`?*&utyxa<&NA0?h+XIyFLTNLw-8&^&QkFNd zf)x~~2&lXJCD0ns>V9NKQf#XpMlWyZ4$a?nfy9aB_!^?HfrqB9t!5M`C{x$LxiXbC zT?nw%S2(>UULBo#KlebT5Ac)4sX(Qc|5H&JhIq@dGx}0sc$T9i7%5vmDVKmZ86TuT zc!sh=GM@S!9Fa7S4!E5OtacTlZnvNujuBbZg^^9BpVk!oaR0`T;B8NSK3pFxNqsT_&6&NTEDFe>HaNI5j24n z?I(>8HIhQUhZUN=w!`bFl}$(>Bm1s7l`1IS3TUrco3#=a0^Hd43OvCp#TGn~Hlj+3 zJ_yRf$GgJ0Av_8=YGZWQB!43MWs#;WqSzbDgwO8Ubz4@dU(K073tB-^5p~ z{8LW_#-!8K>jhB?kZ+i7ebUAO!8#}9yU6nA=#upEOcRZQ!5ax%2WRB9t5iXJSIvKl zc~ia*n+Xey+1NsSF#O8*r9ZlL-CDe4&F#ly;9h4hXryp|Tvu)(ok)1_R1f`$xTk2m z_lN4W4Di`hb@|z)b;Py%B#krM7$nf>Ph`N!oJ1SmRYN3UB6@Y6^>!rybxhfq1u`?w zx55m~p0hE9JgL41o%^^?a_Xy#0m1NR6GVS*Lt*s)y})0KD)9SOfBPa8_*n-`jN7sp z|GN)v>&~ETyI9Xc2LUn^Ilf53%>7n18KWX32km{yKtLer9ZmK6RNmnv2Db2Yk|%mD zNaFn9+1zR%OGs3%EI?{tv5Q4 zzZO>1+xerGjIHDL2_kqcXvJV!w7zoW@?Xfo&mxW#LGfti0rd9W5PY#(xA&w1L9vgH zDp$XQEAkAfsG;jSlhtDO2o*w#Y0SoFPT$6|RJP9ICF(`#SziircPWZAc<~h1(dJex zJYbvpE`pOsGR*sR2+wytP=$YpA=yNgvIpNy!Hct}md+Kg{shXyQxbrIH5I4!ei;Rs z_?ZV=CBMDv^V@Yd%c6QiB;zU6qB{Dppk{N`*T~lv7jr9Skz&*yOpZEX+r3H~+^=El zZ!r7c;S7B9lyYQ`h(QXSb5aC{stkVq%(t`tgI{~-&YQQGdp#~BB50nX!hiUVoMM7GgEg5SR?uog0-Af~F%PK=Xu*UL-aQ20 zyb_h2gD!IPZ`~M??}zi$#NJ-LHPH(Vk*5+DGZ?TZBxGV^N~xJLL`gG#8ZmIIPJvp* zW=tXxT%QnF$=9&cq4UN5eLalfKCAUMIwpG*DSV&0tQ>G2gn`4snDATrdF<~rR#u#K zy1J~3smaMRcI&GWYcbqRSkc_;SY}O8R?V;HPv3o{`xbPbxqpbb*9kh$e!t1U2=)JkGmSU4(P@;&q|nB92t^g~_~|0pR6m@qsi|1tjQSXsmJ zK7=PtRSRg=4nX!<;t#?m7H6U=#|)&0oF_>$EP&~QLbi81xBmW2bw88#$SXV~Ogsio z$pmmj$4Idq2^LH0(l5R|s_KpRokFuWc#}*n@$Ka5;^7ZgAXZj6M`K7-1!q0?;bVVs zrS|mzhaX&ZU$>rlVoowPu_$NzOjvuOom8(_pSEOeFIhc^ii&{AO8zmiwVN}OFO_pX zGuYOPpl#1~~(z~zRn^l%T;RN)<`0QGomhIHNX zg@MZ33I(lqH}g@jzG|Btm1t5e0AOexCtoq?CjIN;aL7UE$F-85} zz~^yfpK+Ok!u?p<9E>$1B-Se^UiZR=c=7A%B%SB(c>J?jll)}uw`41r3XGeXcZ)@a zY&w+MCxE!Y-QhmSyZ4Sy5)+=iYdZiiyrf2S;Rp^>Bo(OVmoD&+QdWOXe<)`3GuCy- zPr&@BHU9oh=zJX@Zz((T=NnH_CKvivna0O1Z^s1MD_qv+9u039?#v&bAB=SpGv;TN zX>`TBmW*$y+2<>!fGgy}w5Y7o+DtLL^PFE5bzk+6rw^XCa}5`Fy1lEMYiE}r?iS)A&UVeMv?M3Ud*1~mAag_+L^6qA=62}E8frggH|?YD&ZJJ z6i;PLT3}@yztulwb+87xKEY8-Oy74E6Vrj!y%f@0}fA=DUIV-lgTKa zm6{L4unc%eInAgf`obL~jFThR&U5UQTpqh~TJ@|MoZ`mKU)(8?9K7u)=}_(P^4ORA zup7g^NG=m`_*~V%mCOfzzbpiI?s2L*_{X4~<%xDRFRhK8s%K6S}tdoP$ z^MkMUs999Li+td6&rYAYgIGN3t)Vh z#kL(p3BG17VS#(8m3$(4HEXPLM4iiEaU8G6mmSe6u8RrS@lIa^+zX{MlT`7O~u9VL&A1VnlC9I5GFIiJseu*%_>ySETgG z(eFCE)}PI6WXCa>eLvBmoM3O`%S4?Ef-Z*vv z4g54{DJZSJk9DJwtx#`IIn$lIrUunI_BWqt3i&4k6}LITO~E*YQco zdLR3?sO3I{Iz{fUhfmL!e7IZj^1<_CiTvp}>MQ}WXU6m-p4o6NYdmq5QX;Ot!r>G< zrPjEwB}y~aFbjz?v#WN1L%xh%4 zhT-&=KDn?prwc@1Hg#FfG0+{a(H@6Ge55KS@w>U;_Ry+Ce2T!1ru!`h(Nqm##^RBg zWpu%Xw70-%8}a%p2dAIR zWq+_5>3ktJskIW+DsSJuKzY9|#~3dKCVvLIx7SCvVrpg9KP2eO_PP+ps#jh=JA+I3 zocu4%Dl(!IsZiM&ILwahf7rN(^1hqsjsq4AO>R{l=>YHF#ye<6pQC}AFq_}a~)ih-xoyWdOjiX0;LAdoCv!AfKLn} z`7_l2M2|8XPYYiByCpb^^*{TQ86N+ea$E}g@^5WODEq&#*@2Yxs)4BE>I40H;O6mK z*T&Wssvw5i@{Pg6!&CEdd|k@=NL#;ZFJ5{5_mj+B+636pxH8XQ_a)D3J zKp>EXwRN?}kx?S2;b5EJEt!O`XYC?S#QL>qTLx#&;+n%f0eN{Zas1v>Fn$>&w)9a@ z2$~~3F^PR>jAV6XOMH|zaJblAj9^i&?besD)FG=wBc}@#n*KM*-Vb+FP%1E}FhP@* zl@&)Ecex&|P@@X#4-hD<$x5pjp(KJE^$)vF#g7Gh-7BTdoFboo{!x#z;;ZhBrj5;x zrK`0WojET&38S4GWNBp?7`EA%SRB1G+?z@LE+-NHWEzF}{&UrDUcOlhK*tvLK4*pY z#gscYo_}q33TSfoEUR5;nDMBQC2dLSzWXF zK5OEelbG_eu`*9rdJk8>Fmm03Vm?a|T5*v~wJt24?LphdvjH2su|mTS4Hoq*CyT58 zgqc!O7T4U`lJB&!-d|8DhB8Hu(Rh95xS&~wiVW%So(YuIDXq_wCdO+<2m@!{ zQO-uW+})lC{dDx(Il`T7Mjd@wlvu5$CBZkd%M;0xC^@*=84~Gdj#+JVpgg)n>glD0 zlfyE)*@xPyd37Vz=D+1f0!tjtwHet8y)uTfASKT>5t;~Lr1{~;#yAg%fm|Y5$`EqE zJRUFP&IL;`kz;j8p0etoRQ{TARPU_w9|EIL1bSQ_n`YY@Wq_*Nm*U#-TA{_6(i8 zE!Rka1O0tDitLhG6TkiR;LX-FL@n&w59|4w`shISK>q_?#y>$ay}&=)nP1b&%#J*Ya_RY;~3F{G|r-$CSmet;xTr(w^wj!OjZ8no}BZTG1?=M4- zR)en~Inn@P`8B)D1HRVIO+V44p<754w7uzhiNO_vF+ePQ2YqGz|03?a;+pKbzfTbr zktTw4f`EX4^xkhoK)Q%D>0Np+p#+htGy&;NM0)QnB=p{UXrUuDK&XL)H+nzM%x4bf zGY9jZc@M%#a$S30o4wby_FmuhTf=Nbysf`M7Y@r{z8_oE|1xeS!v_Ug3a?*RVi_zN zt`@AR!FCN~@L5L#T*ewYQUe{emPu5u?-tUocADI}T)1!(&Fh*{^5fXCt@Eo=LIcXP zNvReAU|$_@l&;3?MPz+TQU!Yf=_uqteV&kBKHRtbpUEbf7}4A{$$0^{7qy{dOkRZE zk|=HysfC2nRCk!UFze5v%ymDKWZyU?W4AQpe?$ImS&adYDapt>i?vJaYWJ~!Bffko za-(+E(AWoti)JLL#9f=jatGLAXsNT+lBq6XLk_v#ep+)i!@>2&OjnBW*giMYBUd5L9#Wid(vpNp6 z34VP8=lf#ggc3kF7ydZ;*Q|@UU+;Y9`cP{MhXkmm^5+d?Q>XA-JyJ^-&clL&+D@a; z-b9bv6Zj1hMIYq@eme5eA!w6z z8MIC9gyQ%@SHQD6j&8a(5t*Ff<-?O4E}^avI6jl%dtO8}>RYzsK0CMwmvb)oS0#sm za8l6zGC#WJ$&)94(x(e8QA?qfrtNoQ08+|Wb2vUej%5|&GBbylTmAIw?4DwW!|L*A zl~O#t?i~AW>2h{QN41T`lqY9q^m(Y|WbjQtZEJP2SPmWe7dg3kly4iKm8HJk6KxM3 zVIk`Z+6#v^I4upVc7|hDdN+1eMMOL_H#aYC#r5X<&)XS0Ro~p}FdA)(-nFTirvKNO zXllG!4gQZ`;eBHNS+pUr>Mtu4(Z}%Re~ZKMKip`eNzq*MH%fCqfzn&Q(#}GcRC;H^ zJm>vqS?M;{neP9RB*`4|l+gHTSemp5T-9QfipD_ICF@o&6Ba3oF?+Te2qoOR()iak z<9_vM4a5=&%K&*V%gcz~;uvC-9ud>A*|ZD3+w#*|90{%zj+=4>6dDx$wQ_;HP7JvF znWFKKs$ieBbnTn%aoQ%kv1boVE*>+KjofKlb&YU71V`m$0=AK!h~wlEJh#!Vdw0~! zZ%IZMrQ%nEuX%muh;BBI`%Bf_wEJ^ndnXDs7!EPc9k8w#^^F-w6(NH_rjNnS&i8!H zt}1yR*WQ#MLKlfa0lh@0qiMWSD$ZL3jGv}b-dtYo-Az>y1oRJu1V zls#p};ujPn*~@zHtIri;ulslDatk{#bOC`ttP6J|xkGzW;qwhD<8nV*-O+qr%em&+ z;N5&mKo)DLwChk{ppe37&Z*O$%Hh^5@OKsajnB{f!A`B&24VLeL${!k7u#PPs%T%I zBNTs`xp_=mzf+ev@wy4G>q>vX6@H%cMPHnuC+_z6bdW$SWhR5O2mXrW7IA5Adz^|; zQxTSdo;@B7XrY920#=LIoPTJKc>ABWm6Q$$aSXG4${YXqL6R(T*n`Cmzd0ss^t{Kw z{m&@`HgnL0J9X!KQ0w6u#9n;W);74xiG8%xxKpDfl3KWfK%=eJS}Smje7Tj2us|p= zk7S5-9MJaT+IV|4M<-(@D1^nhQ)O?9?;^bAx2Vr!bO+RYVA=B~MeM76FxjQ;8IB~f zWisGnu-gv92XMU41eR%fIm~BxfNZ@0mm#k9m zv(;-RochhD&W3!={?aP^{O&(oNj|qwd~4v1HV?glF*9D~s zb$ys0C);!UUKIW4aS3yV*Aner;tU1J>kF~tA!oHnZont9Nam2FaUwpE=dL$zIdp97 zQRhTf?>boP(ccCOAtidW+d+zvpws9Vw~r0izN}MO8>Q9SZ7Gie)FXfYGk1`N{3zx6 z&8I~+ODtM3TvejwCk{TI#vPo^6a9GDvDv&oAfn)Dqfju@eV2bH_M2qY$2M~+Hw&7W z#M`h*G9HB8T?Rk1qS)c^Q?{GE)P230RPu^C2Y0;)z(hE!`it`R_X@e6ZDu5c6^6)IOCKBdN(FY~*h#{y58VGUN?`tcR5NEf<;(MazozCBB83`97I@@vFm% z%lbK&f&PnPvfiL49ymYT|;uIwQ^NL`)6QCn>-Bpz1KdN$Yd1; z!%wNTyaQ>AQf4J0DDmyEVPU5o)dOd0vecKMbEdtkEyUNT zMMAydn9uYIFB>2zCJ~*Zs7&nUIU?eX{GWFmIxH$Axw&1?`1j>W>Z+Tj&i?oR z;Pyr9zY<;w(XBnBc1Sa^j-P>2y}Ic`1nIGMsNSX#o3+o$ zz~dStD2mD;UD}(ki3+up=O#(sG&jfX@n9r$v6J%>bVaeIZv9q|P#D3RR=?kl6_p@8 z3@{^kKke%BiKrz0A@$L-b-%C`R~)4IhiWA~cdA@PMH+))w`?v_xkjtcZ0%wA+l?Qh zwIr_CS>geXyIBiF*@_g3ya(m2p>Iqyet1w1fVRsCM~Mj`WnzGKC<64o`NVMYxwp;ilB@r`jre*TU7%TV4Qpo95-`s=+byTO5aKgc=G z0ZEB-7M{0)j!vewH}l_$-Tcw-w>z*KutDY8AY-u^l9lDC_uO*!; z6eGed{U??G`_+gM^`)jvF=IOv+5K(BY3XBHO(NGd-6n+d63BV%@xubIV&1Vk zO0oi>=m5^->b;GfDkL5DIkljilQSb0_WJdh$ElRpSE+sShiianyC0W*vg$DLqk?P_dj5%C!&7U7Z0dx^ZC*y#fgCi>8h1igdy~jGitE+Hl*Sv&LyVbXLf6 zax+MyRQ)y&O**^@L|l059(OXQgq4-A48%!&{GHI%jE$&iEc83PkFOd z7aUkDjziTGZACL^uTWQNQ?#>RGS1RPy*R?}##*0JBMWLax!RkyIgL}-M?Yb%9E<`( zPU$3YqVZhL8V7B+taQdr1`FWKOu=;w;KBN^3paRQWsPI?h9{;$BuN6{T77vn9I|x% zOs*qyqifU#1ummg6FDM5C_C&;Tn$QrKLg>;&$u@|;0L@>ah)GlY>FU=q%{V)WE~a# zN>_&NvY(@iIbI(oFM)%u{^Ex61{yU3r*zlQtzrG3u5)F};S>K4OF&#k0k{qJ;TlW& z{f*PBGaU$qeE4F{G_x|4_lBiNPM>dhB<=TZ9Ub0Q@1TF*^S#U(-mciG(B7wHADzJSzDL5@jNzmIlZx7AH0~FG-DKj;x#zpu#?gXG;a922U zwJ!Oxpo+MzhA^&H$Jd)DM*8Sio7a}H5ro1R1pO9+!0P2SxVBDWoq-;-x{LdKORVxrD9-{)N_eoJ1~W_RcSsY9PL$AMKYEla z59hwr=C2#kuTA2~f~)tParGP5MTcbECCt`2g@VYJb`1s(v>cuMKCKS(i&fik6U-vm z#Ae&JX1}WkDDwVNdDyRgI-8|I(tcCeMWwGMU1G;p5WuWuJg4xxE?TtK2X|%=ur`9h zYz`_4yAs?EP7LG!B7KGA)^%SxqR>7M9zYo;oR%eKX}#CeP(v0w6u1?`*bcBZhTomq zAjW2;GIYFn`$ak6U3ex#Yv;{9GHXa*JN+!uWmXM*RVI_09lJS&hj=eO14sU&;U8PJ z)pFlHLS?A?a82&`-fnA`o{LS*75J>%t2*=O*uCYqYi}zdzPHXC zgZwYMKYJD|6>dP)+e~EiajZ<*Q=P%PY?Xe8lsIK&;{~Tow!D8XZR0k>_&qT)*wHYX zYWmh_YeVmfff`Nz{UJF6v1OaPBvB9B2jewvGDh{hIFhh|YNjFJr^E!P_56j|qDV0* z+m?TOM?x|m2IBW|Em?kTpkEc^55UA4%NdN;;JKINNzC4MChTbN6dZ;?`IsiYq(U-m z;rm_Qm-Le3T=$qg$!erC2UN4Kn`%tnT!Fuf;S0wn@mtse4x9r&`=#misT+2eDhcS1 z@5_eCvsUgR5fQPCK~Tw=yCybF>G^>p-<{Dgj=H zV(BqNE*s8f3vskf$B=O!N(QU(>+EWu07Z=CvyfA50liwDD6>FFcsY)eLl$3jcPd5N zhe3w&jtA&aJ|&*YUnBIAq8xNscV?kff8Qi~;F~>WAW=5e0do$0X;7KTebKdI^;}0Y z#_KY2{>~us4sVvk;HVi{C=Twd(B=$A+%Y++{w9L zp`8cM(RwITzTB;(LPeCZQv1Bx#+%!We&OBdB2ZXu8yi-x!S|b$xV&Cbpz1G4MS9-# zVxG_*Db-d*+p)>&E_IS+;_^;3@Lfk1`DNc8V>3aoy`!zir-t{3oaMGB zljFGh)92#e#{RaawXjCNwel4vn2YqTfzA1oaxrhD1KElLk>ie0mw9cyRHUed}x*0Ppy!r1e4NWFuJ zUz2T(JY-uNUE-wQR?Yn+GO-(!bx(W#t4*jk z;`LFiPzikZJo(UD`XK|PGs*hGee|2Da=C-hK&(YCrJU;oYkfUcJAI)2y)OipC+i4( zSP>UhnffEgfVhF5Oh z0o@x<0jEu8q=ye1;z6c3CV@hsiHs!tjYSEyo?7~Jlq1s@OLDy{s>Iw`9kq9ZSrcm$ zr{lZIv*%qWGXt5y>J-=cx5+zjr(ajNQboGf*^kQip5Ja$s~QpOE}o0Frt2xeMbe!i zmuW2RB)s6SnE9fZ{+TB`I;k3+##Rz&{;_>yv8hWE6eN~GXpMa=b zg3GmX-}QEP7}I1V5FZkN!L}K^sTJa=0O_v^2GNC(zXDrZps=_Ir>Hh; zBnA$`h#`XktIaK#*8@m_KHB4Zv);8Ui#7ez$y2{OuAag$C0Ng3&2@N#>xxi9!~uZqh-i@sOt9^%_gj0EkxY`TqrZ#zd9_(d!a4eSVrsOjE%pKW zdVFH8JqEp55SEx&??}rYlq~Qge9tb4>lbZ6%Vh#MK!lBJ6ZuZ~IsXa43t>l}*|WQo zVJ4lJ#hX|tuVlxUvpxlox^4GUIfFItW<9}QL|oJ`-+Jmck$?|0K#*oS%SdPv7s+Gvmtn}3+`p?PP8u4BD= z$^HZBD0w;d(Ki$rw5q0;TpvQo=E^_a1}l^r&k2V$ENQc;BOfkYqzFdZDj8vAwG_~q z;ZBh(;PR)E9lz(w@fA++o8Cd#v(lEk|BcGc&N^tbN!7r`1e(@|yP zn0G2Vv0kT$Swa&YTF+c@QtEF?I$v{V*3X(~OiB!d9rswD1oPOu-G035gL*P!f6t

&Ci+VMj*tj0`6nlHEsoWrRR#1-ZuKgI5a-MmG2x2TiONhhhFl~N^{GT|91FxyO9 zxV}zaF0bm_QQRB+u$EnmR5eImEGVsa@N$+4@xEp}b@CTS&&d4-#C2e}@U_op#aSjw zxU+@q*lhUD<}cX9y9y>0$x5>vcjyti{&bo0=ZilosTvkHSoS3Ty^qJDB8 z5!S{o%}(m~3^IdC{FC-xPs|A(GIy;tn>%bgPtc@#W%-TB#Yp~y*k19Pm$#-q!FrT+ zb+BOWtu^iNBtgzpP6=VBy7C8M>$fi$S-92jhdnS;Qe#`NecPaFJmB{F)XcPnCk45z z;@~v#wNGc4*!z0HNLW2}kFy;usI5q#7+CtVH~4 zKO^%(TLBtoe>(ERrDJcta7XKPSSJJY$(jAxxr^h%50u+`muEHi@G>1`CTUG%I74m4 z=G+tdUl21^V_>y}_RoTFy6M!py6kKf5wf&b=I6BF`h51dILDp2JhHOA*&EtzvvAl9Z(a}{npqm z7k!t0`kZwRKh$RO#gKCE;dsiAC-NkO$KBSSZOIc{IWjxCn!v}Wixc{1s%*oh*iDBO z<5xf?`U;-p_^apqd7oroQklHq!ED^)eY#TCR^Lx%1WeCaR9}nx13mYsFUbr?uT`%y zaN6(DD)y@i`~cNWYn7nN`Z4beX4;;Oc;S+iW|__%P>J=|(NqLZUVeC`J!owy-uGs( zhmcUxopKpVW$ABEdef_bS4N*lYuOUVuip*}?7ir&v7+dnGqP|!g;mX+K4P6~Il*#$ z47a{IpdvqkXH+uk4MKSr><6G7YbY z38fblIn9Z<`MAZ=46cC~X&$hxryaXLbx4O%>Z^zf@Jr|FNOxFkd)Z z&3yK$nA<%?Ry*o!PAT>_PTu~)O|p|%=LPr6He=|G?C;s{imykcE=bluWor#Ch! zDFmWYs$$1jS+a~=TVPNN;q21Iu1|iLSSOzq$Gx68oAMm1CNeFy=d32!pq^cg_ z4_+=crhjjb+h2%N_9-~OOxMmNPP9%b&)TCFi|KW8)RmPH7Be37#|b(A{dz7oea@qr zGPqi}Qi3PVj8ED$Z94ygz<_9**2nuRpegM|1)W+#UGWeVdH$XSoH_~MG>ciB;Q z&xg{62BwyypniAM(5iIjb^hoZ4VxtVy{WLa>6SUIvD61G=W%30pM{NC)rqT5BfIJB zU71B>7C)P&?=`gr255T{+cz7_J8o-6o=eJ{T`x>qgE3s|B?4s|+Z;(k`}<|`7{~l* z)?mQ;i1a4UWAS0;J1JqA1F_tjiFWpE0M*Cnx6wPKY zbj2Sv5R0qr2v<@wjyAzW{jikMo>t$SEP+YuFL+127n(_E&TxiCGjGfr_!LIkvyL5y z@*rWyUmLX3Pa^uRR{w8ETn6wftVre?rm`;4eN{E)Ueb`j2z(0Klx~{`uM1cr_rU`d zHmQF})%a8ouZNz`9n7ca@44Z27H+31zcZID^54p%cwbw@Pg_q)P8Y($5<2ySD!OAx!@yi<*nEKEwQ0uzqYd$bYD|n zuKmGWVKx}+Kb$LK5v+bN`@J(R&coU+#kQYSYBEUopJ&1hx&~Ps<4NQ;E*yinP+lJ1 zeex{|(-Kgr%DfNPy=W+joBi|pz;>hRDw&y<%Jz|v2 z;9GM0OZ5yY3x~C00c(NflLBCgu_{mE7H@RdNT$vQ??_2D-30ptjmbA_dOw37(ECkJ zAx^b#*1zP{bz+DmloR2@Qe~`r zsekck)mqE^IBS$fmcH8TLs|+{G;d{pWJFYLY1Y)x_M34A$cOI{| z$1zY;3k>5}<0#SQpR8Wjz}^MJB_4kLl4Ewxn&kW@kE&0mF2*Vy`Ejm}=qVlTK>v;H zliRGuPeujV_#XM0&)8>!p9RWfs5MX4f$kb?-Kx6f;K=vyr`<9#1VBd49Ac))N?5|R zltM*MI4=$5!kANZ;-oX?>(c1;iHCl^7Ur5CVXqj__)w)Q&-cymw<1V-L(a10fnMpj$Kl7v&|N$%YQ56O2?(8chs+)*E(wg7pC%|GaT6k8rB;0Qg7^~-(-UHt5*)ku7&<-Bt8 zXzHF~IyaZ=zES@G8fZG#FIJz@PbOn}D9whAfsmHnRf08rbz~?08W|6(aUsA5Xq#k- z)^|_Okh9g8X>U3@Vu9DmJhA%{lr`cxj|nC8B`A=S<8SA^>&;qC;C9%qVt}O z017Vobqz*vd9=xn)ujtjBsRaPJ1DY9OCdt>H;!6btNA7_Nz2|7I*{8wZ_(3!d%Tm1 zU8}&eGQxC;NEkzzG@1-omv3wLp`>a;A*b`EojbRRjQbW3Sh1aMvPgjp z2wBftQ5!J^sBpqvFuuK;*zBT@7-Rkyfm$hgCgDo|3EGT^*31RG#H+>1t%E1?&UCm5 z`FsdC58o_XhL_y+#7>{bJB|-?wd0LiY+##_mCOcWx6RJs;zcjd%|e$<>&%bdcH(3v z>P!o#1+EpYhOn8t`JsI!PF5=!O+x|BPi?(u+ll7+HtHE`j|6ityyY!+#O{>fQBJK# z*dzoNFwmr&0rrF>o`b9NsQ3Cis=Qc`t}fd%a(W`Io^;{zJhSNew$J0m6FUW>a*fc; zUumc9o=>KmwH_k3^2T;|>z_hMC(RGKj`$QWeMfr1koMq}9wX{StntvL&~o^+$}reS zOOVp=Tfo>PE^?JrHGw#n%Fl}BYCxiInTy?r=(pl~`3h5+;hB6|$1OE$zm>=3_Pdfb zMcDO+tMHyg{DZgaFE!OB25OnyBy^qdRjugf9SG#fT{_KnCE6iP0up0JngZ3o1#h0ei_V1 zlKg`i#;!W6Q$PE>6|8p)i&PccY%7s92wNV@{Psb$sZ0&6Gi_3$Qn4>83@#2;GbroX zg9}2E=G5yf^xE$9EWd5r4`o1}|9s_u6XyZ&mTLX1mXS2E*>oy=Zy&W83?a`y1O1kxaHk&^C`owl$Fo~6$P2^%jS4BU>@n?0B;j-*VN2~6y_ z4fP4na@_kmb>M+ZR&B%YJy7zJhxh0x@+UGSU0F?~v=Ex4Z6&QX%CS(yYEig)r=l>c zRzfJV!9@LM<&f)-;)CQRa9*1G*GGFra?=Oz?XIWy6Fe~>A~b6hllKUd^p)-duTkC) zXW-DdOsOjI^zj_~hY~ufmgYBa4u5M~FC<2tC2h%3k6+na-y-C7{O(US4?ZlXr62vS zk&J|~ffhkF2Ph-$-THm?!9-*#=3KKm+V2y9&8ym7kZ0pdaRts8gYDBmm+qK(w&7we12=~Vb0=k0#< z4aLX}HbUY^ON3}a43!*hE^4BU7nLzv*`OAc*BciW|5uP+xyi>0bIM>GV8M|d|A^gd z!{sD=dY>nTl|xxJ+%P1=>nk#*+N>uwBWhQpf;Y1*ORLxfOMSQxU`fn@?tW;mXOwbU z_nL-@rFsq?h_P&k&+HyOCy+C>FVvDQt%ufRzjx+E(m2`tdvO+J5dCxows1vL-p=>G z$N&>%#Q(w7ul;|{40L(r2pkz4z-qQ%*~tEdb~wiK)zS$vLseoH@Lca;tE5nh6a34R zDERx|gpPEu?8HA@!$P=~dV&J)|6H)<3Yi#pZxlqJVE+D zj{6@ zeialT_iIMoPBxUlt*z9eE-Uv(cJef2>5kCbwD>It0f)Pms~b0pz>eSdKG}x;`u;!? zb8_#lXrFhWUjodpW$e6VbE;|A0uWjE&wzN+et%mkT0p@1k{8uk(Dc)ZSb_}`YDL*- z_-_xCV*yerQhvg08d)1Ko9xX{O5;1CWNktp$3B%CwHU7$V)#Cav$*g>?rS<4YLm{C zaK`n(5FHEkY*dv`R;%_(CyW$S3Z9_ijU7I zDENIl%P;2Jw{HZQWisSaWO#(lMfWTGj~;7;iR9QW=;Y;V&5%g-*NPZ3iiuJCwcM_^ zzX+r&h&ehWh~+CQ$5RMmE5n@XGWoxZYVwjIyFI4j{Jb|=3nY+qd)o7dM(xYnPX1@a zu8!kV%N_DA9Qk_n#mh@{t@FKeDa5g*-aR6sJ{)jo#m1qlmH3v`1#)z7z?p3qMd`DL zs>ya1t7!U5mNmI_baZfgP+%&t_W<vu9hv zSzp<2FY?z}PdX6U{{fklce?)-En@l~#*hA(5*Kabb08y09a*$A4|h zYmmr)$*8se{XEp)mGl2R3PLNDUB@srDeA>dv;1Er1Ki?YYJfXQW`w)&@bN$swN>Do zj_QN^3Kxf+5#c)7;bpf~Vm0|EZxG9dVs?-4UiO5J#Qd26@*1RH8e7#&pu<1qfKx3u zPKS;8EXI`La?l_FdyYUv09QqMmGSS=#``wsOfjt(vKWHQze@rhhc#aRY+&I^Cbx;m zs=3ZahmB!E#LjX75>}#HxnwqW`^sj+JUKA_Gj4CcK5z8*t7Y;A=bq_!y93{` zts9;!N=GbUb8@^jTpwR=)YKXs( z5s-<2k-=@YNLs`)EFMq%z(w~gj!MnSuih6`vOJLB5pZn>sO!^?Z`RXB2r6qJ-NZ?b zJN*y>wwpS{Ho$SwpE9|CfQYIxN6|+%NY&d}7!{Cb6z&$G!OFe;(U{qH8ATTeH77$z zBiT90UJCAD?r1>#Su`nXrt0Yd;VE8wM5^;++3C>T5r_z;cizG?VjoBu82FQq))*Mm zPs?94>GJ#ng7rW`#Q36_o+LpA4YIkc6kh?toH>1s0%R`WWO9YT`Ye@N#f1po4Y|XJ&-mDv>}|1N(NA)M^RTDmoaa5-6|Izjvw3a~ zoiq>fZyX`UcXRGP3<^2D_Xf5EG=Bc-?ypZTxG~Zd&m`QpYQ@|!5;oQbyylCOiy);t zcdjI$>%k2WtJV7VCtU4@kalq5+njeztEh<-;tF0te*gR$#;yvDt1)ZD=LowP4!5rH6%?&N@xu9nW&khEl(0Z$;q@v(+u6DLLq)#R03UtZym)H%eLd_2I)Q)$I(b<=$_fzr=6uKR=` z$xa*jRBPpbGom;Y0awcB44Io@*|evA`|cKYoPCT^lz9S`NcUIaB{DeQO!7O-SCpGQ zmZSh~)?gMCf#|>z(f4|>^~?;LRSRirCJ_9=v!L(?JQi!$)P^_n-ivUM%%C(VG1l7Y z8T&$8g*w55#mk4@7bU|zA}H6(MH_6G^&hh-y_806rYj;isij zBenT3%=;c!v+?kO1>WO53!Qld=mpw}0XncPuKgi%hdALAKDo4dFi4C}(4=h8|U z0Ic_#%N&d>au2i}@lWPDhpH@m0$jyGM&)LwR>PH!CT;s-1al0$5n01vQAtn93$(Yu z{h+0$ljg`{{VNs$@24x`6bk9Is7bH_x4lVga4+Z$GZ=whl;()f<%C%7et%D6G@j`) zDph)Uqg&r{UMZ7%zqQ4$(s`JozQ=i`Arm--aK{H-gDlpQrY*x`e%O%tHSwn=S?s4s zrEXHo)4k!h1?ki78|YTKE-1`Y%qwXkgFGr7%r@&ipXb+`xm1YHk-@hhfofJQHat%B zA{m8Kz#Bmu(xSR@?7gm!eFxx8$>2umQ|Ir)TSBz3YQgNDS;JRGv;51@WB8CNe!+F? z1+SnFEpmH=MY@hl&UdQ4-kHgQ;pWTI$ODg-UFCT|`tU3=zMqFb;x~w1iq@d->PpGU z55%WPW86vaeV${sYY4e@yMJ{MK33(_e$1b@FZ#(Oz1+iTHZ>_49Fx4@=0j~V3R5%Q z;yx!AUXPEIm?$@(KZokb8j-YopR4pGN9Yg5bmG)P&Sjmb#^PD)&kBA`Hm66zEP9UQ zUp+QLG-j7bJgsyZu-vLBQmJ4Z_hCrwfjS2y=P6Js!1=yPb2;v(p_yv}&wnBNlI|67 zS)Y_H(ea&E*$49Sm4V;DA;Ct+>8^(S8N`g6xE#qWU*vDE6OFcHy$()tfEUEI_HfdQ zD+}OC=P;FPgX^qXG`q2Mx{3AkRk0)f`5BV#xZ04A_ayVid|9-3wYjqBR)0`$=F_c~ z9;NMI`^~{cu~o{G+=|sL>^$JJq(l~|TY35#Jn$-!1F<-IXd2;tK`f0lRy^TLk6Fvl ziK8>Zo-a(!uI+k>aoHovPZo=cv!`n|t1d$gd@PgzAXnxl-?zM9;J1DGFYzd{&j}lw zS#UUwi)#sm+m4)--zHcRkHW$Sc`NT3E1@qQte4#RlLE5%R#p}VgTYKMFCQSf&?5kE zW(~vHM+QRx3beg+s8UtbCE_D@y`szI_VJLKvgICMaHAvq_-y_axD&Af3p7!w9qgJo zd8oUDVlIJZjV_n?R=Z+ozGj~NmN{oX{B7MW))gs-YhvcPQ)L)Bm?9)W?;NXX7^vYq z|3OupUVo$`nX<}s8g8HXUlyP}*rWNUr9_2X+$rkDNnuK@XT8A;ocb+9<64(mg8#eV z-lH&RUBc2rf~m`-n-!m`hU5!X^T)L2$7+KL z@MAL07lmvkm%wTn^6oy6&&?PhCRoxPC?l>K7?+9HE-@2?~nUSEhJ1G&d(^lsa}h(h9oy>?Z(Z= z8`wk@cwPAOoBH?ZWLN0Qi z)hr!ieKi&R+=;{bJrq(~a!n{xPV!T|SN3%GcUSeHW4>+@f>*a`-I{JuuK9kNf=7-s zJpU3~(hPFVXqZDf=fIv=KQ1-we&scf1CI1{-$Q!y6^8YXJ>)S?4W!!6dg;)em@RRw zNLW#w?OE6mWs*$3x&sbbFm6o8!Eh^t74WaMU(uB)P!C8WK5lHt`6zy#f9m3)&f2Yx zTUxN3`6orKY0QvU*3ide=U#==97`Xq`bHFt!iQxf7y#0mS=!E?x&}7b(tH9t#VGGL}rTMDydDYlo-UvpkJ~d#=Ot zlk*GMGFK6Wl!U)D{cG1Q%zVc_&uy{YE&K7!!XfKaM%s5<-Yrv}oboe{DCe7>#r|YE zpz9Ats7bT6h0-iHJlySjla;Op;%8Jtg`$3mjvgyVI9`+=c}%nPgiewum6#2mbwuZ zX(*QMpcFaIZc5xYk%wqlhs120@mQpaA#HR@4LY~fkeAl${z*6IcM(wi_=K)I}d@25sN-kqm5VwLY#OOi8uFuc5<)(iAtfqzs53|BC&JZ>e_^h%AB&m~+3 zkKw=E9N~R{@Sr@+mwYp(yHj2(l?*FwBk}qLaShS**Xl#cnVz{3*uusWNIs_YNy&0C zwO6WN>5ZoffnHkuYf-Q39hTddu~D>RR@S#4 z|9PFf>hZ3G9d6uC%@d&Bl5dIvsf1VJy+ZYCK)u7LGz2OuMe|zJ#FWhp;oYv37a{A5c`nf;6!LZBWi;CH3p#=zjYJddl!#16-7Wme3{i3 z%p6s&dv3bclQ`sumbqs#OSoN;eboIfL7l|EJCG*JS9Ndeg6g`0W$aiVl$N<3X$=Y`b{5L+aRTo&*(W~i2$wofR zuS$P!9N}D2BAS%Sf|FCD84nQ9Oc3GZ*m0x*-Ke)NaI%&SQX;VU#=YMyvwl?~Y;k`_${zj7R8=K*zt`jI09bHrvMK@VF;9dYJ8L;a( z+WqHZ1n~0GQ!D}C7yI+@erZ}={x6W>eJWznw@TDMU$SQS;{K6O*MjtN*LgVcnO}v;^snR6t|lM}Wc(p1iz_q zY2(eu>o!_R2`~#_0lIb|0x?Y(Poy>RkFjUQps4d#Pja`7)^})a*T;AeCp#JtLeRNm zj~79Y+edMeQE@#h8b&$9HYC#CObr!ud+kHu=^Pk6Tj8v(w(wvJgrW`qiP-9QrJQWL zzEi?>+b2Hw*6FtvMqO`sS>t4+5%fBJFy(YN{id;mF?sp=Q7lP?e!FIgZoRwatd1_3 zlRx@?`QGaAvRK9ONf!%wEjUb(by=P^{YxEM)R-C`+oPC&j42^YIYL?1UtMf(c>}CJ z-8R{3r33k9r6r>pS2{~Cq24Jqfp$p$$&de_~0}6fsY4WXD$y>E~Ml{m|vCT!{PPM1mGiLS*Dun z&zX~(bV~l8N)D^5Pob3v%iG4y?`}!)P}4=v1lZnmyjcUu^I}u>ge1MA!s+0wGEYBj zd)O8$eoDB$bxHD92&%v+Xl)<~h=xFYL?B(%gvm1I&xrq*0%(^rg+)k5#Lb1x ze9)PDKfuW@MC$hH9WW4+v`oIx~HQA-Pyn_bn3xM z^{>i}=nMVtu&6-*HaQ5gN%FSV>Pdx~dKa`)YEkO>=_25U?i$?;m;w=Akl=IP>f4G@ zGC1`7z@NnyHf@!tEiuKrRUPzwII_*x)uYAX@j*vv;0rWTNIlER$@+rur_*~j z6!DHKZnnGMKam0vjf2k5CN6G9;IA>t*z^?~AL`*9DX(X)Gx4%HeR2IcXUNoZbV1_{ z!s4IYVyLE241I3fyU2O7thtxl7e80#p&8k_Gx=WI@dT|lZ*ghRPw~!)^NgQAS{do+ zcRaV@J<9d^&amAaWH_Vxgp%u7k(T1xtJ`I7r2+Oy6Akvx?78IIYUD8;iW9XRx;834 zx>E5GkJc;ud40JU7QI~Vy|tsc|Gab|cxPQY*}o*2Ql54l#^>@Px{Qru(9M*7FXn3H zKtWDhn@}KPMt4aS>cW|?sp<-%SqMab$(Bz2iq2WBJ-xxr+?0$?cnfl}rs<{|oEurw zcfYiYPi#5-jWtw~<+jW}jG6C_n8h;%dLIh>)a|K#$K01plgF${m`P+H0@9pJ%Q0tdBfjARrC0BveJw z4)gtactQT-3Wj#{HWQ!4?gxqR;iaC%J#eLk10Q~O zq)xODm*Xc8#Z_u*%(L~vaih8VkK8EC0doYOZ`g$JbRm5t$$)a|m<)=s4;)s0A3TQJ zTu(Y-uqDG~3WvAe^-7=Voty%6cRddDc?|EVa`N%E)+je*GvL@c<`+ia;5x~NDF4qm zBNL3+ZyOVjM`Bdg9F@6@%VfB$-%GSAagOrvjih#Ul@)D1N0B6{WeNj>_deeLX8Fcq z{FK9QXZ%ZH=+ix&-kI?}V(#MS2$qexoNSfAwwFtxhNHbGsq*48&5v-uMlUn~66)5mtky(0K9@?WW*bIt)HAM(w^>YP(%LVp zj-ga#lo8yK^D~={%u|)~0iwn7>{jJqF2#J6b~>h9iFIns1rh4>wb@Gukj(fsUjNDR z_a0ZOkzWRQr*m%B85eji)GH1P1RR{fl%Bg07!S-?{lh^1X9fLq4HHW%Jyy1+moSI` z-@XRLgV3g&q!qT+l+U=CG{!u8qCs&-!A{TFc1o~pp0wGp`gZI6K;(3F*%fVOw11X@ zo1JV?7wM+oMohk**`8L^@INO3Bq&{-3cA}-3|=WG!8IYws~r(mBihXgiFwMlet4he zOgV4o(Q}+ZL*@#mgOt#kyx#W|huU4q-H2y|@{xXYWWS3UAH1lypeRBc>9M2=pl6lGrt0BOWpSUS_;}aG-b+H<7`Z- z(3B6@Er7sqq@N0RwS-sF#Yb&JG05Fq;qt`L0w)Nq2AnmzpZMy~Pu2DCx$C~5_~6qP zM1|t3l9@ccsXi3!x$+ct&w2Eoz~1jC=~^h36_D#Ri>}s}l_DT_@wwu?BM~M3Gn10J z&-QfERJ0%7P%_~s#`tPt&{sNoV`zK!e&(a>8elO|NyANEEJJ)1wWHnXEC12PZAwFI zsPG-J!S7TBwInW2*M)jqxUhD5coPjI%R9#a9 z@I2MDxzoMAOM#BsXJ2b~!6W+a0g8}FjL|U8v$J^atidb102D(0BO1Q_;#=uhW%Lx9 zWs&A&rIvkSqAW#?w9%TUmR#OrjP4dG(;(RLIW3_=rK<)2RP1O{WPFz3)NR1*4aL7{pi6~ zvblK5*J)l*x!wPobv}NG3q^_7_rl^$@mH}9>nHP#llgJ6TK%JHy$n7#=gi!GU{O`p z$Jw_sc~}cNZL>yK%d^;X>jr*}>2{erg(7ShjDJ;oZkf)mIAyZ#(QoZowBKcsO@jz3 z{F{Tz9#ijCLRkzlp1z%7f6aQcMW*D?9QzQ@4Ub^!(cw!O0`XHAL zQwxds=srW$(MAC5O}M8}@^&FN^leGgLv!9JBJ~~gh zP%O;VR^g*Lcm@^r;pK}@kF#neyU1sc4y-LMgW0oR`ZivqaWMr2az`{cykNLwJf?uF3DWW)^7{J;nuSXs8vIBM_ydTpf;;{(Yn)g1@6!pnmC0IqfM#RhWniSWe7N z2{+&5?0H7Q*i6jUT^Q|Z1jdaPj}2X+u9|^1f~CYeAdiv}fY40Pm=Wzth+UCi`C2xD zG!YDAb+kPq@PR21jmn62eNX}RWd$Q=`L}GgQ^Yh)8%S#I8`wN9nnJvxwBuR6rSzi) zJs2s0!*ywhgbgR2rns8I`Cj4rRwP~+Pvl6uJ^=g=B^}PDHOfR;aZb*9(29dW6iexD?-u-b7{2kG}g(|kW#l0 z*;DL;MtY-lj(FxiL)MYn&v>$VHBrlEFalaBE1C9uY$U2gerUe^Or5$LLm;-yw0qe- zs`=(2&GpQubSnLk)}*~cw(;;G7NgrkTDfy}IZzj!=`L;hKq>iwvUkkKACq3ixg0rG z_obzHN(wZT1$P4&8)|(bNS@f3y(sf;j7epHN=@p;r`Z8H>&fqv&B`Y5&Id?4Z@D+V zwO6~#w`RY?^Q%(AtwzoJhj1zI+j3-RXeGvfNs^4t4{+t3ab1I?g#Aoy+SV35R|II9 zOGLMu=UC@mwo|z1+#Uj11>{F~W4YJT3d#7Jv80Cm%+{>LS`mb&%O3aKWyMpMd` zucY@~6010;YlBagHb;MLO~+ri*Adg`g4gT`9$3qo_%;F&^w$p$%cbVXNWYn;eTS;)Y4OPb>gGEjOe{>A8-^U=!Nc zx=qY&#?W7|u|iMhL&8_}_apmMI9f;QGr?BfwWJBvLUA46L4eEo)P0U_K9R}{BMN*t z4U>um8icvaF>v%z%({}T;9~dzZDW$(g^?jqf38QMpDa5MDi3zRqcrf=thsc=C578Wp=AAIIfc$`!=dFDwrrftB5DD~r+ttG zj8M{-Wffc5g`tpA*S7XL&P0+FH~NwB2sX}`#wR#)MPcK`9+w^4dcS+n6e;WI_E)`m z60h|S67{Gkc<2*FxFL9YV|JxF&|J^)W1bTL*GZo8I1LXUUzJr@4aLQ$LgSXmq8%%f zR0Ey}Y!uX$ZpO_zL|x-l-yye_l3E`qFvv=xK(d5G%SD%^ajMKWfZam#4mDNjR%o7b45E#fX5jJ@S2l`A?ICGgK^c|1r->Z-s)Ls<1TIvc7KM6^OuiD7Z;e-lygMbVKW5Zvh@h zPW;);)Y0HY?9y<+7l_LA8!FRFRNPCo(Vf)HKZ&C0V3@E>rY#YT$rhzpKd|a%Dt;cv zt1#qRztOPtg{~Z7?0%;~p`{C$tK|yDI5Iimfc<>C9g0VVzpGdg!vHe0>pa?9p3bR_ z)|IO=uXa%odN|rLiCSD6W4)?|OZ|39LJyo8%CIW5Z*l)!pPtIQ5BU~@1O0MYd%}yV zgM50=5ztjSf^E2OlFIjJzTuTwKSg~~cHwrpx-HyRIfFaFy8HZ<{j5Y(OA(8)m_}vT zBzL)(^t`y5zyF?!`AoI^7HV>(fNSWnC#5jd3mIZ1UHx{h8pUN1yBRY`P!# zv=uj84Z50F-|R3PR!b8TTe%%LRhO>k-DtIYkoI1fgtNDKTM9pwRE#D0eC=gix25hi z3YxM}wk@N7%Af2bvB?du=IYEhKIrpE5mZ(;O|6SSEDj>7^-}@nO%L=4hhO8&c5Gtl zD*JSAIU9L|5DBkZU(2NUzUxlVxuvsm^B=bm7g~J zxN-0*g8<`FPkPm-^T-LMB!5}sGm>lFf}DNm_yHXr(s9G4^QZp;}M$TZ*2!V|Ooef3q%{NBg>+&djJv$*U%dOLWANL?;cj3(OU zHREC8oo%YS4~oJTDNg9;$G9ix`HaHqnMVlm#{z8qz`D~Ks#i=U`>Tu-`m*ziQbsw$!CUg#+RCxHgR!NpH+-#>|FeY~AIdWMrCwR2oc!63 z<)oJ#?6`$MKlAI-sRKCIkJPR=eV=GM_MtMP8!$Ct4IDCjOiQ5%A-ZNKt3^^p{~gci z-?1;b8I$YFbl%TEc@mb3`^a(}ABig4>hiae)c5S~^imLNwJXvvmy$h z{N$`UPRXsD?Z-?k1%sVks(WcD-Z+H;(~uqJ$hy4DdmAGgolp+b+w{EJW9v#6yQg<- zm`}%soENBvb@~@QOXggD2E1ZfW$swBR+@nJmTl^2>@}HgC;gUy5nrrg>ej?WlPFkpUDYdcjxwK>4`^2-9?wu@LuG&kcG39f- ziJEft^$Eo@NvmAJE)SeS!-v!R(ks9gHf77W&5iDZ&JSzyQ)%L)&nFfBg9XTw&o&ig z2!Z7Mv}j6sH+NcH2W)q56(RkV`4ZkYX*aI%wBEsVc>LacFWz9$5yZcr6ZtNip}TaH z0kjO7H&5F+7vO!v4*aMGXTPk5vc_2I++VHgXlHk;$OAza{E7z0D)05C7#S!Q>f9+^s;Nk$V`whM2 zY;Y?P@nCCEX}V9^oabcmikuBsQ=gVLetxJgx>K;-N`b$b9M+uBY6l2kXsAA0_|en9 z)WOzGkv$d3GFFLIfU;<+O`b!*Gx!p+Ee@Qc5RyO0zU=Wt}dSPa;Ow9bKMQh z;wQ}nM^4Tx>9RnHG5l(qkR)CqNsDJ(ZLL`67sal$ATp-mvC#*%?S4N%bi=WjQ%#VI z8(v_o`iDo!_AkGG!*i>rkEi8LEtv8+RX}i+Mj-V3E?^AKV)`%ie|^!JTu~`9 zX{t?}uv46|DQsB8-rtu06o)TCjm&2VC2xh2*jM%mepG_Q=-S_jEE7m5Y-N3(A3TIS?x>XX2=Uv~(&R1qg zG)?f>I$8kyApD6B#&Mn1R9uZLokbLqneL0gg664#=)buKRAj%)5RX@p{+bn5eb$!eIv_IGoo_S$jvr9#^@eD)AhxQJ&>$m zvEN>fix#Zs%a+_JLe%JXFbhpsM&*WJsTqU#J9slVA9?wG&pskZ?Fc z>^q#4EN$^v-JDCp>Y4W2$z{C5xesBL%q(MGctsg4k)Npt&3@a+oAI{0BJxaKwF@3s zzOB;UM#*uDo|finzx4}58`p`eZ8IaJJ8q874wSQ<^{`M_Ec0uhjkQPsA(LZ+gE0wm zJTy)TuIM%zLYUJVYAbzVYSVT+R+D16KFk*LPjIn&oUb^^%`G0z6=|>1%75uic>qkv zOY`gp0%Cr|yY-xg!Q2a4=j$0?`4S)aFLY!Jm03}L6w~5%zI0}8;8SYpBmC&4Ixq{` zt!0r`R_|>xy;9A?pc)iYB}mSEHW`z>;l+96ftY#qBHxV0#reTb`^IK?llEI#qoiRu z6@FS~@6iUdav{5Lyv6S1Bk$St_p)XsA94Vt*0?3sVodAo>|bA$i|A$<;O=7U7LE(F zCky{{v)O-EW^`8(Ca@i}zBoE^zmnl2##E+b)w~b-rhjBMJ*uamgQ7=T*`^7`5daOv zo9hDDfUR8%E9Hz^vV%BMF-HnaD_b(79mNo_v?Xk50}^e|5P`c6QezE3GD+Fdp}t7i zXxdo3D4nytp)?6{!Y2$pBH z5I>&GNE)>E0t6B9ThNTOIO;ALNg{%hC#Yf3qIFr6m@@k;Uh2nn^+r#@_rqauYRMh#p@GM)d0Z(|{y6tQ_$#=QZ^n|ws zXadc#&%w5N-?VLXymI6y)%8M$VkU+T4ub_!s-ou{zeDHB5I^Z#h_;p|CB-FkE*;KXyUBA*>?{H2 zdcb`{L=LE@Ru$O%uqi{MdPO+s6Ob4OuSnAYc%Vu3N`Sjs2gGo{OQxUPOD(q72e;juLKWjF=x4t2?|hMGNJx z>!`#fd(a&{U{Cp|+|~3FrMoiS=ogN5=p8J(DjU9e)&%!(C^NRImwd?P=e4<7JBaJ- z>|y1kGGiDsS+i#`C~tK(jL$UK+KGXXODdY-O*XKae<&xeVGg-!LOpP#%|T~Y{~$os z*|!Lp?%)(M!Cj6qSh8Ki#*w^TRd^R;pl+&V4r_{7j(c$iausJS6hUp(N_UdrN}vqz5ow|YQ9C&F%XLy3uGS^=F1)XTNW z$s97WffI3}*#Uvz#EyMy*+}z!$dhaD34R6Vd=%BkNlbmbJ$;l=bb>dSL(P?pm;G~gGTveGRlLvDi87JZ zB@hS}_9q#~Kc}sa9jswITgP^Ihf??%@2!*5_P9P$p;^$?NWP+_0_)f5Lf)7O9#GT^ z+4=K4YUVyme^fA*Hh`kpqHfZGKb@sz3Z3{J2aVKyEi@I;S)&MllIIkATuX4B{n3&u zl_qKXNRO@Xb9{Z2-BfBCWf_mn(cqwOr-_3^>|MAIc$JNj_3{-@H4~bQj4G_V0)e=& znx!rE5jR#hy(trwcCy+BAf#et- zglBMQ=#@z8ev$>aOR+E#4s#hNR#jDNBO*aC`^eTG2BSZB3AsX!O*5C>wb`=A@e_P+!nn~; zz~Q9Pm@C##q$mQ^8gE?mAr(I8qR3IZLAv-kCJz%*P2nK|8OIlZ9 z;#(3@%S3NAN8UQcjl_pTxp*f+jJ*i?U0EB>p9rqZJ^#ZdA`(2N^<+Apfi|e!Z#Q^u zMDu&xc6!+&Trq?d1jOFqGCUAwIvudVk$r97 zd!cesz+PlKcHZ4E4W*pL!mUp5zIw6>q5=FJ!atWdQ>J<_k>`0DJnpf>E)2`Lj~^u~ zTpB^{gf{){#Toy3EbhAdeRJ^0r2kF*k&+7_kUA`=NSh@V*$sA3SDarnLE20Ss5JdG zvLF(iLiJkr_98>kK>tAifFZjlNa!~~?bDf;QTpJ%1kA=Wk}6woocX$I8I8EVuO3qA zmW~t$pJFA90O;}6n~|K%!cJ&WG&v^Q=&~`G<>~Tfk&~ebSMVpd11}}l8RN}kC;K6uh}kP?V5os`baw>tKW++K&w83(r!WLE44*NpJ@{*! zaTfb0j;`|b@+*-?mL3keqEly-mhuseK3OBQR zf3W3TU>^E!+ODJVMU|CM_WucoDVb`cjMT$HL)DNoIhN-?8@60ZUTs!e{sFTqy?G!r z-P+X{240^Do;YtmrLo`DO;{Sfi?uxViFLzAn~Ae(MGo{M)2BjJO=&HVWz?thSI;?? zfZY+w2|-JUM&YA+ybinabC;_jpuuaz^0(>ont{2w-pdpo?dciI)o z8Zi(FLaWa;9v0x?LWl_-q%hf)`+i+pYISl2edY8Pso`h0u-fNj7i|o4#o>wGeYeuC zbXo_>^My+^zN4$u1zg$vmt2I-_EEylLXyZOpG?O!W_JFGwVCC7cEW;Yv2q1s?In8gL*NbbX>gBueI07elb4<5=V8J==IsV(>I?8On=_3XRy-A?Bt0li?kZu=*Y&+Bx>8E zF@0(Jhaj-2#tvRH7E5o)F?}fRksUci{5P*OY)*?Wrd1oXeXY07` z(oT^)ds272*K8U>=X`%J5$FE~j=vtI^SkJ%!x-4n`FTAaxMgSB-~K@}TOd#rOD5I3NobB9(L>Oh?DU_e_wELWtTxUW2vif=HRu32cp~x*4Dq3&q z?JQt163?=oH$P~P$WrucF`q4ujny$&HJ6gx%8zuRbn8?VIs2+`FCBoAS@xzL9$lC| zVz;_9Q2YKk^6_gs^Yl?|#Xu9GKf@5`kK5qKIJW;JPq$%Paj`K$8!8r~=|xqV+NVQ{ zcBZ`K2ixrTXlW#FFKIUw4@^ogyPjkk=nafT2~w-XKIPq&F1IE9l2-FMI!)scr8nd> z)mx)E$VJl58yHag>5N1L=!e_=a4a_m0nRWqFXM_ROkR??d2?pxgzj?ccKbwEI5q?- zqCC17`9v{$Xmbj~?*gTWT@t4 zw!^vSLVJBlE!i^fCH!~kH4rk^2JLy+sVO=b985u!+7LyaK$0*GQ)aJlOSfE)plduY zP&OO6O03W{Ctg0Z?i1w?&|&0hMVM!klSi6PNg8X5$LM0OG6a4^Cry_?#Ni3;kT$ehk?wMZ{8dBQ?$b}5>Z_MqTSQWqSn-!Oyhd$5g_ zMXJ_9>_*b*?I#~I3Mg185UWDPBE#R`-fzbk zgHh^s8hDyhP%y2d<82i1q;_I%#=Z!9!bq72V8ZFXH+zal8^`EBr_U~V*yiYGd_9*GP6?ma6!6TbZaeDiDL zt+V{!k3=9hzTwwPcmE;f-_!pOLy`Z#p}NN$u13o&BJfkM81fxqrqybTvjrIy!0FB+ zEGr3Mv5oBNkM$o}UxaDKvFlO{FMEf^lF|q_?2)ipP7*^oIkup_)_2-lq<g}B|E#AzjvfU!f`e@Co&5}&!l?KO*41W4 zEqm(^VOh~Q!GA%S`_r}A5HmdFPipm)7ZqR-A>1;_@XvEDw)ivmvzVjmBA1y;ha>Fh z<1cd4;RW{rT0wX(ywPe3?nbVC=TH@j_gXeyc+|2btx%8K$mq?c)eXz~U#HV8yOfvr zhf5%jXH$3mr|?7jH*&HsA3h5FN+`QaEh+n?jr7N?xVTi+!E+X`+qvT@7OfNKHUlXs z^dC=Ti6q~9aXgKG{|+Y*J)ZEX8VKa)k6!fBC>kec26iQ4ktGNdwPX~74GIDdSnYmQ zCh)-2x4DAAOYd<4My8-=%lrneUi z`_4(aAgfMOSgj8x---^;-tSnN(O}1xZ%}fDet?xapS#%R)aLZ!iEYayduo{0%@oKd zCpElV6oqC86c0OQWSnebKV*aK;%IoRA@y{PxQcf;%FAhgp zyBu-yz1j};roec-B8mb{(IJNHW)eDF_U&=CdsH5SE_yostlMStN;-jP86f71`E%2^ zg%loXTkyfE8=O1dg`Aiph}MPgL*5LMD){O}l;2Nk@xqqFS^nS#Abai-Y>5k)Dr*v4 z6%_&R;=vZIbS8<^IAT0XqzwE%`UPBtsZ^rH72U-mrZToZrRZx#{1OgSguk~$Y&akL znd{NgNm!g-mliJ~eH`|Uf{ZP9YE!g~s7Hx|B?Mcq@E9#Y>(cj<$qbh9S-S6JgYR(L zUQ*C1kd&6AVr?>~t|c#`MxsaVJ_*&l z5$*VSC2y-M5UFp#54cYI^e9W4p6xoZVkhLOm@_B*T%^zYfRgJhF6N_)(74ral%=Fw zguh6KssdYadI;6>hbV`-M=^(PZ(VSvH!$Vco)juIKEITZyOyq>KI;G3Z}WEkk=Tvn z%i#>ZZ~j>(_@lRpQZft9!>JRW5!v*1h#jB1LH;XI!GLSN%DB$4BjfYt8>}uC%~wEU z>>LgkI`GifKb<-@^7ZhCL1LC7Q(sR?kzk)_EhsFhkbqBk@ABO4)P= z4}^o598O;N_L_;MfivycFd(z;sn2tKl;p3JLTK6Sga+3d^d|-QnFTyC+V^=5v~c@x~f2i=$w+`TqSQ z&@(HZGb`yZ?Exj-&cj&Cs?y8u^C(%=kzo>>5eFjtv#D?6MwRR%6?XH;Z;VI1KmBU=F6-w=plb#blvD*V}1+?3kThg|Ip-2eiknTClDCYDTBPr5y}b zFU_1iHxx^4$h0fNgj@x=4=qh2!x%Gv{b1+m`QP8)q$h@IXY#ws4zA)fPey&V+{95ZGoZwa&&FZ*J%CewGBMO8Kb z`ppB8))Qy6MYQna`zI<(Q-`^g&MzCj$QIKG58ORMW4f6GRZ^5KpvSe}Ge1Ch6MWUC z3u-R3R4%p03x|HBq$QwSj#CQ%nb|#Pik}%nKkeW)$t)J5)6R&|{xK3NnRrSJJaSK@ z8PR^1Bz-E0sovEsn$mO8QuMo&az-7_tiP<$AUq>O!(?o;|2#H}t(wk>E2&Cb6wA&a z_UpQ>vomffCSN5&OSBwH6jFhQ3xBWE&);bY)o>><@R`X<`l@Juq2X%xk(|GTU}{5n z;#bSnp}<28XhE&LPSJT|;E@%%UxWl!(JE`#9!gW~@{K6J7?CR&E_fUR?`D;I=>!(O zm~Wu(=|-=3Vp37!I|vpddmNIO$|usM(BISGB?1zGy=d#s()`V|Q1o`+&06tGEK_oN z^cVc)@$#b|<3vKEXtpXmMN6#M6)rJWI?i&7tpwwtKX$rCML%P-g>rxUK*k4v&g2w2 z_x(6L8aJTL*1Xywj|6>~=Rd{hpm-+7c0YM=YS~V7WTpSZb397! zn%p6Kslr0}+LewS`tKv9u;v)(xZ##J7V9G61(4li$+`A$A>97bE zJK}CJG{9|Cr;+(~n{w{9v%^jbPx?Q^5^6y)kctD3OYa_j6#Gy>sR80VbdPGqtro2# zPyP=U;IJv*?~>|(LqIy~@YMrcTgf2uBZ z^||oXpkol4ado11pZE07$g675C@V+^xh{1Sx-4(fmRJ)JO$0EqrK^F9F{KR{J$uC^ z(dPpu6D*6Ke$H_!9$^>kGToaz`LNx7gPqZ}Po&C`PNGElVQHq-C+9&o$=fXm)4+PE zQbK5wRz{+tc)x`Q?PBL0vbl%vOu(OP@D*VAo3s8(4XxKqt}%`y1?KD(%XmUvrqk=; zrkVbv>T5){zo?FUMgL^X+{p<{&~l?@reA7ku71~y&mEeM-k7sg?=@;<+fv@B_-92a z*so5s-Vr?Xy%RkLW*ZGr3(D!$pjcmx=O5bMgL8XKA9oLcd*jk;gY^f6wQsiM`S_^D z2E^-zMXP&D#j~DZDqK>(?7x2@Wju<7FDUm31#f>In$WTmHqVdl!oWWrs-1)n7j?Z% zYjF`!_BRu?JpPS3wmlF^ZZU0RpTBEx>4r>4yxhPbbejMum|yh97-s{Arg7W>na8t3 z>2o}GAKUz%#qz0`8g?l^buTlY2B#3u-9HSKG-z#79eb}cLYR){8I)@Jnw}nN23K*p zS~-h$rST;#W1lH(o%FBC0`$F9GCeU&duw9sQp|ApildsAiw1m1UT)}{Jw%@Jovn3v zvDd4q$qn;85%d)ohrmzIBxHpQYPpyHNbmXZYmVVsB0{o&tywPa@x9aVbK4V~m%lij zGT-F)>`RP>9_{*c>x&5RLarka;A8NzyJquKpVz^x0v+Fuu2 zMrHbkd)e^Hx$v#T&-H9MFP*iyg6MZ=0G#2)n|H0v)0Hlp16+q*B!ARI zHZ`00-cb?$DkYzC;Xk{*R+JA2+OW}Tex0+=W_rRD9s)&1jwe$y&fp82$pXJh$o+U5 zgzH!n6qljoZclW{ecWp2Mu#=1*M03fRb;RdVeF+=S5F`_W4|ryK~F@gESJ=2Ee&Z* zqr~*n>WN{+ObyAe$QjFrZ&&_^5_a$2Q&5a#rji@hZWpG-74{4xfgyU}X$>WZMQ=(T zV^;$PSZYl+{0XXNZ=Eo(U`(f-Og00o8`~{9lGGvu4ko?JKAbp$ z)h2?2A@)0uX=GF%mCVo0Yt!fLYe zY0*K-*F;LCyxqvcD%?U2y!5H>j{PRdM?fiYV+<@D%hMy3{*KGxaR~WVltLOHophRJ zQ{}A_#cLV){#X9v^7Up&1o%vPwE1%r)B-Y;w@K-KcIh`9k?3MM3Yq2e=R3GGL~y_V zlhSSGcxwsa$6Z8f-I}a)d_v=s~aZ?skUKYs3<>iPrXUHiqv&;UJzEVeG z-61xw!1&!qo807a%f74NT8c};w%jrB9FOm-@DoHbEi8k`&nrncX@2Up4oV1HiEd1U zNrN2)k*y#)=iND7%GbQACi4m2hg?sK^T4^sQHR7SppDMObp9}h?I=sTaClvpbet~xGrVcR5(R9rk7lP(JuIP`i&^tGv|30H^%4IMi0H5 zO>>$?bWdn1l-P26mco7sIpH4BhO~6&?{)a)`!*%U8t6xj0^|8a=ZTta2$D_E*tJOZ zW-Q2pt1WA;{3|%y24Y45p9lK7cUQV@N#T2zFO~96bepmlud~9PIlP4{Ncf#UjEQXf zT`&$SlcQ@mNUbcG7FngHf;UC7O7?&B_a0#z3_EB<=II{pjNDzhxocp*_@==+O36@} zd*V!V2RA?cq%JgfRcdOO-I>Uji~|?tdIq-#uIZ z`%6i^FYo@fSH)5N@=v+a=_U5R-mC1A|3zcEQluxk42+B#u2_E$5}dKPxVWC{cd7Sp zNl?8-wJMeJmty^jR8~%|X!mKkyr$F_*u_P!$;8l~KqHC+V;4fSCm8hV$|L?tq5jjA z&Gc`Zbbni1|8Ml}zqr-%LBfXJxv;d%tD8EI<26~I(s<99H6y-XB(0yRw{kVY3SsZ; zpR)gVKsK$Nl|mcALXh?>T1&>S>JuS<&!YwmW6E-Ie#$CW&z&OB#a7lj`qhHkoHgye zODR?P1nQ4Pm(!e{KrjrZFdT1!kFbXIN0~Oj7KM8;475QTG^?HMQ)4y|R}Mz=X-Z_} zkwAYeg7D@S05_)I*>=fW@%oxD#G>=*=m<*&0TmPih#zi_ z-vrEPc&u*6^>#i5DnHLzDd;5^&m+lxaD}1SW$-@A%^y1~INh<(e~(WLFApm{zRMV6 z^35^Mo$xW+WVwwpjL*sSr(MW7(24Nx)9d-62YFep7h@7JhM1s_bxT+=)tt=`0htoI zYrt}nfXPiIR&CW1WEKn*(MZyYA500AZfpvDNk$N6t}tnOOczaWW8d?e5w3>e(fg_Dt84%7B) z3x`rL$uFP{EVO4We!2eqoTZqJb;1SjFfIF;vkotI(B+fn={#O@;ISNJQh~v@?$G(6 zqu=-vw5~2G?8|KeV3Yk-9z7_NMBr)8Opa*?|Fd~hva|OrJ|fjc#`i(~BD|y-`~sQA ziyk1{u{R7nj{afrY!}O(d%XPbr#sPq^jA^9hC1>Bt8}ny;qTucA3b`+85N;I8Xg|b zxs{&%@o()Iq2MHxh0-t$NhK3y`ACDpM~saXSd6A?w#LTC{%z*V|7tS%FT(YI zEi?6KOgu)S?SRFBi zN&Q?u^q>C`{>{#Ua;6u;9NbQK1~IxezxHEVtXeOz#B`?8<8YQOKV5v>jw0*1S5!R=Vn}NY|A(l`l|2D@x40Uak$ms+PhVpUnCs_5o^9TBRjcx;x$Iu zg@bY;6&eBBH2yu7&JSurDlVQ@;rr118>uli|+5Y-C(}wY6@$(i{5qIiMHr4QLo2Z<@2?Ym=JWAw@qw>sFwlGB&QBP6U@%jxoht_@CEisGw~6<{7eP$o!U zxfTdEZL92`lheDR$7#6l7&;es6|9ckAo+sjpaMVUx@zo2B}L}3ua1kQf0_$_Dud@G zCKBUqS|)Jq?OZ!fY1=5NzrE+PleX~rPGwoyr!wKH7+AGArcI^YoT(psQ{z8GHF=n{ zC$x=6b70(Sb@Wbl{(*ru3=ihl7$&{c*^OndecaO~7CsRzpYooBrf5hPHu(fQx)3z; zga_*{M!?z5X)jG6;Uj**f+I`0#6{}lrrEVLvHGyzL_MF3TbJpZhelclgcKJ2$Ju>V z-58xm-jnkSa_fiQdk}9z&>}FfX|V9;vqSf7vif+6-#@*DvOJ|eR9yG=H-4RJ!M`X; zEMS$1e)39QapaumGVS%@@>WmVH!(Ne#Cj_ybfV|_)R6SyHQzMLdB;MoiRetQRpvhv z`u^G^zzLkVwXVMES0nZm!bUBHM{3| zE}_&BtZzL%M+{s~EorhnkOkQPt`62wjgfVJEV1rCc2A`XXZS(8#aMN-+B^6AxIQQU z^M@b6O{SWIFVA)5nANQ_o>@YfjC1(Q!hl2f(K%fsO%;G|#aYB9)0Js8{Q|9pho-0< zEI#9rQU7sXCS~`b=is3ni;`1}?_j=Brhu6$Ygh$);!I%+k9Ho0fSf)%{G0PG1*#920K6^?wz3HoBch+up)o&K8 z{Jta6CrhoV=1?((tpqzP+1qq0N?f1r&|lzw^-JEjcGE5 z$4Zr0b=17lgMYf^==<}ekg)FL`O+tT&m=kIk-##S#JY6*94`0c6BwH=Y$ zI_+lC{i1yE*|NZ#-;)NO(kM2U=QUz0ip8)=L$`lcyPCA!Jfnt4zwUo$&LKp3Eih8` z>tI}-_A|sxo_j#}l2)& z<{As3d)|xxymIl1D1|xUjC5WbgHti~Kc}p>3p?f_Mm)a)?c?DWfAgxOUi2`> zf2K)wFltjX44(aaVaZLM{~g1{bwxjl?4j0q0{)DNNxHLn-aAPp&5LqJ>BF=Mu*gQi_p&6HN!mo zJFrU)s&pTH{`hX5H2puFZh7;Lsvt=|Q0$|Sugl)XOpN#v{B*j%WlnoG)SZU$^3aYo zyC$&=Q^ly^>i^;GEra7&qO3vNB1;xnlEq{(Sj;R-vY45fvBiuQGqYNFBl%*@Qp z3}3(Z=FPt^Q7tb5MA=PT}F1?YZZ+P(i^N+1PlG z9!b?j0>?La93h$xdZenmlJf^%2Gk&TirZ#HJ=2muQ+Lv58_S^en!^s*lR)xL$E8V= zpSX1`bmc$x#)1yCeq?roeURxc{?rxTf``Rqt^%JMGg8;-Z@ocP&J3}V>Z)tP3bF>{ ziMY-W-!e73SCA5GPHm9uhi*gg82yjC#ghhrOYPwR^m6h?Rb&@Pl*LAJaHdvoLpYj) zG*g6Bjd2Y(A@Ea$EVjep&)r!jQNSyFgQoXywC!~h=k1R(OAWvM4P3vW2yEjE9xT@~ z=F5pL4XKT2)TPyaf?++EJ*qqG_mO6~U?ik7;$>=E90DsvwNE5ElzJbMdgSv&MA+|F zQBeX*^)C>@b6xNG9U|`9EHkIjTRTNK@DBCL^#{u-wwBKIUG-sv`DP;jOsn-)0iXfi zn!lR&n+?3^V|t(su+YvOpBih_662*a4h2nDU7ipu1rd-?r$=;`TNbI8Q>3lI*@MLh zX7dFEr(LvXK{YNc3OY-_240b-%Y?l^+jeVScL-n<6{i!zq7rcFR%%6HWH>;rovD=oQ=a5zENL_Poh>>|sF3uw9Pai5-|lz2523<^w9t#H?im|6Wbs;^2k z@d(SQS?MG6;mO%K2Cig`oqXLNMlzk2AA4Tps|A3&5Fa2`S z{imr`es@>K)fxS8^%S|-LBP+gNFE`-PO2&E)x};$tl5gI5QA+Gf$5*??N90J!^)(; z9sYjSUZaQQ$T29B+_^uz5{!2VnIlkUc&^rgp^#tAeW2GgYnQ|<7Q1S}bjZ7b>;|<{c{u!D>Cow<9&R0IG6t6~v&jE5a}r#8m{ItSb{4I2!JGPcP*F`T&mc0X~mRsxBE#T6I(!XGivv4u+|3z_z`3P*v|6e zciB^4tfmghO+Rj2t6+x2TdN9-U4k$@GOE7aAMT(aF*-9$#6KUPF<3&~TIs$TZ{H{h zAPmP+^dq+CGn$;NUtu(6)@vgd(|F^^^u9vyl0p3A`NP|5Og*`3-6WSclXshsTCm*eHVY*Q%tsrt}#2JNCt| z^i7YBlqI|9*h-(LHsG|s%WRb`@Fx!DSypXWSf}T?GRn2YIhj?#r=s~H214G91zIL5 zYvdS39;fJ6C}`=snsZ$i^#)iX&v&^GSNHheYlI;kdBg#8Rxx-XVMXDiM`EVeZvNj) z;^GW+c`AKJ`;TlrqbNLN=4UzEV0qmes%tK`no8?6QRdI{)*}TG?meZE5iX6vEv=8g(Wx>^FghmYzIe(&2rUMI{ z&GU_REBRki60uiIZ3OpVB!WAKHSDI#5%Qi5OG$p0(wDgFF6@JU-fOcJ)OhR7&SMsGd($N~}p=K4>BB`RtU*gS0F z=t77K`p!-xq|55Z%Q|qUW!Jj4JifGC-&KK3;*78%fz6O(BfG!K`SXN7n(*2|`=cCL zZTF1#Gp=5gJJp{<`#wCS;*JmYP7)>3BJdfVHCI$pmf|DdW}8jcGR%5Pg-RP6IG2d| z^e=Wl!aV9W_Z-4n&qwryFgacN<(@v8EI+Qv5zlXt+sixQc zg_2mr#Zn$Dz@PhZEoAB>EePZ9p8|G_Y_chWgYEmKm~sgt1Uk4uKx%i|qD-Ftr!VGZ zaaxf3y`9$_U35IOMhq9-d=uA_2rA&@-Qh)vRfG~K_~zCdk?Fvo%yAB!%vNnW`LTK- z@|r65d6JITlloZ;XoF?PUZcMP5d)-}XoO>NrdwynQi9tzBS}$oycw)R-X8@Ag)%j* zq0davYoHy8aH$ua{c+#@z6G$5v+s~(ailFMVM?EQ5ty%kg~x4|l(2SLQfDw+`H)Rp zJPYfJF>l0Vc+_C-c8wQr-H(YL2-Pf=n)NZXK4 zpL^)+y;o)OFmR@1(voso5IO=azB#1(_CC^}6_BJpyb2 z(ST<0LQpbz&wFw%(Gs%-0T|K$QiX}YWRKFAlLq;tl&?-5H#V`Dv< zu(3v<*hK=_o1=J=P{|QIUU)jFNh&5HMQk8xIB$#wUY$`1Dl@H?qx5cXJC$RFoF*5w zqVR_Myh8R1!?OiK;Vd75*fyK+1wqGI)-ywE1BnoQV7Sz5%$z0|aY01jir0w@9eJ{L zP3Ysq4Q%H+aa{UM4FZQeTeZF;Rl&=@urG&!oT!&EVUp5Vqb70~!U&32`H&fhnDtv` zd-&whv7g2ptX-Ilx9$Rm-Od8(c-CK^wwj<4lAeTdxn`^g!MhqSIU+Tu@9*C&+GMgT z`I%DP67lL+N_BUPONLB{*WDdthPiD(cW=``49viGFO`0$CEi!kPdL4)q2y77yd-T|P9$9ug zR7?GR0uRQFmUzfq+OcZ`kbr%i!_qFbf+K= zab$~`)vFn}jlbUy?1HdRO;PtZ`xaZ}1Vwp=c_gASsCLhQN=EyA^QK01tdux6(~h`f z9D}&!BUlop^08Ub4$tWdkdc$1BEONv9Oa?Ff>Im+^+Hx2zSQF&2pECiE4DDE(1?wQWJSWG{MC$_a z>CY`A!+d#$wt*+Su=&oX-V@Oh6DZD4?w zVHw^)_RbRh{TwYHW3kFL@av)r=Bq}dK^1e}{RV^w!@b~|>bbD)XrhA!LGRlxX}o5% zFL)S2Lj*?i?rY??@IFA2l9>`61)o`;!3r9-)7fIpl=pciSF!TiOkr4!R#e)>D0*Gk7oQaf-j)u5VX7 zd3Ngn{h6bN&kG1l`1H9DP2yS@wv?hp)BoL5jaz-%eu6)rCb{R>qUCcDszk$aNo@Uz z1JE$0+Kj6}0D6k`MTX|D^2a^9Soy9YwX%`_5ypToi)fl#l<07ByBgJ7>|A+IgYe5R z`5Bi}>iYc3v|dCu|NS4d)Zzb(mOB0)&{BOcTFPKI_nJ1po<7!hLnMytiuWHS#;hNts!CIO!?&}tF;aN4hVi+j zfP}0cpl1t4tzJ#H^xl4BtPRf&fGH5CiK=@ez`lnh89nDOB-x$1(2lTM^KFNJi!h4+ zn>i^8HTfUZ@|cqJ|1JRf-;HS0hth5|h)YVA9-#aa(LvGI*QaXoFNquuV)p?%xlr1F zR3&MnV`IT^hGqg77u`QT4(rWM$ogx35qYCY7t~}+G@5IHmsIe#y!{l`+I%}etrCcZ%4^^nh!;j2t=zyjaxv#+ za%aC3m;Ck+W(nZ&owqI#K0^X9SR2EU_8lTf8|hg+dbfD$81_0y{@vDcw|T>0jmvYh zojw^|eHEP(E^-E^E^hUD{LtIs`vsIM-0jTr0F6D5KE79|X5IYdLu2MV3dwYH7-!@ zR&RtbmUT}Jv@$wAoANvvRCqYCkqLCRp{p5fL3=k4x?RTn1OImvo5=f*1!IpI#E(s5 zs5ow?5mj+7O$=Rg=XFu%?|de7(5v1X!ntj=_+6mfeEs+4EZOTJ)u($G5s-k zXZ@gFSujcWuOJ-qqo-!vFy%+dsl}U4z7(MS47`sPUx1ikY-~BT(%6Y&>V-nl%zpSX7x#!Jz(tvL( z;A7*?21o3bnm!>SR_UIY!K7>O;H6RKm?l)AilP-W-&pRtqjrd_{Y)_tKLHF(GWN9trROp z44%QXeuC{uoRW`ubj*uD)qn}E^1+0nzlY})c{7>dN~{o!*!7E#ZB*jhi88L>JKI^7 z=knUG*9XKXUh~drZ2tLv0Lc(VJI%D@{zKJI^{4ks$G@aBf`c*h&9q#;%ZNgKI;EBH2I>$4wMGteFBn_l0r#WNye#Ui;IghrE1rq_i|T% z8yxxng}LzGow)xS8pK~yW4$|(MuVye^6zKH@CATFt;xyB|4!l^nVf%CcZ?&)d!h5OB~`UVEBFW2dOt?0w` zNsU>8)UWtjur`b8w7Bai$3)sk3%(hb=he{LY_nUn#*6?-oTgfw-hTJD)ctbm_I*rn z=gCO#haln|u$WT0Q>%z%{xxgg;LGhXeyEJ-i5YLUn^co`M5_HMQtyhRv#FE^wXc9t79V)FvkjGc5Pt>vojcv)_F=e?^!E4rmjDcFTF0;aDq7 z3H}QyZ9mby3bMs6GHhc{LG6X}1`k%d(<*TM_v)FctbcH*ej{bw|IB&qCwU1h{N@=t znwA>nJ9>L5dYD>}wDY_w?ce4*22?6ZGF-oPp2R`is z4_eddkKYPTc4y}3L|?twv^yQE`sO>Cs-dm%(X{?zL=0qh@0x6-=T=<--5Wt77)`fr zegy?(x<|xThf7Z%+t`ge^&|}D3a2vpUM>~1G1oG|Vzi78W6r7`e`=reNt3{2PKLcX zZHI`cJdJZMmy@~xl3g5=sLMMDlgP`Fo7QhwNf{q)zKw1!-r*g1Ei2AZy9UTXG*7fX z6}2|+`7u))Eh>oy(VLOZ2DQp2Jsml zzx!QkaZqggq|pk<8_Ce^Hd&^i@&u3DtAMI(^Kc`X>^NKFaEv)z>0M^{8sFcTqjSi@ z=VZ5)!paVciORs01b6!(mnAjo{JyNmm}^@sZ}J_4;oGy3JQFo=;qMe^k+e6MP` zdyK1ocGACX93ii$0F})h^@;ECQ-cE>HvlK>`F7?07o7kuPV&>-Cq_7x>q)L3l=Xe! z{G7G)=Aj95q&$OtVfK~jg%&q9o2S6HAT3p3R_)p1XNjv{8Qx}y8eh0<=4%W<3@_h@ z`8*Sy_t7;sIa;nRS-6+zcd{a99FiA`9riO!&hzbuz2FPg1TKAdsby$db61_p^$qHw z#uIrClowqW?GrMuNzWw*vj!*lYbz23w~EmdIxUk81JzZhFz1bcTe1ube z1qSIP507Qn!((}}0R`?ZR@>kEe&`tMg4GuuF7)2+*6i+{o%Ze+oTpo@IZI4^CJ}-P7JzXDi}xy!FmElj~X(Ju2#@zlWca!6Qc43BSe?! z`_HuVFdw>k0;A{8y4K5X($zzcQC^N#A)&MuON;+K;!GX$hy^CdcQL!~7|F3h8av4XeetkR|va9Hw zn`*-k+^#TUi_i$4RzsTZUC%UKk>Uzk2D2^xNKLciW;aaGH!3 zF7sY_YjZVu>~$H%QegKD;o;t0%u**7q5DT%wK+u4C2_e1*UJ#7ozK18e9Cj{%Zl=_ zSk^eAa(hd{&S^76ODhBqj^b{as~KcK-*mGDWMr3+q`C_(-M!w*QMO28RzJ({{H&kKWf$aNR;xyX<+()DO*?@6QkcS`rYCzna7jbvoc!_c|8hZ$q;)c;6;vy%*61nrZbm8usU5o0mu#&?QbIQ%_)acMLIcF)Ty(#!66B$x9tk5Js+ zZ(J)6-gjeM#l=4?Hrk)zxH-9tMbAHlW>XZZR^FpXgp--;ALWi1#vTqP*)mEqn#FNa zk}-r#nx0@bFSx6GMTYvt&+q2h;ePJr%^klMj%Bp>GG|9aaKEN-x9FbfxzxJ%Godp+ zF&3Zu=19_sPPO|{(cW#uJ!X-Yrh>jMz_2!JK8%j(@?yA@tE8)HKGVf3R;#AJ$1sUK z(BDh+s%jf|IK}Td03y9eeuF`uBr7)j#87JY67X;~?X0FsC}&hE9Q(7l*t!OnPo(YE z7MCfs(A#^UmnUezmD!*8xm>>-cf12Rv3CC?ZHXGwn{L`OQH!TCMn*wFY5Jsf=r~Di zT0(Jv@2WY5Z%!+EV`=xe!8>&l7;U3~BUX*YBXZNv-~|&BgvtVpM>VW3*ed?nXN^9Z^y1jGmcImv$f!tLi;9}OhP

|lEir^;^Kfjll=?Gvp`VpBL>y1^7LV-+Ifo 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 literal 250156 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

1^N&%|)f! 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

%PCB#6w=O7YRIeX1c`iv})T|FWi+Y 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 aAD%$Cki*}F>vFIFhcU$5py;}D_`d-^oiAVj diff --git a/docs-develop/core-services/position-service/index.html b/docs-develop/core-services/position-service/index.html index 3863d82f7c..1d8005115a 100644 --- a/docs-develop/core-services/position-service/index.html +++ b/docs-develop/core-services/position-service/index.html @@ -4324,8 +4324,18 @@

Position Service

  • gpsd - gpsd service daemon if is available on the system.
  • serial - direct access to gps device through serial or usb port.
  • +
  • modemManager - location information retrieved through a Modem Manager controlled modem
+ +
+

Warning

+

The Modem Manager based provider exploits the Location service offered by an enabled modem controlled by the ModemManager daemon: to work properly, MM requires that the modem has a SIM card inserted. When selecting this mode make sure you meet this necessary condition. It is possible to check the status of the modem through the OS running the command mmcli -m <numberOfModem>, the status of the location service through: mmcli -m <numberOfModem> --location-status; while the location data can be retrieved by running mmcli -m <numberOfModem> --location-get.

+
+
    +
  • +

    modem.manager.refresh.rate.seconds - refresh time of the position when using the ModemManager base provider.

    +
  • gpsd.host - host where gpsd service deamon is running. (required only if gpsd provider is selected.)

  • @@ -4333,6 +4343,9 @@

    Position Service

    gpsd.port - port where gpsd service is listening. (required only if gpsd provider is selected.)

  • +

    gpsd.max.fix.validity.interval.seconds - maximum time waited for a new position info before considering the fix lost.

    +
  • +
  • latitude - provides the static latitude value in degrees.

  • diff --git a/docs-develop/search/search_index.json b/docs-develop/search/search_index.json index 20ebdff7ce..2c376b7c65 100644 --- a/docs-develop/search/search_index.json +++ b/docs-develop/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#welcome-to-the-eclipse-kuratm-documentation","title":"Welcome to the Eclipse Kura\u2122 Documentation","text":"

    The emergence of an Internet of Thing (IoT) service gateway model running modern software stacks, operating on the edge of an IoT deployment as an aggregator and controller, has opened up the possibility of enabling enterprise level technologies to IoT gateways.

    Advanced software frameworks, which abstract and isolate the developer from the complexity of the hardware and the networking sub-systems, re-define the development and re-usability of integrated hardware and software solutions.

    Eclipse Kura is an Eclipse IoT project that provides a platform for building IoT gateways. It is a smart application container that enables remote management of such gateways and provides a wide range of APIs for allowing you to write and deploy your own IoT application.

    Kura runs on top of the Java Virtual Machine (JVM) and leverages OSGi, a dynamic component system for Java, to simplify the process of writing reusable software building blocks. Kura APIs offer easy access to the underlying hardware including serial ports, GPS, watchdog, USB, GPIOs, I2C, etc. It also offer OSGI bundle to simplify the management of network configurations, the communication with IoT servers, and the remote management of the gateway.

    Kura components are designed as configurable OSGi Declarative Service exposing service API and raising events. While several Kura components are in pure Java, others are invoked through JNI and have a dependency on the Linux operating system.

    Kura comes with the following services:

    • I/O Services
      • Serial port access through javax.comm 2.0 API or OSGi I/O connection
      • USB access and events through javax.usb, HID API, custom extensions
      • Bluetooth access through javax.bluetooth or OSGi I/O connection
      • Position Service for GPS information from an NMEA stream
      • Clock Service for the synchronization of the system clock
      • Kura API for GPIO/PWM/I2C/SPI access
    • Data Services
      • Store and forward functionality for the telemetry data collected by the gateway and published to remote servers.
      • Policy-driven publishing system, which abstracts the application developer from the complexity of the network layer and the publishing protocol used. Eclipse Paho and its MQTT client provide the default messaging library used.
    • Cloud Services
      • Easy to use API layer for IoT application to communicate with a remote server. In addition to simple publish/subscribe, the Cloud Service API simplifies the implementation of more complex interaction flows like request/response or remote resource management. Allow for a single connection to a remote server to be shared across more than one application in the gateway providing the necessary topic partitioning.
    • Configuration Service
      • Leverage the OSGi specifications ConfigurationAdmin and MetaType to provide a snapshot service to import/export the configuration of all registered services in the container.
    • Remote Management
      • Allow for remote management of the IoT applications installed in Kura including their deployment, upgrade and configuration management. The Remote Management service relies on the Configuration Service and the Cloud Service.
    • Networking
      • Provide API for introspects and configure the network interfaces available in the gateway like Ethernet, Wifi, and Cellular modems.
    • Watchdog Service
      • Register critical components to the Watchdog Service, which will force a system reset through the hardware watchdog when a problem is detected.
    • Web administration interface
      • Offer a web-based management console running within the Kura container to manage the gateway.
    • Drivers and Assets
      • A unified model is introduced to simplify the communication with the devices attached to the gateway. The Driver encapsulates the communication protocol and its configuration parameters, while the Asset, which is generic across Drivers, models the information data channels towards the device. When an Asset is created, a Mirror of the device is automatically available for on-demand read and writes via Java APIs or via Cloud through remote messages.
    • Wires
      • Offers modular and visual data flow programming tool to define data collection and processing pipelines at the edge by simply selecting components from a palette and wiring them together. This way users can, for example, configure an Asset, periodically acquire data from its channels, store them in the gateway, filter or aggregate them using powerful SQL queries, and send the results to the Cloud. The Eclipse Kura Marketplace is a repository from which additional Wires components can be installed into your Kura runtime with a simple drag-and-drop.

    "},{"location":"administration/application-management/","title":"Application Management","text":""},{"location":"administration/application-management/#package-installation","title":"Package Installation","text":"

    After developing your application and generating a deployment package that contains the bundles to be deployed (refer to the Development section for more information), you may install it on the gateway using the Packages option in the System area of the Kura Gateway Administration Console as shown below.

    Upon a successful installation, the new component appears in the Services list (shown as the Heater example in these screen captures). Its configuration may be modified according to the defined parameters as shown the Heater display that follows.

    "},{"location":"administration/application-management/#eclipse-kura-marketplace","title":"Eclipse Kura Marketplace","text":"

    Kura allows the installation and update of running applications via the Eclipse Kura Marketplace. The Packages page has, in the top part of the page a section dedicated to the Eclipse Kura Marketplace.

    Dragging an application reference taken from the Eclipse Kura Marketplace to the specific area of the Kura Web Administrative Console will instruct Kura to download and install the corresponding package, as seen below:

    Warning

    If the installation from the Eclipse Marketplace fails, it can be for the lack of the correct certificates. In this case, import the certificate in the SSLKeystore from the Certificate List tab under the Security section. For more details about the procedure see here.

    If the bundle is an official add-on for Eclipse Kura, the following certificate has to be imported:

    -----BEGIN CERTIFICATE-----\nMIIHxzCCBq+gAwIBAgIQCCxCSNb4iszmNPNCflUcGTANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE\naWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMzA5MTEwMDAwMDBa\nFw0yNDEwMTEyMzU5NTlaMG8xCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlv\nMQ8wDQYDVQQHEwZPdHRhd2ExJTAjBgNVBAoTHEVjbGlwc2Uub3JnIEZvdW5kYXRp\nb24sIEluYy4xFjAUBgNVBAMMDSouZWNsaXBzZS5vcmcwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQC5hXH2cQoOQlXs5cQ5itZ1Dzct9R+bqr2HaF+imlgo\nxJ+Vw1ukfQPpSbmSO17A0hLgpSJyVgoPlpOKkg6LGTz8/2qB7DWHdQbg2p0IGQhr\ndm4oJN2qknnGNl/YYkjz2QJswr1M98raydmq0hqJi0M3q9JSO64O3wOMNduvNG+O\nrCBol7cbxLr7NNoFxZncZ9giP7QF0XYS6nA8dtIyXU3SARRSPn6y9OX1ttltveck\n41ocaU8ORiTF7i89t649XAbtsvxUWM+qVnvlMxpaXqbhnrXMQ/pV2yfdU/qiFQth\n+RqFgBYoX5roxvmjB14+2qlymn236N4KOGhvfr+Fp8C8Fv6N6wFyKZctXewQ6IsA\n3zDvJmF3QaCz6h88lg+IqbRjX5MOjhSkE7XDNKb+xAw5pYzkn9LP+QJLf0iYJw2D\nZ/X+InVPiZ5UdXyXWypN3q0W5vlz/TmWuVZv76/azZ3anoSPiKh+F3si1xZVEMZQ\nIkqsgUfq69M4KvHrdi4nGEOfdBHxjos9ul1AsJR57hrhIchsESthUK04e7d2LYOB\nhHAr0uJNdwFsFD2EBR25ogN83bZ8NaDrrdK2P6sV+hWWK+MY1qRuRub7/fYuR4AU\n82toms9p1usjuyMmuIGEpLwk7jZe6XITcbXQEXDA8JKSZrZ/mOA4yTfIGR/gXXB7\nwQIDAQABo4IDfTCCA3kwHwYDVR0jBBgwFoAUt2ui6qiqhIx56rTaD5iyxZV2ufQw\nHQYDVR0OBBYEFO8gL5LNWmSgCqbujR1qH0bUfrIrMCUGA1UdEQQeMByCDSouZWNs\naXBzZS5vcmeCC2VjbGlwc2Uub3JnMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYI\nKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8E\nBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMIGPBgNVHR8EgYcw\ngYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU1JT\nQVNIQTI1NjIwMjBDQTEtNC5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0\nLmNvbS9EaWdpQ2VydFRMU1JTQVNIQTI1NjIwMjBDQTEtNC5jcmwwfwYIKwYBBQUH\nAQEEczBxMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYI\nKwYBBQUHMAKGPWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRM\nU1JTQVNIQTI1NjIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAX4GCisGAQQB\n1nkCBAIEggFuBIIBagFoAHYAdv+IPwq2+5VRwmHM9Ye6NLSkzbsp3GhCCp/mZ0xa\nOnQAAAGKhcgXYgAABAMARzBFAiEApQsk19PxbsLa452EPaPCXe7SAtpbm5RHnrwj\nyKAjWx0CICli5A3XAGwmg7IEy4lVA5YBt+mhvlegWkXrKt+oc/CoAHUASLDja9qm\nRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGKhcgXWQAABAMARjBEAiAvx7lc\nMyKS6bbnsjbzYOLzJbcS2aAjCzQz4mFiuFA59AIgbt+rpE40/RO0JnFyLP9fsbUf\npUj16ZYinOLorqDk9r0AdwDatr9rP7W2Ip+bwrtca+hwkXFsu1GEhTS9pD0wSNf7\nqwAAAYqFyBc3AAAEAwBIMEYCIQDCrdQYGYA7BlsT5gXZmkutN15gDQDjlfJBxIRb\nZ0FAAgIhAIr0eNFvkpec6VJ5pPrNklFt78XP0NjEOJxjrCFTLKVdMA0GCSqGSIb3\nDQEBCwUAA4IBAQCvENXlAGP311/gV5rMD2frsK+hlcs/4wjRKUS+nwp3RLTRd3w4\ncZLHcsw9qCxeniuHsc/Wa6myr0kKdRc4V6movLq9vMdSjT9dDOZWtZgFaadB0+z2\nA/Jsq1/AFFWqWisF64627j/Wf7RwuasxM0dnkAl3m9Hli5xKPgjbovXiH/dCeMvS\nMTxD1p3ewIYITzV+1Q5FoFuGyIyuh1Kzo7A41xKPe+XfWHqt+hKL8MWkJ9ACD2b0\nZDlD2OaX7K+vI8aWprmwVdpp3deuUoHgBqa1PkHPRmP0bFbamBdB4H6goRX5+DEy\ncTW2rRm8jFiLm1kf0/iOL7/ddw0yZQAUMthU\n-----END CERTIFICATE-----\n

    that has the following description:

    Common Name: *.eclipse.org\nSubject Alternative Names: *.eclipse.org, eclipse.org\nOrganization: Eclipse.org Foundation, Inc.\nLocality: Ottawa\nState: Ontario\nCountry: CA\nValid From: September 10, 2023\nValid To: October 11, 2024\nIssuer: DigiCert TLS RSA SHA256 2020 CA1, DigiCert Inc Write review of DigiCert\nKey Size: 4096 bit\nSerial Number: 082c4248d6f88acce634f3427e551c19\n

    If the bundle is not an official one and it is not hosted by Eclipse, retrieve the certificate with this command:

    openssl s_client -showcerts -connect <download_link>:443\n

    and import it in the SSLKeystore.

    "},{"location":"administration/application-management/#package-signature","title":"Package Signature","text":"

    Once the selected application deployment package (dp) file is installed, it will be listed in the Packages page and detailed with the name of the deployment package, the version and the signature status. The value of the signature field can be true if all the bundles contained in the deployment package are digitally signed, or false if at least one of the bundles is not signed.

    "},{"location":"administration/directory-layout/","title":"Directory Layout","text":"

    This section describes the default Kura directory layout that is created upon installation. The default installation directory is as follows:

    /opt/eclipse\n

    The kura sub-directory is a logical link on the actual Kura release directory:

    kura -> /opt/eclipse/kura_3.0.0_raspberry-pi-2\nkura_3.0.0_raspberry-pi-2\n
    "},{"location":"administration/directory-layout/#kura-file-structure","title":"Kura File Structure","text":"

    The idea behind the Kura file and folder structure is to separate user and framework configuration files, that is files that can be modified by the user to customize Kura behavior and files that are used by Kura to persist configurations. Moreover, some files are generated at runtime by Kura (i.e. database) and they are kept in specific folders.

    The user, console, log4j and packages directories contain files that can be modified by the user (i.e. the configuration for the logging or custom Kura properties). The framework folder keeps the configuration files used by Kura and that shouldn't be modified by the user. The data directory is used to persist files that are generated by Kura. Finally, the plugins folder contains all the jar files needed by Kura. All of the Kura files are located within /opt/eclipse/kura folder using the structure shown in the following table:

    Directory Description bin Scripts that start Kura. console This folder contains files that are used to customise the Kura Web UI appearance. data Data files generated by the Kura runtime. data/persistance Embedded Database files. packages Deployment package files that are not part of the standard Kura framework, but are installed at a later time. framework Configuration data for Kura framework. The user shouldn't directly modify these files. log Log file from the latest Kura installation. log4j Logger framework configuration plugins All of the libraries and Kura specific jar files needed for the framework execution. user Configuration files generated by the user or by Kura at runtime. These files can be modified by the user to customise the Kura behavior. user/snapshots XML snapshot files; up to 10 successive configurations including the original. user/security Files used by Kura to configure security. .data Backup files needed to restore factory configuration"},{"location":"administration/directory-layout/#log-files","title":"Log Files","text":"

    Kura regularly updates two log files in the /var/log directory:

    • /var/log/kura-console.log - stores the standard console output of the application. This log file contains the eventual errors that are thrown upon Kura startup.

    • /var/log/kura.log - stores all of the logging information from Kura bundles. The logger levels are defined in the log4j.xml configuration file as shown below:

    /opt/eclpse/kura/user/log4j.xml\n

    The default logger level in this file is set to INFO. This level may be modified for the whole application or for a specific package as shown in the following example:

    <Loggers>\n    <Logger name=\"org.eclipse\" level=\"info\" additivity=\"false\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Logger>\n\n    <Logger name=\"org.eclipse.kura.net.admin\" level=\"debug\" additivity=\"false\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Logger>\n\n    <Root level=\"info\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Root>\n</Loggers>\n

    In this example, the logger level is set to DEBUG only for the net.admin bundle. Additionally, more specific, properties may be defined as required for your particular logging needs. The logger levels are hierarchical, so that those in a deeper level of the hierarchy will apply; otherwise, the more general logger level will override them.

    Once the logger levels are modified as needed and the log4j.xml configuration file is saved, Kura automatically loads the new configuration. By default Kura checks the file every 30 seconds.

    "},{"location":"administration/remote-management-kapua/","title":"Remote Management with Eclipse Kapua","text":""},{"location":"administration/remote-management-kapua/#built-in-services-management","title":"Built-in Services Management","text":"

    This section describes the remote management of devices running Kura via Eclipse Kapua Console. The Eclipse Kapua Web Console provides the administration tools used for the management of the built-in services exposed by Kura.

    To remotely manage a device running Kura through the Eclipse Kapua Web Console, select the desired device from the Devices Table of the console and open the Configuration tab as shown in the screen capture below. Please refer to the Built-in Services section for a description of the available Services and their configuration parameters.

    "},{"location":"administration/remote-management-kapua/#installation-of-a-new-application","title":"Installation of a New Application","text":"

    As described in Application Management, a new application embedded in a deployment package can be deployed and configured using Eclipse Kapua Console.

    To do so, select a connected device and click on the Packages tab. Then, click on Install/Upgrade. The Install New Package window opens allowing the deployment package to be installed from an URL as shown in the screen capture below. Once installed, the new application parameters may be modified in the same way as the Built-in Services. Click on the Configuration tab to see the service that corresponds to your application.

    "},{"location":"administration/remote-management-kapua/#snapshots","title":"Snapshots","text":"

    As described in Snapshot Management, the overall Kura configuration, including the new installed applications, is stored in a snapshot xml file. The Eclipse Kapua Console also provides options to Download, Upload and Apply, or Rollback snapshots as shown in the screen capture below.

    "},{"location":"administration/remote-management-kapua/#remote-command-execution-from-eclipse-kapua-web-console","title":"Remote Command Execution from Eclipse Kapua Web Console","text":"

    The Eclipse Kapua Console provides the ability to run system commands directly on the device. Refer to Command Service for details on how to configure this service in Kura.

    It is also possible to send a script to execute using the File option of the Command tab in Eclipse Kapua Console as shown in the screen capture below. This script must be compressed into a zip file with the eventual associated resource files. Once the file is selected, click Execute.

    The zip file is sent embedded in an MQTT message on the device. The Command Service in the device stores the file in /tmp, unzips it, and tries to execute a shell script if one is present. Note that in this case, the Execute parameter cannot be empty; a simple command, such as \"ls -l /tmp\", may be entered.

    "},{"location":"administration/snapshot-management/","title":"Snapshot Management","text":"

    The overall configuration of Kura is stored in an XML file called a snapshot. This file includes all of the parameters for every service running in Kura. The original configuration file is named snapshot_0.xml. This section describes how snapshots may be used.

    Each time a configuration change is made to one of the Kura components, a new XML file is created using the naming convention snapshot_[time as a long integer].xml. The nine most recent snapshots are saved, as well as the original snapshot 0.

    "},{"location":"administration/snapshot-management/#how-to-access-snapshots","title":"How to Access Snapshots","text":"

    To display snapshots using the Gateway Administration Console, select Settings from the System area, and then click on the Snapshots tab. The following three operations are available: Download, Upload and Apply, and Rollback.

    "},{"location":"administration/snapshot-management/#how-to-use-snapshots","title":"How to Use Snapshots","text":""},{"location":"administration/snapshot-management/#download","title":"Download","text":"

    The Download option provides the ability to save a snapshot file onto your computer. This file may then be edited, uploaded back to the device, or transferred to another equivalent device.

    Starting from Kura 5.1, the snapshot can be downloaded in two formats:

    • XML: The original XML snapshot format.
    • JSON: The JSON format used by the Configuration v2 REST APIs and CONF-V2 request handler. For example the downloaded snapshot can be used as is as a body for the PUT/configurableComponents/configurations/_update request. The takeSnapshot parameter specified by the CONF-V2 request is missing from the downloaded JSON file, if that parameter is not specified, a new snapshot will be created by default.

    Pressing the Download button will trigger a dialog that allows choosing the desired format.

    "},{"location":"administration/snapshot-management/#upload-and-apply","title":"Upload and Apply","text":"

    The Upload and Apply option provides the ability to import an XML file from your computer and upload it onto the device. This function updates every service in Kura with the parameters defined in the XML file.

    Warning

    Carefully select the file to be uploaded. An incorrect file may crash Kura and make it unresponsive.

    "},{"location":"administration/snapshot-management/#rollback","title":"Rollback","text":"

    The Rollback option provides the ability to restore the system to a previous configuration.

    "},{"location":"administration/system-component-inventory/","title":"System Component Inventory","text":"

    The Framework has the capability to report locally and to the associated cloud platform the list of currently installed components and their associated properties.

    This feature allows, locally and remotely, the system administrator to know which components are installed into the target device and the associated versions. The feature is particularly important for a system administrator because allows to identify vulnerable components and allows immediate actions in response.

    From the local Kura Web UI, the list of system components is available in the System Packages tab of the Device section. Once selected, the user will get the list of all the system installed components.

    The component's inventory list is available also via REST APIs and, with the same contract, from the cloud. The Mqtt contract defined for this component is available here

    "},{"location":"cloud-api/app-dev-guide/","title":"Application developer guide","text":"

    This guide will provide information on how an application developer can leverage the new Generic Cloud Services APIs, in order to be able to properly use the CloudPublisher/CloudSubscriber API, publish a message, being notified of message delivery and of connection status changes.

    The Kura ExamplePublisher will be used as a reference.

    The application should bind itself to a CloudPublisher or CloudSubscriber instance, this can be done in different ways, such as using OSGi ServiceTrackers or by leveraging the Declarative Service layer.

    The recommended way to perform this operation is choosing the latter and allowing the user to customize the service references through component configuration.

    If the component metatype and definition are structured as described below, the Kura Web UI will show a dedicated widget in component configuration that helps the user to pick compatible CloudPublisher or CloudSubscriber instances.

    1. Write component definition

      The first step involves declaring the Publisher or Subscriber references in component definition:

        <scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    name=\"org.eclipse.kura.example.publisher.ExamplePublisher\"\n    activate=\"activate\"\n    deactivate=\"deactivate\"\n    modified=\"updated\"\n    enabled=\"true\"\n    immediate=\"true\"\n    configuration-policy=\"require\">\n  <implementation class=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n\n   <!-- If the component is configurable through the Kura ConfigurationService, it must expose a Service. -->\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n   <service>\n       <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n\n   <reference name=\"CloudPublisher\"\n           policy=\"static\"\n           bind=\"setCloudPublisher\"\n           unbind=\"unsetCloudPublisher\"\n           cardinality=\"0..1\"\n           interface=\"org.eclipse.kura.cloudconnection.publisher.CloudPublisher\"/>\n   <reference name=\"CloudSubscriber\"\n           policy=\"static\"\n           bind=\"setCloudSubscriber\"\n           unbind=\"unsetCloudSubscriber\"\n           cardinality=\"0..1\"\n           interface=\"org.eclipse.kura.cloudconnection.subscriber.CloudSubscriber\"/>\n  </scr:component>\n

      The snipped above shows the definition of Kura ExamplePublisher, this component is capable of sending and receiving messages, and therefore defines two references, the first to a CloudPublisher and the second to a CloudSubscriber.

      In order to allow the user to customize the bindings at runtime, the target attribute of the references should not be specified at this point in component definition, as it will be set by the Web UI.

      Reference cardinality should be use the 0..1 or 0..n form, as it is not guaranteed that the references will point to a valid service instance during all the lifetime of the application component. For example, references can not be bound if the application has not been configured by the user yet or if the target service is missing.

    2. Create component metatype

      Application metatype should declare an AD for each Publisher/Subscriber reference declared in component definition:

        <MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n      <OCD id=\"org.eclipse.kura.example.publisher.ExamplePublisher\"\n           name=\"ExamplePublisher\"\n           description=\"Example of a Configuring Kura Application.\">\n\n       <!-- ... -->\n\n        <AD id=\"CloudPublisher.target\"\n            name=\"CloudPublisher Target Filter\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"(kura.service.pid=changeme)\"\n            description=\"Specifies, as an OSGi target filter, the pid of the Cloud Publisher used to publish messages to the cloud platform.\">\n        </AD>\n\n        <AD id=\"CloudSubscriber.target\"\n            name=\"CloudSubscriber Target Filter\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"(kura.service.pid=changeme)\"\n            description=\"Specifies, as an OSGi target filter, the pid of the Cloud Subscriber used to receive messages from the cloud platform.\">\n        </AD>\n\n        <!-- ... -->\n\n        </OCD>\n    <Designate pid=\"org.eclipse.kura.example.publisher.ExamplePublisher\" factoryPid=\"org.eclipse.kura.example.publisher.ExamplePublisher\">\n        <Object ocdref=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n    </Designate>\n  </MetaData>\n

      It is important to respect the following rules for some of the AD attributes:

      • id

      This attribute must have the following form:

      <reference name>.target\n

      where <reference name> should match the value of the name attribute of the corresponding reference in component definition.

      • required must be set to true

      • default must not be empty and must be a valid OSGi filter.

      The Web UI will renderer a dedicated widget for picking CloudPublisher and CloudSubscriber instances:

    3. Write the bind/unbind methods in applicaiton code

      The last step involves defining some bind...()/unbind...() methods with a name that matches the values of the bind/unbind attributes of the references in component definition.

      public void setCloudPublisher(CloudPublisher cloudPublisher) {\n...\n}\n\npublic void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n...\n}\n\npublic void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n...\n}\n\npublic void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n...\n}\n

      As stated above, since reference cardinality is declared as 0.., the application must be prepared to handle the cases where references are not satisfied, and therefore CloudPublisher and CloudSubscriber instances are not available.

    4. Publish a message

      If a CloudPublisher instance is bound, the application can publish messages using its publish() method:

        if (nonNull(this.cloudPublisher)) {\n    KuraMessage message = new KuraMessage(payload);\n    String messageId = this.cloudPublisher.publish(message);\n  }\n
    5. Receiving messages using a CloudSubscriber

      In order to receive messages from a CloudSubscriber, the application must implement and attach a CloudSubscriberListener to it.

      This can be done for example during CloudSubscriber binding:

        public class ExamplePublisher implements CloudSubscriberListener, ... {\n\n  ...\n\n   public void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber = cloudSubscriber;\n    this.cloudSubscriber.registerCloudSubscriberListener(ExamplePublisher.this);\n    ...\n  }\n\n  public void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber.unregisterCloudSubscriberListener(ExamplePublisher.this);\n    ...\n    this.cloudSubscriber = null;\n  }\n\n  ...\n\n  @Override\n  public void onMessageArrived(KuraMessage message) {\n    logReceivedMessage(message);\n  }\n\n  ...\n\n  }\n

      The CloudSubscriber will invoke the onMessageArrived() method when new messages are received.

    6. Receiving connection state notifications

      If an application is interested in cloud connection status change events (connected, disconnected, etc), it can implement and attach a CloudConnectionListener to a CloudPublisher or CloudSubscriber instance.

        public class ExamplePublisher implements CloudConnectionListener, ... {\n\n  ...\n\n  public void setCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher = cloudPublisher;\n    this.cloudPublisher.registerCloudConnectionListener(ExamplePublisher.this);\n    ...\n  }\n\n  public void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher.unregisterCloudConnectionListener(ExamplePublisher.this);\n    ...\n    this.cloudPublisher = null;\n  }\n\n  public void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber = cloudSubscriber;\n    ...\n    this.cloudSubscriber.registerCloudConnectionListener(ExamplePublisher.this);\n  }\n\n  public void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    ...\n    this.cloudSubscriber.unregisterCloudConnectionListener(ExamplePublisher.this);\n    this.cloudSubscriber = null;\n  }\n\n  ...\n\n  @Override\n  public void onConnectionEstablished() {\n    logger.info(\"Connection established\");\n  }\n\n  @Override\n  public void onConnectionLost() {\n    logger.warn(\"Connection lost!\");\n  }\n\n  @Override\n  public void onDisconnected() {\n    logger.warn(\"On disconnected\");\n  }\n\n  ...\n\n  }\n
    7. Receiving message delivery notifications

      If an application is interested in message confirmation events and the underlying cloud connection supports it, it can implement and attach a CloudDeliveryListener to a CloudPublisher instance.

        public class ExamplePublisher implements CloudDeliveryListener, ... {\n\n  ...\n\n  public void setCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher = cloudPublisher;\n    ...\n    this.cloudPublisher.registerCloudDeliveryListener(ExamplePublisher.this);\n  }\n\n  public void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n    ...\n    this.cloudPublisher.registerCloudDeliveryListener(ExamplePublisher.this);\n    this.cloudPublisher = null;\n  }\n\n  ...\n\n  @Override\n  public void onMessageConfirmed(String messageId) {\n    logger.info(\"Confirmed message with id: {}\", messageId);\n  }\n\n  ...\n\n  }\n

      The CloudSubscriber will invoke the onMessageConfirmed() method when a published message is confirmed.

      In order to determine which message has been confirmed, the provided messageId can be compared with the id returned by the publish() call that published the message.

      Please note that if the underlying cloud connection is not able to provide message confirmation for the published message, the id returned by publish() will be null.

    "},{"location":"cloud-api/built-in-cloud/","title":"Built-in Cloud Services","text":"

    Eclipse Kura provides by default a set of services used to connect to a cloud platform. The following sections describe the services and how to configure them.

    The CloudService API is deprecated since Kura 4.0. The functionalities provided by CloudService are now provided by the CloudEndpoint and CloudConnectionManager service interfaces. See the section describing the Kura 4.0 cloud connection model for more details.

    The DataService and MqttDataTrasport APIs are not deprecated in Kura 4.0.

    "},{"location":"cloud-api/built-in-cloud/#cloudservice","title":"CloudService","text":"

    The CloudService provides an easy-to-use API layer for the M2M application to communicate with a remote server. It operates as a decorator for the DataService, providing add-on features over the management of the transport layer.

    In addition to simple publish/subscribe, the CloudService API simplifies the implementation of more complex interaction flows like request/response or remote resource management. The CloudService abstracts the developers from the complexity of the transport protocol and payload format used in the communication.

    The CloudService allows a single connection to a remote server to be shared across more than one application in the gateway providing the necessary topic partitioning. Its functions include:

    • Adds application topic prefixes to allow for a single remote server connection to be shared across applications.

    • Defines a payload data model and provides default encoding/decoding serializers.

    • Publishes life-cycle messages when the device and applications start and stop.

    To use this service, select the CloudServices option located in the System area and select the CloudService tab as shown in the screen capture below.

    The CloudService provides the following configuration parameters:

    • device.display-name - defines the device display name given by the system. (Required field.)
    • device.custom-name - defines the custom device display name if the device.display-name parameter is set to \"Custom\".
    • topic.control-prefix - defines the topic prefix for system messages.
    • encode.gzip - defines if the message payloads are sent compressed.
    • republish.mqtt.birth.cert.on.gps.lock - when set to true, forces a republish of the MQTT Birth Certificate when a GPS correct position lock is received. The device is then registered with its real coordinates. (Required field.)
    • republish.mqtt.birth.cert.on.modem.detect - when set to true, forces a republish of the MQTT Birth Certificate when the service receives a modem detection event. (Required field.)
    • enable.default.subscriptions - when set to true, the gateway will not be remotely manageable.
    • payload.encoding - Specify the message payload encoding. The possible options are Kura Protobuf and Simple JSON.

    The default CloudService implementations publishes the following lifecycle messages:

    1. BIRTH message: sent immediately when device is connected to the cloud platform. The BIRTH message is published with priority 0 and QoS 1;
    2. DISCONNECT message: sent immediately before device is disconnected from the cloud platform. The DISCONNECT message is published with priority 0 and QoS 0;
    3. delayed BIRTH message: sent when new cloud application handler becomes available, a DP is installed or removed, GPS position is locked (can be disabled), or when modem status changes (can be disabled). These messages are cached for 30 seconds before sending. If no other message of such type arrives the message is sent; otherwise the BIRTH is cached and the timeout restarts. This is to avoid sending multiple messages when the framework starts. The message is published with priority 0 and QoS 1;
    "},{"location":"cloud-api/built-in-cloud/#mqttdatatransport","title":"MqttDataTransport","text":"

    The MqttDataTransport service provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. To use this service, select the MqttDataTransport option located in the System area and select the CloudService tab as shown in the screen capture below.

    The MqttDataTransport service provides the following configuration parameters:

    • broker-url - defines the URL of the MQTT broker to connect to. (Required field.)
    • topic.context.account-name - defines the name of the account to which the device belongs.
    • username and password - define the username and password that have been assigned to the device by the account administrator (generally username is account-name_broker). (Required field.)
    • client-id - defines the identifier of the MQTT client representing the device when connecting to the MQTT broker. If left empty, it is automatically determined by the client software as the MAC address of the main network interface (in general numbers and uppercase letters without ':'). This identifier has to be unique within your account.
    • keep-alive - defines the \"keep alive\" interval measured in seconds. It specifies the maximum amount of time that should pass without communication between the client and the server. The client will ensure that at least one message travels across the network within each keep alive period. In the absence of a data-related message during this time period, the client will send a very small MQTT \"ping\" message that the server will acknowledge. The keep alive interval enables the client to detect when the server is no longer available without having to wait for the long TCP/IP timeout. (Required field.)
    • timeout - sets the timeout used for all interactions with the MQTT broker. (Required field.)
    • clean-session - controls the behavior of both the client and the server at the time of connect and disconnect. When this parameter is set to true, the state information is discarded at connect and disconnect; when set to false, the state information is maintained. (Required field.)
    • lwt parameters - define the MQTT \"Last Will and Testament\" (LWT) settings for the client. In the event that the client unexpectedly loses its connection to the server, the server publishes the LWT message (lwt.payload) to the LWT topic on behalf of the client. This allows other clients (subscribed to the LWT topic) to be made aware that the client has disconnected. LWT parameters that may be configured include:
    • lwt.topic
    • lwt.payload
    • lwt.qos
    • lwt.retain
    • in-flight.persistence - defines the storage type where in-flight messages are persisted across reconnections. They may be stored in memory, or in a file on the disk. (Required field.)
    • protocol-version - defines the MQTT Protocol version to be used. This value may be 3.1 or 3.1.1.
    • ssl parameters - defines the SSL configuration. SSL parameters that may be configured include:
    • ssl.hostname.verification
    • ssl.default.cipherSuites
    • ssl.certificate.alias
    "},{"location":"cloud-api/cloud-conn-dev-guide/","title":"Cloud connection developer guide","text":"

    This guide will provide information on how a cloud connection developer can leverage the new Generic Cloud Services APIs.

    As reference, this guide will use the Eclipse IoT WG namespace implementation bundle available here

    "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-cloudendpoint-and-cloudconnectionmanager","title":"Implement CloudEndpoint and CloudConnectionManager","text":"

    In order to leverage the new APIs, and be managed by the Kura Web UI, the Cloud Connection implementation bundle must implement CloudEndpont and, if log-lived connections are supported, the CloudConnectionManager interface must be implemented as well.

    The ending class should be something as follows:

    public class CloudConnectionManagerImpl\n        implements CloudConnectionManager, CloudEndpoint, ... {\n\n    @Override\n    public boolean isConnected() {\n        ...\n    }\n\n    @Override\n    public void connect() throws KuraConnectException {\n        ...\n    }\n\n    @Override\n    public void disconnect() {\n        ...\n    }\n\n    @Override\n    public Map<String, String> getInfo() {\n        ...\n    }\n\n    @Override\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener) {\n        ...\n    }\n\n    @Override\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener) {\n        ...\n    }\n}\n

    A corresponding component definition should be provided in the OSGI-INF folder exposing the implementation of CloudEndpoint and CloudConnectionManager interfaces.

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" activate=\"activate\" configuration-policy=\"require\" deactivate=\"deactivate\" enabled=\"true\" immediate=\"true\" modified=\"updated\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.CloudConnectionManagerImpl\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n      <provide interface=\"org.eclipse.kura.cloudconnection.CloudConnectionManager\"/>\n      <provide interface=\"org.eclipse.kura.cloudconnection.CloudEndpoint\"/>\n      <!-- ... -->\n   </service>\n\n   <!-- ... -->\n\n   <property name=\"kura.ui.service.hide\" type=\"Boolean\" value=\"true\"/>\n   <property name=\"kura.ui.factory.hide\" type=\"String\" value=\"true\"/>\n</scr:component>\n

    In order to be fully compliant with the Web UI requirements, the CloudConnection component definition should provide two properties kura.ui.service.hide and kura.ui.factory.hide to hide the component from the left side part of the UI dedicated to display the services list.

    "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-the-cloudconnectionfactory-interface","title":"Implement the CloudConnectionFactory interface","text":"

    The CloudConnectionFactory is responsible to manage the cloud connection instance lifecycle by creating the CloudEndpoint instance and all the required services needed to publish or receive messages from the cloud platform.

    As a reference, please have a look at the CloudConnectionFactory defined for the Eclipse IoT WG namespace implementation.

    In particular, the getFactoryPid() method returns the PID of the CloudEndpoint factory. The createConfiguration() method receives a PID that will be used for the instantiation of the CloudEndpoint and for all the related services required to communicate with the cloud platform. In the example above, the factory creates the CloudEnpoint, and a DataService and MqttDataTransport instances internally needed to communicate with a remote cloud platform. As can be seen here, the CloudEndpoint instance configuration is enriched with the reference to the CloudConnectionFactory that generated it. This step is required by the Web UI in order to properly relate the instances with the corresponding factories.

    The deleteConfiguration() method deletes from the framework the CloudEndpoint instance identified by the PID passed as argument and all the related services. In the Eclipse IOT WG example, it not only deletes the CloudEndpoint instance but also the corresponding DataService and MqttDataTransport instances.

    The getStackComponentsPids() method return a List of String that represent the kura.service.pid of the configurable components that are part of a Cloud Connection instance. This method is used by the Web UI to get the list of configurable components that need to be displayed to the end user.

    The getManagedCloudConnectionPids() method will return the list of kura.service.pid of all the CloudEndpoints managed by the factory.

    The factory component definition should be defined as follows:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.DefaultCloudConnectionFactory\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.factory.DefaultCloudConnectionFactory\"/>\n   <reference bind=\"setConfigurationService\" cardinality=\"1..1\" interface=\"org.eclipse.kura.configuration.ConfigurationService\" name=\"ConfigurationService\" policy=\"static\" unbind=\"unsetConfigurationService\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory\"/>\n   </service>\n   <property name=\"osgi.command.scope\" type=\"String\" value=\"kura.cloud\"/>\n   <property name=\"osgi.command.function\" type=\"String\">\n      createConfiguration\n   </property>\n   <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n   <property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\-[a-zA-Z0-9]+)?$\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.DefaultCloudConnectionFactory\"/>\n</scr:component>\n

    In particular, it should expose in the service section the fact that the factory implements org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory

       <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory\"/>\n   </service>\n

    Important properties that need to be specified to have a better Web UI experience are the following:

    <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n<property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\-[a-zA-Z0-9]+)?$\"/>\n
    those allow to specify the form of the expected PID that the end user should provide when creating a new cloud connection.

    "},{"location":"cloud-api/cloud-conn-dev-guide/#provide-a-cloudpublisher-implementation","title":"Provide a CloudPublisher implementation","text":"

    To provide a CloudPublisher implementation, other than implementing CloudPublisher API in a java class, the developer must provide a component definition in the OSGI-INF folder that should be like the following:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" activate=\"activate\" configuration-policy=\"require\" deactivate=\"deactivate\" enabled=\"true\" immediate=\"true\" modified=\"updated\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.publisher.CloudPublisherImpl\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.publisher.CloudPublisher\"/>\n      <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n   <property name=\"cloud.connection.factory.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\"/>\n   <property name=\"kura.ui.service.hide\" type=\"Boolean\" value=\"true\"/>\n   <property name=\"kura.ui.factory.hide\" type=\"String\" value=\"true\"/>\n   <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\"/>\n   <property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher(\\-[a-zA-Z0-9]+)?$\"/>\n</scr:component>\n

    As can be seen in the previous snippet, the Publisher exposes itself in the framework as a ConfigurableComponent and as a CloudPublisher.

    The component definition must contain the following well-known properties:

    • cloud.connection.factory.pid: this property must be set to the kura.service.pid of the factory that created the cloud connection which the publisher belongs. It is used by the Web UI to enforce that the correct cloud publisher implementation is used in a specific cloud endpoint.
    • kura.ui.service.hide: as specified before for the Cloud Endpoint
    • kura.ui.factory.hide: as specified before for the Cloud Endpoint
    • kura.ui.csf.pid.default: as specified before for the Cloud Factory. It is an optional property.
    • kura.ui.csf.pid.regex: as specified before for the Cloud Factory. It is an optional property.

    The relation between the CloudPublisher instance and the CloudEndpoint is defined by a configuration property set by the Web UI at CloudPublisher creation.

    "},{"location":"cloud-api/cloud-conn-dev-guide/#provide-a-cloudsubscriber-implementation","title":"Provide a CloudSubscriber implementation","text":"

    The CloudSubscriber implementation and component definition is similar to the one described for the CloudPublisher.

    "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-requesthandler-support","title":"Implement RequestHandler support","text":"

    In order to support Command and Control, the cloud connection bundle should provide a service that registers itself as RequestHandlerRegistry. In this way all the RequestHandler instances could be able to discover the different Registry and subscribe for command and control messages received from the cloud platform. As an example, for the Eclipse IoT WG bundle, the CloudEndpoint registers itself also as RequestHandlerRegistry.

    "},{"location":"cloud-api/overview/","title":"Overview","text":"

    This section describes the new cloud related concepts and APIs introduced in Kura 4.0.

    "},{"location":"cloud-api/overview/#motivations","title":"Motivations","text":"

    Before Kura 4.0, Cloud APIs were quite tied to Kapua messaging conventions and to the MQTT protocol. Defining custom stacks that support other cloud platforms was possible, but the resulting implementations were affected by the following limitations:

    • The legacy APIs assume that the underlying messaging protocol is MQTT. This assumption spans across all API layers, from the low level MQTTDataTrasport to the high level CloudClient. This makes quite difficult to implement cloud stacks that use other protocols like AMQP or HTTP.

    • The CloudClient API, which was the recommended way for applications to interface with a cloud stack, enforce the following MQTT topic structure:

      #account-name/#device-id/#app-id/<app-topic>\n
      This topic hierarchy, which is Kapua related, might be too restrictive or too loose for other cloud platforms, for example:

      • The Eclipse IoT working group namespace allows authenticated devices to omit the accont-name and device-id parameters in the topic. Moreover, telemetry, alert and event message topics must start respectively the t/, a/ and e/ prefixes. Adhering to this specification is not possible for a cloud stack that implements the legacy APIs.
      • The AWS cloud platform allows publishing on virtually any topic, using a CloudClient would be quite restrictive in this case. A way for overcoming this limitation for an application might be using the DataService layer directly, adversely affecting portability.
      • The Cumulocity cloud platform allows publishing only on a limited set of topics, and most of the application generated information is placed in the payload encoded in CSV. Using CloudClient in this case makes difficult for the cloud stack to enforce that the messages are published on the correct topics. Moreover, the cloud stack in this case must also convert from KuraPayload to CSV, this can be currently achieved only by introducing rigid conversion rules, that might not be enough to support all message formats.
    • Applications that use the current APIs are not portable across cloud platforms. For example if an appliaction intends to publish on Cumulocity or AWS, it should be probably aware of the underlying cloud platform.

    "},{"location":"cloud-api/overview/#concepts","title":"Concepts","text":"

    The main interfaces of the new set of APIs and their interactions are depicted in the diagram below:

    As shown in the above diagram new APIs introduce the concept of Cloud Connection, a set of related services that allow to manage the communication to/from a remote cloud platform.

    The services that compose a Cloud Connection can implement the following cloud-specific interfaces:

    • CloudEndpoint (required): Each Cloud Connection is identified by a single instance of CloudEndpoint that implements low level specificities for the communication with the remote cloud platform.

    • CloudConnectionManager (optional): Exposes methods that allow to manage long-lived connections. The implementor of CloudEndpoint can implement this interface as well if the cloud platform support long-lived connections (for example by using the MQTT protocol).

    • RequestHandlerRegistry (optional): Manages the command and control functionalities if supported by the cloud platform.

    • CloudPublisher (optional): Allows applications to publish messages to the cloud platform in a portable way.

    • CloudSubscriber (optional): Allows applications to receive messages from the cloud platform in a portable way.

    • CloudConnectionFactory (required): Manages the lifecycle of Cloud Connections.

    A Cloud Connection can also include services that do not provide any of the interfaces above but compose the internal implementation.

    "},{"location":"cloud-api/overview/#cloudendpoint","title":"CloudEndpoint","text":"

    Every Cloud Connection must contain a single CloudEndpoint instance. The kura.service.pid of the CloudEndpoint identifies the whole Cloud Connection.

    The CloudEndpoint provides some low level methods that can be used to interact with the remote cloud platform.

    For example the interface provides the publish() and subscribe() methods that allow to publish or receive messages from the cloud platform in form of KuraMessages. Those methods are designed for internal use and are not intended to be used by end-user applications.

    The format of the KuraMessage provided to/received from a CloudEndpoint is implementation specific: the CloudEndpoint expects some properties to be set in the KuraMessage to be able to correctly publish a message (e.g. MQTT topic). These properties are specified by the particular CloudEndpoint, and should be documented by the implementor.

    The recommended way for applications to publish and receive messages involves using the Publisher and Subscriber APIs described below. If an application directly uses the methods above, it will lose portability and will be tied to the specific Cloud Connection implementation.

    "},{"location":"cloud-api/overview/#cloudconnectionmanager","title":"CloudConnectionManager","text":"

    If the messaging protocol implemented by a Cloud Connection supports long-lived connection, then its CloudEndpoint can also implement and provide the CloudConnectionManager interface.

    This interface exposes some methods that can be used to manage the connection like connect(), disconnect() and isConnected(); it also supports monitoring connection state using the CloudConnectionListener interface.

    "},{"location":"cloud-api/overview/#publishers-and-subscribers","title":"Publishers and Subscribers","text":"

    The limitations of the current model described above are addressed by the introduction of the CloudPublisher and CloudSubscriber APIs, that replace the CloudClient as the recommended interface between applications and cloud stacks. CloudPublisher and CloudSubscriber are service interfaces defined as follows:

    public interface CloudPublisher {\n\n    public String publish(KuraMessage message) throws KuraException;\n\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void registerCloudDeliveryListener(CloudDeliveryListener cloudDeliveryListener);\n\n    public void unregisterCloudDeliveryListener(CloudDeliveryListener cloudDeliveryListener);\n\n}\n
    public interface CloudSubscriber {\n\n    public void registerCloudSubscriberListener(CloudSubscriberListener listener);\n\n    public void unregisterCloudSubscriberListener(CloudSubscriberListener listener);\n\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n}\n
    "},{"location":"cloud-api/overview/#cloudpublisher","title":"CloudPublisher","text":"

    The CloudPublisher interface should be used by applications for publishing messages using the single publish() method. This method accepts a KuraMessage which is basically a KuraPayload that can be enriched with metadata.

    The main difference with the CloudClient APIs is that the publish() method does not require the application to specify any information related to message destinations. This allows to write portable applications that are unaware of the low level details of the destination cloud platform, such as the message format and the transport protocol.

    "},{"location":"cloud-api/overview/#cloudsubscriber","title":"CloudSubscriber","text":"

    An application designed to receive messages from the cloud must now attach a listener (CloudSubscriberListener) to a CloudSubscriber instance.

    In this case, the message source cannot be specified by the application but is defined by the subscriber instance, in the same way as the CloudPublisher defines destination for published messages.

    The low level details necessary for message delivery and reception (e.g. the MQTT topic and the conversion between KuraMessage and the message format used on the wire) are managed by the publisher/subscriber, typically these details are stored in the service configuration.

    While in the previous model an application was responsible to actively obtain a CloudClient instance from a CloudService, now the relation between the application and a CloudPublisher or CloudSubscriber instance is represented as an OSGi service reference. Applications should allow the user to modify this reference in configuration, making it easy to switch between different cloud publisher/subscriber instances and different cloud platforms.

    Publisher/subscriber instances are now typically instantiated and configured by the end user using the Web UI.

    Publisher/subscriber instances are related to a CloudEnpoint instance using an OSGi service reference encoded in well known configuration property specified in the APIs (CloudConnectionConstants.CLOUD_ENDPOINT_SERVICE_PID_PROP_NAME). This allows the user to create those instances in a dedicated section of the Web UI.

    "},{"location":"cloud-api/overview/#command-and-control","title":"Command and control","text":"

    Another field in which the current Kura cloud related APIs can be generalized is related to command and control. In the previous model this aspect was covered by the Cloudlet APIs that are now replaced by RequestHandler APIs

    Legacy Cloudlet implementations are defined by extending a base class, Cloudlet, which takes care of handling the invocation of the doGet(), doPut(), doPost() ... methods, and of correlating request and response messages. Messages were sent and received through a CloudClient.

    More explicitly, Cloudlet only works with control topics whose structure is

    $EDC/<account-name>/<device-id>/<app-id>/<method>/<resource-path>\n
    and also expects the identifier of the sender and the correlation identifier in the KuraPayload.

    In the previous model, there is no way for a cloud stack implementor to customize the aspects above, which are hardcoded in the Cloudlet base class.

    The new model delegates these aspects to some component of the cloud stack, and requires applications that want to support command and control to register themselves as RequestHandler to a RequestHandlerRegistry instance.

    In order to ease porting old applications to the new model, some of the concepts of the old Cloudlet APIs are still present, this can be seen by looking at the RequestHandler interface definition:

    public interface RequestHandler {\n\n    public KuraMessage doGet(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doPut(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doPost(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doDel(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doExec(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n}\n

    A RequestHandler invocation involves the following parameters:

    Request parameters:

    • method: (GET, PUT, POST, DEL, EXEC) that identifies the RequestHandler method to be called
    • request message: A set of key-value pairs and/or binary body contained in the KuraPayload wrapped inside the KuraMessage.
    • resources: A List<String> of positional parameters available under the well known args key in the provided KuraMessage properties.

    Response parameters:

    • response message: A set of key-value pairs and/or binary body contained in the KuraPayload wrapped inside the returned KuraMessage.
    • status: A numeric code reporting operation completion state, determined as follows:
    • 200, if RequestHandler methods returns without throwing exceptions.
    • 400, if RequestHandler methods throws a KuraException with KuraErrorCode == BAD_REQUEST
    • 404, if RequestHandler methods throws a KuraException with KuraErrorCode == NOT_FOUND
    • 500, if RequestHandler methods throws a KuraException with other error codes.
    • exception message: The message of the KuraException thrown by the RequestHandler methods, if any.
    • exception stack trace: The stack trace of the KuraException thrown by the RequestHandler methods, if any.

    The parameters above are the same involved in current Cloudlet calls. The request id and requester client id parameters are no longer part of the API, because are related to the to the way Kapua correlates requests and response. In the new API, request and response identifiers are not specified and not forwarded to the Cloudlet, this allows the CloudletService implementation to adopt the platform specific conventions for message correlation.

    The Cloudlet parameters must be present in the request and response messages encoded in some format. A user that intends to call a Kura Cloudlet, for example through platform-specific REST APIs must be aware of these parameters. The user must supply request parameters in request message and must be able to extract response data from received message. The actual encoding of these parameters inside the messages depends on the particular platform.

    The fact that set of Cloudlet parameters are roughly the same involved in current Cloudlet calls allows existing Cloudlet based applications to continue to work without changes to the protocol.

    "},{"location":"cloud-api/overview/#cloud-connection-lifecycle","title":"Cloud Connection lifecycle","text":"

    CloudEndpoint instance lifecycle is managed by a CloudConnectionFactory instance. A cloud connection implementor must register a CloudConnectionFactory instance in the framework that is responsible of creating and destroying the CloudEndpoint instances.

    The CloudConnectionFactory will be typically invoked by the Web UI, and is defined as follows:

    public interface CloudConnectionFactory {\n\n    public static final String KURA_CLOUD_CONNECTION_FACTORY_PID = \"kura.cloud.connection.factory.pid\";\n\n    public String getFactoryPid();\n\n    public void createConfiguration(String pid) throws KuraException;\n\n    public List<String> getStackComponentsPids(String pid) throws KuraException;\n\n    public void deleteConfiguration(String pid) throws KuraException;\n\n    public Set<String> getManagedCloudConnectionPids() throws KuraException;\n\n}\n

    The createConfiguration() and deleteConfiguration() methods are responsible of creating/destroying a CloudEndpoint instance, specified by the provided kura.service.pid, and all the related services.

    The getManagedCloudConnectionPids() returns the set of kura.service.pid managed by the factory.

    The getStackComponentsPids(String pid) returns the list of the kura.service.pids of the ConfigurableComponents that are associated with the CloudEndpoint with the specified pid. The Web Ui will render the configuration of those components in separated tabs, in the dedicated CloudConnections section.

    "},{"location":"cloud-api/overview/#backwards-compatibility","title":"Backwards compatibility","text":"

    In order to ease the transition to the new model, legacy APIs like CloudService and CloudClient are still supported in Kura 4.0.0, even if deprecated.

    The default Kapua oriented CloudService implementation is still available and can be used by legacy applications without changes. The default CloudService instance in Kura 4.0 also implements the new CloudEndpoint and CloudConnectionManager interfaces.

    "},{"location":"cloud-api/user-guide/","title":"User guide","text":"

    This guide will illustrate the steps required for configuring an application that uses the new Cloud Connection APIs to publish messages to the Kapua platform.

    The involved steps are the following

    1. Instantiation and configuration of the Cloud Connection.
    2. Instantiation and configuration of a Publisher.
    3. Binding an application to the Publisher.
    "},{"location":"cloud-api/user-guide/#creating-a-new-cloud-connection","title":"Creating a new Cloud Connection","text":"
    1. Open the Cloud Connections section of the Web UI:

    2. Create a new Cloud Connection

      1. Click on the New Connection button

      2. Enter a new unique identifier in the Cloud Connection Service PID field. The identifier must be a valid kura.service.pid and, in case of a Kapua Cloud Connection, it must start with the org.eclipse.kura.cloud.CloudService- prefix. A valid identifier can be org.eclipse.kura.cloud.CloudService-KAPUA. As an alternative it is possible to reconfigure the existing org.eclipse.kura.cloud.CloudService Cloud Connection.

      3. Configure the MQTTDataTrasport service.

        Click on the MQTTDataTrasport-KAPUA tab and fill the parameters required for establishing the MQTT connection:

        • Broker-url
        • Topic Context Account-Name
        • Username
        • Password
      4. Configure the DataService-KAPUA service.

        In order to enable automatic connection, set the Connect Auto-on-startup parameter to true

    "},{"location":"cloud-api/user-guide/#creating-and-configuring-a-new-publisher","title":"Creating and configuring a new Publisher","text":"
    1. Select to the connection to be used from the list.

    2. Click on the New Pub/Sub button.

    3. Select the type of component to be created, from the Available Publisher/Subscriber factories drop down list, in order to create a Publisher select the org.eclipse.kura.cloud.publisher.CloudPublisher entry.

    4. Enter an unique kura.service.pid identifier in the New Publisher/Subscriber PID field.

    5. Click Apply, you should see the publisher configuration

    6. Select and configure the newly created publisher instance, and then click Apply

    "},{"location":"cloud-api/user-guide/#binding-an-application-to-a-publisher","title":"Binding an application to a publisher","text":"
    1. Select the application instance configuration

    2. Find the configuration entry that represents a Publisher reference.

    3. Click on the Select available targets link and select the desired Publisher instance to bind to.

    4. Click on Apply

    "},{"location":"cloud-platform/kura-aws-cloud/","title":"Amazon AWS IoT\u2122 platform","text":""},{"location":"cloud-platform/kura-aws-cloud/#overview","title":"Overview","text":"

    This section provides a guide on connecting an Eclipse Kura\u2122 device to the Amazon AWS IoT platform.

    "},{"location":"cloud-platform/kura-aws-cloud/#prerequisites","title":"Prerequisites","text":"
    • In order to connect a device to Amazon AWS IoT Kura version 1.3 or greater is required.
    • An Amazon AWS account is also needed.
    "},{"location":"cloud-platform/kura-aws-cloud/#device-registration","title":"Device registration","text":"

    The first step involves the registration of the new device on AWS, this operation can be done using the AWS Web Console or with the AWS CLI command line tool, in this guide the Web based console will be used.

    "},{"location":"cloud-platform/kura-aws-cloud/#1-access-the-aws-iot-management-console","title":"1. Access the AWS IoT management console.","text":"

    This can be done by logging in the AWS console and selecting IoT Core from the services list, in the Internet of Things section.

    "},{"location":"cloud-platform/kura-aws-cloud/#2-create-a-default-policy-for-the-device","title":"2. Create a default policy for the device.","text":"

    This step involves creating a default policy for the new device, skip if an existing policy is already available.

    Access the main screen of the console and select Secure -> Policies from the left side menu and then press the Create button, in the top right area of the screen.

    Fill the form as follows and then press the Create button:

    • Action -> iot:Connect, iot:Publish, iot:Subscribe, iot:Receive, iot:UpdateThingShadow, iot:GetThingShadow, iot:DeleteThingShadow
    • Resource ARN -> *
    • Effect -> Allow

    This will create a policy that allows a device to connect to the platform, publish/subscribe on any topic and manage its thing shadow.

    "},{"location":"cloud-platform/kura-aws-cloud/#3-register-a-new-device","title":"3. Register a new device.","text":"

    Devices on the AWS IoT platform are called things, in order to register a new thing select Manage -> Things from the left side menu and then press the Create button, in the top right section of the screen. Select Create a single thing.

    Enter a name for the new device and then press the Next button, from now on kura-gateway will be used as the device name.

    "},{"location":"cloud-platform/kura-aws-cloud/#4-create-a-new-certificate-for-the-device","title":"4. Create a new certificate for the device.","text":"

    The AWS IoT platform uses SSL mutual authentication, for this reason it is necessary to download a public/private key pair for the device and a server certificate. Click on Create certificate to quickly generate a new certificate for the new device.

    Certificates can be managed later on by clicking on Secure -> Certificates, in the left part of the console.

    "},{"location":"cloud-platform/kura-aws-cloud/#5-download-the-device-ssl-keys","title":"5. Download the device SSL keys.","text":"

    You should see a screen like the following:

    Download the 3 files listed in the table and store them in a safe place, they will be needed later, also copy the link to the root CA for AWS IoT in order to be able to retrieve it later from the device.

    Press the Activate button, and then on Attach a policy.

    "},{"location":"cloud-platform/kura-aws-cloud/#6-assign-the-default-policy-to-the-device","title":"6. Assign the default policy to the device.","text":"

    Select the desired policy and then click on Register thing.

    A policy can also be attached to a certificate later on perforiming the following steps:

    Enter the device configuration section, by clicking on Manage -> Things and then clicking on the newly created device. Click on Security on the left panel and then click on the certificate entry (it is identified by an hex code), select Policies in the left menu, you should see this screen:

    Click on Actions in the top left section of the page and then click on Attach policy, select the default policy previously created and then press the Attach button.

    "},{"location":"cloud-platform/kura-aws-cloud/#device-configuration","title":"Device configuration","text":"

    The following steps should be performed on the device, this guide is based on Kura 3.1.0 version and has been tested on a Raspberry PI 3.

    "},{"location":"cloud-platform/kura-aws-cloud/#7-create-a-java-keystore-on-the-device","title":"7. Create a Java keystore on the device.","text":"

    The first step for using the device keys obtained at the previous step is to create a new Java keystore containing the Root Certificate used by the Amazon IoT platform, this can be done executing the following commands on the device:

    sudo mkdir /opt/eclipse/kura/security\n
    cd /opt/eclipse/kura/security\n
    curl https://www.amazontrust.com/repository/AmazonRootCA1.pem > /tmp/root-CA.pem\n
    sudo keytool -import -trustcacerts -alias aws -file /tmp/root-CA.pem -keystore cacerts.ks -storepass changeit\n

    If the last command reports that the certificate already exist in the system-wide store type yes to proceed. The code above will generate a new keystore with changeit as password, change it if needed.

    "},{"location":"cloud-platform/kura-aws-cloud/#8-configure-the-ssl-parameters-using-the-kura-web-ui","title":"8. Configure the SSL parameters using the Kura Web UI.","text":"
    1. Open the Kura Web Console and enter select the Settings entry in the left side menu and then click on SSL Configuration, you should see this screen:

      Change the Keystore path parameter to /opt/eclipse/kura/security/cacerts.ks if needed.

      Change the settings in the form to match the screen above, set Default protocol to TLSv1.2, enter changeit as Keystore Password (or the password defined at step 7).

      Warning

      Steps from 8.2 to 8.6 will not work on Kura 3.2.0 due to a known issue. On this version, private key and device certificate need to be manually added to the keystore using the command line. If you are running Kura 3.2.0, proceed with step 8.7.

    2. Open the Kura Web Console and enter select the Settings entry in the left side menu and then click on Device SSL Certificate, you should see this screen:

      Enter aws-ssl in the Storage Alias field.

    3. The private key needs to be converted to the PKCS8 format, this step can be performed executing the following command on a Linux or OSX based machine:

      openssl pkcs8 -topk8 -inform PEM -outform PEM -in xxxxxxxxxx-private.pem.key -out outKey.pem -nocrypt\n

      where xxxxxxxxxx-private.pem.key is the file containing the private key downloaded at step 4.

    4. Paste the contents of the obtained outKey.pem in the \"Private Key\" field.

    5. Paste the contents of xxxxxxxxxx-certificate.pem.crt in the Certificate field.

      You should see a screen like this

    6. Click the Apply button to confirm.

    7. Kura 3.2.0 only - manually import device certificate and private key into keystore.

      On the host machine, open a terminal window in the folder containing the files downloaded at step 5 and execute the following command:

      openssl pkcs12 -export -in xxxxxxxxxx-certificate.pem.crt -inkey xxxxxxxxxx-private.pem.key -name aws-ssl -out aws-ssl.p12\n

      where xxxxxxxxxx-certificate.pem.crt is the original certificate downloaded from AWS and xxxxxxxxxx-private.pem.key is the private key.

      The command will ask for a password, define a new password.

      Copy the obtained aws-ssl.p12 file to the device into the /tmp folder using scp:

      scp ./aws-ssl.p12 pi@<device-address>:/tmp\n

      Replacing <device-address> with the hostname or ip address of the device.

      Open a ssh connection to the device and enter the following command:

      sudo keytool -importkeystore -deststorepass changeit -destkeystore /opt/eclipse/kura/security/cacerts.ks -srckeystore /tmp/aws-ssl.p12 -srcstoretype PKCS12\n

      The command will ask for a password, enter the password defined when creating the aws-ssl.p12 file.

      Restart Kura to reload the keystore.

    "},{"location":"cloud-platform/kura-aws-cloud/#9-setup-a-new-cloud-connection","title":"9. Setup a new cloud connection","text":"
    1. Click on Cloud Connections in the left panel, and setup a new cloud connection

    2. Click on the New Connection button at the top of the page and set the following parameters in the dialog:

      • Cloud Connection Factory PID -> org.eclipse.kura.cloud.CloudService
      • Cloud Connection Service PID -> org.eclipse.kura.cloud.CloudService-AWS

      Press the Create button to confirm and then select the newly created CloudService instance from the list.

    3. Set the broker URL in the MqttDataTransport-AWS tab, it can be obtained from the AWS IoT Web Console clicking on the Settings entry in the bottom left section of the page, the URL will look like the following:

      a1rm1xxxxxxxxx.iot.us-east-1.amazonaws.com\n

      The mqtts protocol must be used, the value for the broker-url field derived from the URL above is the following:

      mqtts://a1rm1xxxxxxxxx.iot.us-east-1.amazonaws.com:8883/\n
    4. Clear the value of the username and password fields.

    5. Set a value for the topic.context.account-name and client-id.

      • Assign an arbitrary account name to topic.context.account-name (for example aws-test), this will be used by the CloudClient instances for building the topic structure.

      • Enter the thing name in the client-id field (in this example kura-gateway).

    6. In order for the previously added keys to be used for the SSL connection with the broker enter the Storage Alias defined in step 8.2 (e.g aws-ssl) as value for the ssl.certificate.alias field.

    7. The setting lwt.topic under MqttDataTransport-AWS needs to be updated as well by entering a value not containing the $ character. This is required because of the fact that AWS IoT does not support topic names starting with $ (except for the $aws/ hierarchy).

    8. Press the Apply button in the top left section to commit the changes to the MqttDataTransport-AWS.

    9. Enter a name without the $ character for the topic.control-prefix setting in the CloudService-AWS tab, for example aws-control.

    10. The Kura CloudService uses some well-known topics to allow remote device management and to report device state information, this features are not supported by default by AWS IoT, the following settings can be applied in the CloudService-AWS tab in order to avoid sending unnecessary messages:

      • republish.mqtt.birth.cert.on.gps.lock -> false
      • republish.mqtt.birth.cert.on.modem.detect -> false
      • enable.default.subscriptions -> false
    11. Click the Apply button to save the changes.

    "},{"location":"cloud-platform/kura-aws-cloud/#10-connect-to-the-cloud-platform","title":"10. Connect to the cloud platform","text":"

    Make sure the AWS CloudService instance is selected from the list in the top section of the page and click on the Connect button, if the connection to AWS IoT platform succeeds the Status of the instance will be reported as Connected.

    "},{"location":"cloud-platform/kura-azure/","title":"Azure IoT Hub\u2122 platform","text":"

    Starting from release 3.0, Eclipse Kura can connect to the Azure IoT Hub using the MQTT protocol. When doing so, Kura applications can send device-to-cloud messages. More information on the Azure IoT Hub and its support for the MQTT protocol can be found here. This document outlines how to configure and connect a Kura application to the Azure IoT Hub.

    "},{"location":"cloud-platform/kura-azure/#get-azure-iot-hub-information","title":"Get Azure IoT Hub information","text":"

    In order to properly configure Kura to connect to IoT Hub, some information are needed. You will need the hostname of the Azure IoT Hub, referred below as {iothubhostname}, the Id and the SAS Token of the device, referred as {device_id} and {device_SAS_token}. The hostname is listed on the \"Overview\" tab on the IoT Hub main page, while the device ID is shown on the \"Device Explorer\" tab. Finally, the SAS token can be generated using the iothub-explorer application that can be found here. To install the application, type on a shell:

    npm install -g iothub-explorer\n

    Then start a new session on your IoT Hub instance (it will expire in 1 hour):

    iothub-explorer login \"{your-connection-string}\"\n

    where {your-connection-string} is the connection string of your IoT Hub instance. It can be found on the \"Shared access policies\" tab under \"Settings\". Select the \"iothubowner\" policy and a tab will appear with the \"Connection string\u2014primary key\" option. Then list your devices:

    iothub-explorer list\n

    and get the SAS token for the {device-name} device:

    iothub-explorer sas-token {device-name}\n

    Be aware that the SAS token will expire in 1 hour by default, but using \"-d\" option it is possible to set a custom expiration time.

    "},{"location":"cloud-platform/kura-azure/#ssl-certificates","title":"SSL certificates","text":"

    In order to connect to your IoT Hub instance, Kura should trust the remote broker through a SSL certificate. The simpler way to get the IotHub certificate is to run the following command on a shell:

    openssl s_client -showcerts -tls1 -connect {iothubhostname}:8883\n

    The result is the SSL certificate chain. Copy all the certificates in the format:

    -----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n

    and paste them in to the \"Server SSL Certificate\" tab under \"Settings\" in Kura. Then click the Apply button and restart Kura to update the keystore.

    "},{"location":"cloud-platform/kura-azure/#configuring-a-kura-cloud-stack-for-azure-iot-hub","title":"Configuring a Kura Cloud Stack for Azure IoT Hub","text":"

    The Kura Gateway Administrative Console exposes all services necessary to configure a connection to the Azure IoT Hub. You can follow the steps outlined below to configure the connection to the Azure IoT Hub.

    The first step is to create a new Kura Cloud stack. From the Kura Gateway Administrative Console:

    • Select Cloud Connections in the navigation on the left and click New Connection to create a new Cloud connection
    • In the dialog, select org.eclipse.kura.cloud.CloudService as the Cloud Connection Factory PID
    • Enter a Cloud Connection Service PID name like org.eclipse.kura.cloud.CloudService-Azure
    • Press the Create button to create the new Cloud stack

    Now review and update the configuration of each Kura Cloud stack component as outline below.

    • MqttDataTransport
    • DataService
    • CloudService
    "},{"location":"cloud-platform/kura-azure/#mqttdatatransport","title":"MqttDataTransport","text":"

    Modify the service configuration parameters as follows:

    • broker-url - defines the URL of the Azure IoT MQTT broker. The URL value should be set as mqtts://{iothubhostname}:8883/

      Note

      An SSL connection (mqtts on port 8883) is required to connect to Azure IoT Hub\u2122.

    • topic.context.account-name - insert devices as the MQTT topic prefix for the device-to-cloud and cloud-to-device messages

    • username - insert {iothubhostname}/{device_id} as username for the MQTT connection
    • password - insert {device_SAS_token} as password for the MQTT connection

      Note

      The format of the SAS Token is like:

      SharedAccessSignature sig={signature-string}&se={expiry}&sr={URL-encoded-resourceURI}\n

    • client-id - insert {device_id} as Client ID for the MQTT connection

    • clean-session - make sure it is set to true
    • lwt.topic - set the Will Topic to something #account-name/#client-id/messages/events/LWT

    You can keep the default values of the remaining parameters, so save your changes by clicking the Apply button. A screen capture of the MqttDataTransport configuration is shown below.

    "},{"location":"cloud-platform/kura-azure/#dataservice","title":"DataService","text":"

    The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below.

    In order for Kura to connect to Azure IoT Hub on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

    Note

    Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

    "},{"location":"cloud-platform/kura-azure/#cloudservice","title":"CloudService","text":"

    The default settings for the CloudService should be modified as follow to allow a connection to Azure IoT Hub .

    • topic.control-prefix - insert devices as the MQTT topic prefix for the device-to-cloud and cloud-to-device messages
    • encode.gzip - should be set false to avoid compression of the message payloads
    • republish.mqtt.birth.cert.on.gps.lock - should be set false to avoid sending additional messages on GPS position lock
    • republish.mqtt.birth.cert.on.modem.detect - should be set false to avoid sending additional messages on cellular modem update
    • enable.default.subscriptions - should be set false to avoid subscriptions on Kura control topics for cloud-to-device
    • payload.encoding - should be set to Simple JSON

    The screen capture shown below displays the default settings for the CloudService.

    "},{"location":"cloud-platform/kura-azure/#how-to-connect-and-disconnect-from-the-cloud-platform","title":"How to connect and disconnect from the cloud platform","text":"

    The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

    Note

    Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

    "},{"location":"cloud-platform/kura-azure/#kura-application-connecting-to-azure-iot-hub","title":"Kura Application Connecting to Azure IoT Hub","text":"

    The Kura example publisher can be used to publish to the IoT Hub. The example configuration should be modified as follows.

    • cloud.service.pid - insert the name of the new Kura Cloud stack, org.eclipse.kura.cloud.CloudService-Azure in this tutorial
    • app.id - insert messages as application id
    • publish.appTopic - insert events/ as publish topic

    This configuration allows the publication on the default messages/events endpoint on the IoT Hub\u2122.

    "},{"location":"cloud-platform/kura-ec-cloud/","title":"Eurotech Everyware Cloud\u2122 platform","text":"

    Everyware Cloud provides an easy mechanism for connecting cloud-ready devices to IT systems and/or applications; therefore, connecting to Everyware Cloud is an important step in creating and maintaining a complete M2M application. Information on Everyware Cloud and its features can be found here. This document outlines how to connect to Everyware Cloud using the Kura Gateway Administrative Console.

    "},{"location":"cloud-platform/kura-ec-cloud/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

    The Kura Gateway Administrative Console exposes all services necessary for connecting to Everyware Cloud. The reference links listed below outline each service involved in the Everyware Cloud connection. It is recommended that each section be reviewed.

    • CloudService
    • DataService
    • MqttDataTransport
    "},{"location":"cloud-platform/kura-ec-cloud/#cloudservice","title":"CloudService","text":"

    The default settings for the CloudService are typically adequate for connecting to Everyware Cloud. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

    Warning

    The \"Simple JSON\" payload encoding is not supported by Everyware Cloud. Use the default \"Kura Protobuf\" encoding instead.

    "},{"location":"cloud-platform/kura-ec-cloud/#dataservice","title":"DataService","text":"

    The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

    In order for Kura to connect to Everyware Cloud on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

    Note

    Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

    "},{"location":"cloud-platform/kura-ec-cloud/#mqttdatatransport","title":"MqttDataTransport","text":"

    While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

    • broker-url - defines the MQTT broker URL that was provided when the Eurotech Everyware Cloud account was established. Information on how to obtain the broker URL can be found here. In the MqttDataTransport configuration screen capture shown below, the broker-url is mqtt://broker-sbx.everyware.io:1883
    • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
    • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

    Note

    When connecting to Everyware Cloud, the username must have proper permissions. Information on users and permissions can be found here.

    For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

    "},{"location":"cloud-platform/kura-ec-cloud/#connectdisconnect","title":"Connect/Disconnect","text":"

    The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

    Note

    Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

    "},{"location":"cloud-platform/kura-hono/","title":"Eclipse Hono\u2122 platform","text":"

    Eclipse Hono\u2122 provides remote service interfaces for connecting large numbers of IoT devices to a back end and interacting with them in a uniform way regardless of the device communication protocol. More information can be found here. This document outlines how to connect to Eclipse Hono using the Kura Gateway Administrative Console.

    "},{"location":"cloud-platform/kura-hono/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

    The Kura Gateway Administrative Console exposes all services necessary for connecting to Eclipse Hono. First of all, in the Cloud Connections section, a new Hono-enabled connection needs to be setup.

    From the Cloud Connections section,

    the user needs to create a new connection:

    by specifying a valid PID:

    The result should be like the one depicted in the following image:

    The reference links listed below outline each service involved in the cloud connection. It is recommended that each section be reviewed.

    • CloudService
    • DataService
    • MqttDataTransport
    "},{"location":"cloud-platform/kura-hono/#cloudservice","title":"CloudService","text":"

    The default settings for the CloudService are typically adequate for connecting to a Hono instance. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

    "},{"location":"cloud-platform/kura-hono/#dataservice","title":"DataService","text":"

    The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

    In order for Kura to connect to Eclipse Hono on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

    Note

    Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

    "},{"location":"cloud-platform/kura-hono/#mqttdatatransport","title":"MqttDataTransport","text":"

    While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

    • broker-url - defines the MQTT broker URL that was provided when the Eurotech Everyware Cloud account was established. In the MqttDataTransport configuration screen capture shown below, the broker-url is mqtt://broker-url:1883
    • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
    • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

    For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

    "},{"location":"cloud-platform/kura-hono/#connectdisconnect","title":"Connect/Disconnect","text":"

    The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

    Note

    Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

    "},{"location":"cloud-platform/kura-kapua/","title":"Eclipse Kapua\u2122 platform","text":"

    Eclipse Kapua\u2122 is a modular platform providing the services required to manage IoT gateways and smart edge devices. Kapua provides a core integration framework and an initial set of core IoT services including a device registry, device management services, messaging services, data management, and application enablement. More information can be found here. This document outlines how to connect to Eclipse Kapua using the Kura Gateway Administrative Console.

    "},{"location":"cloud-platform/kura-kapua/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

    The Kura Gateway Administrative Console exposes all services necessary for connecting to Eclipse Kapua. The reference links listed below outline each service involved in the cloud connection. It is recommended that each section be reviewed.

    • CloudService
    • DataService
    • MqttDataTransport
    "},{"location":"cloud-platform/kura-kapua/#cloudservice","title":"CloudService","text":"

    The default settings for the CloudService are typically adequate for connecting to a Kapua instance. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

    Warning

    The \"Simple JSON\" payload encoding is not supported by Kapua. Use the default \"Kura Protobuf\" encoding instead.

    "},{"location":"cloud-platform/kura-kapua/#dataservice","title":"DataService","text":"

    The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

    In order for Kura to connect to Eclipse Kapua on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

    Note

    Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

    "},{"location":"cloud-platform/kura-kapua/#mqttdatatransport","title":"MqttDataTransport","text":"

    While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

    • broker-url - defines the MQTT broker URL that was provided when the Kapua account was established.
    • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
    • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

    For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

    "},{"location":"cloud-platform/kura-kapua/#connectdisconnect","title":"Connect/Disconnect","text":"

    The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

    Note

    Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

    "},{"location":"cloud-platform/kura-sparkplug/","title":"Eclipse Sparkplug\u00ae Cloud Connector","text":"

    The org.eclipse.kura.cloudconnection.sparkplug.mqtt.provider package provides a Eclipse Kura Cloud Connection that implements the Eclipse Sparkplug\u00ae v3.0.0 specification.

    Minimum requirements

    This addon is compatible with Kura 5.2+.

    "},{"location":"cloud-platform/kura-sparkplug/#introduction-to-eclipse-sparkplug","title":"Introduction to Eclipse Sparkplug","text":"

    from Eclipse Sparkplug

    Sparkplug is an open software specification that provides MQTT clients the framework to seamlessly integrate data from their applications, sensors, devices, and gateways within the MQTT Infrastructure. It is specifically designed for use in Industrial Internet of Things (IIoT) architectures to ensure a high level of reliability and interoperability.

    The specification aims fulfill the following 3 goals:

    1. Define a common MQTT topic namespace.
    2. Define a common MQTT state management.
    3. Define a common MQTT payload.

    To achieve that, the Eclipse Sparkplug specification defines an architecture (see picture below) and 4 main actors:

    • Device: a collection of related data points, which may represent a physical device (like a PLC, a set of sensors, etc.) and that notify the value or quality change of their data points. In this cloud connection, a Device is represented by the attached Cloud Publishers.
    • Edge Node: the gateway that is responsible of the interaction with the MQTT broker and that estabilished sessions with the data-consuming Host Applications. This Cloud Connection assumes the role of Edge Node.
    • MQTT Server: a MQTT server that supports the v3.1.1 (or v5.0) version of the protocol.
    • Host Application: the data-consuming application that subscribes to the MQTT messages generated by the Edge Nodes. A Primary Host Application is responsible of controlling and monitoring Edge Nodes, which can be configured to modify their behavior based on the state of the Primary Host Application (for example, the application goes offline). You can imagine the Primary Host Application for Eclipse Kura being Eclipse Kapua.

    Image from https://sparkplug.eclipse.org/specification/version/3.0/documents/sparkplug-specification-3.0.0.pdf

    The main principles upon which the Specification is based on can be summarized as follows:

    1. PubSub Protocol: this is the main topology upon which the architecture is developed.
    2. Report by Exception (RBE): messages need to be sent by the Edge Node only when values at the edge change, and the message should contain only the value/metrics that changed. There is no need for continuos polling; although it is higly discouraged, it is not mandatory to have Edge Nodes apply the RBE.
    3. Continuos Session Awareness: Host Applications are aware of the state of the Edge Nodes, and Edge Nodes are aware of the state of the Host Applications. This continuos session awareness is achieved by the means of birth and death certificates and state messages (continue the reading to find out more) and is the key to allow reporting by exception.
    4. Birth and Death certificates: these messages represent the state of Edge Nodes and Devices (online/offline + data that will be reported). The birth messages are always the first ones that are sent from the Edge Node, and the delivery of death certificates is ensured through the MQTT Will message, even if the connection is lost ungracefully. The birth messages always contain all the metrics that the Device or Edge Node will ever report on. If a new metric is added or a metric gets removed, then a new session needs to be estabilished.
    5. Connection Persistence: with the mechanisms above, the connection does not need to be persistent. For example, an Edge Node that disconnects gracefully with a MQTT DISCONNECT packet will not be seen as \"dead\" from the host application because no death certificate has been triggered (Will messages are sent only on failures). Hence, the Edge Node can implement a logic where it remains connected only during the timeframe needed for sending the new data.

      Warning

      This Cloud Connection maintains a persistent connection to the MQTT server.

    "},{"location":"cloud-platform/kura-sparkplug/#eclipse-sparkplug-topic-namespace","title":"Eclipse Sparkplug Topic Namespace","text":"

    All clients using the specification must adhere to the following topic namespace:

    namespace/group_id/message_type/edge_node_id/[device_id]\n

    where:

    • namespace: defines the structure of the remaining elements and the encoding for the payload. With Sparkplug v3.0.0 the namespace to utilize is spBv1.0.
    • group_id: some Edge Nodes can be related to each other identifying a group (for example, Edge Nodes in a given plant). This element is the identifier for the group.
    • message_type: defines how to interpret and handle the payload. It encapsulates the semantic of the message and can be one of the following elements:
      • NBIRTH/NDEATH: Edge Node birth and death certificates.
      • DBIRTH/DDEATH: Device birth and death certificates.
      • NDATA/DDATA: message containing data reported by the Edge Node or Device.
      • NCMD/DMCD: message containing commands for the Edge Node or Device.
      • STATE: message from the Primary Host Application indicating the state (offline/online) of the main consumer.
    • edge_node_id: the identifier for the Edge Node.
    • device_id (optional): the identifier for the Device. Must be unique under the same edge_node_id and must be always present on messages belonging to Devices (D- type messages).

    The combination of group_id and edge_node_id must be unique and is called Edge Node Descriptor.

    Tip

    It is advised to have group_id, edge_node_id, and device_id as small but as descriptive as possible, for better efficiency.

    "},{"location":"cloud-platform/kura-sparkplug/#operational-behavior","title":"Operational Behavior","text":"

    This introduction will focus more on the Edge Node and Device, as they are of more interest for this Cloud Connection.

    "},{"location":"cloud-platform/kura-sparkplug/#session-management","title":"Session Management","text":"

    The session estabilishment procedure ensures the Edge Node to be subscribed to command-type messages for receiveing commands from the Host Application.

    An Edge Node can (optionally, but incouraged) specify to be aware of a primary host application state and, in such case, it needs to subscribe to the relative STATE messages. The Edge Node will send the birth certificate (and thus completing the session init procedure) only after receiving the STATE message denoting the Primary Host Application is online. After connection, if the Edge Node receives a STATE message denoting the Primary Host Application is offline, it must restart the session estabilishment procedure.

    On connection, the Edge Node sets a MQTT Will message containing a NDEATH certificate. Doing so, if the MQTT broker does not receive any communication within the Keep Alive period (client lost connection), it will send the Edge Node NDEATH certificate on all subscribers. A birth/death sequence number bdSeq is maintained in the Edge Node to match NBIRTH with NDEATH messages in the Host Application. Each bdSeq in the NDEATH message is matched with the corresponding bdSeq of the previous NBIRTH message. This allows the Host Application tracking the state of the Edge Nodes and mark not up-to-date metrics as STALE.

    A Device can send a device DBIRTH message after a Edge Node NBIRTH has been sent. The DBIRTH message contains all the metrics that the device will ever report on. If a new metric is added or an existing one removed, then the Device session needs to be re-estabilished. If the Edge Node looses the connection to some of its Devices, then it needs to send a Device DDEATH certificate on his behalf. The data-consuming Host Application will then mark that particular Device as offline and mark its metrics as STALE. Once the session is estabilished, the Device can publish the changed metrics using the DDATA message type.

    "},{"location":"cloud-platform/kura-sparkplug/#multiple-mqtt-server-topologies","title":"Multiple MQTT Server Topologies","text":"

    A Primary Host Application must publish its STATE message every time it connects to the MQTT broker. This ensures the Edge Nodes to be aware of its status as long as they remain connected to the MQTT server.

    At any point in time, an Edge Node can be connected to at most one MQTT server. If multiple MQTT servers are defined each time the Edge Node receives an offline STATE message from its Primary Host Application it needs to terminate the session and estabilish a new one with the next MQTT broker.

    "},{"location":"cloud-platform/kura-sparkplug/#further-resources","title":"Further Resources","text":"
    • Eclipse Sparkplug v3.0.0 specification
    • HiveMQ Sparkplug guides collection
    • Eclipse Sparkplug FAQ
    "},{"location":"cloud-platform/kura-sparkplug/#cloud-connection-configuration","title":"Cloud Connection Configuration","text":""},{"location":"cloud-platform/kura-sparkplug/#cloud-endpoint-layer-configuration","title":"Cloud Endpoint Layer Configuration","text":"

    The cloud endpoint layer allows to attach CloudPublishers and CloudSubscribers to publish/subscribe messages on Sparkplug topics.

    "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-device","title":"Sparkplug Device","text":"

    Each CloudPublisher attached to this cloud connection acts as a Sparkplug Device. The corresponding configuration is shown in the picture below.

    The parameter specified as device.id will dictate the Sparkplug device identifier used to publish messages from this cloud publisher. A device DBIRTH message is immediately sent from this publisher when the first publish occurs or when a the set of published metrics is changed. The subsequent messages will be published as Sparkplug device data (DDATA message type).

    The Sparkplug Device implemented by this publisher does not support the following features (optional in the Eclipse Sparkplug specification):

    • tck-id-operational-behavior-device-ddeath: Device death messages (DDEATH message type) since the usual way to publish data from the Wire Graph using a WireAsset attached to a CloudPublisher has no implementation for reporting error states (see WireAsset.onWireReceive)
    • tck-id-payloads-alias-uniqueness: Sparkplug aliases for metrics
    • tck-id-message-flow-device-dcmd-subscribe: writing to outputs, hence it will not subscribe to device command messages (DCMD message type)
    "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-device-payload","title":"Sparkplug Device Payload","text":"

    The payload of DDATA message will be encoded using the Sparkplug B Protobuf definition converting the KuraPayload into Sparkplug payload as follows:

    • Metrics from KuraPayload.metric() become Sparkplug metrics. Only the name, timestamp, datatype and value components are added. The timestamp is set to the publishing instant. The datatype is inferred from the Java type as follows:

      Java Type Sparkplug DataType Boolean DataType.Boolean byte[] DataType.Bytes Double DataType.Double Float DataType.Float Byte DataType.Int8 Short DataType.Int16 Integer DataType.Int32 Long DataType.Int64 String DataType.String Date DataType.DateTime BigInteger DataType.UInt64

      All other Java types will cause the application to throw an Exception.

    • KuraPayload.getBody(), if non null, will be copied into the body of the Sparkplug payload

    • KuraPayload.getPosition(), if not null, will be used to create the following metrics from the KuraPosition object, if the value in there is not null (with the corresponding Sparkplug data types):

      • kura.position.altitude: DataType.Double
      • kura.position.heading: DataType.Double
      • kura.position.latitude: DataType.Double
      • kura.position.longitude: DataType.Double
      • kura.position.precision: DataType.Double
      • kura.position.satellites: DataType.Int32
      • kura.position.status DataType.Int32
      • kura.position.speed: DataType.Double
      • kura.position.timestamp: DataType.DateTime
    • KuraPayload.getTimestamp(), if not null, it will be used as the timestamp metric of the Sparkplug payload

    "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-subscriber","title":"Sparkplug Subscriber","text":"

    This cloud connections allows creating a Cloud Subscriber that will subscribe to a generic set of topics. The configuration is shown in the picture below.

    It is assumed that the payloads received by this Cloud Connection are encoded using the Sparkplug B Protobuf definition. Users of this Cloud Subscriber should expect to receive KuraMessages containing a KuraPayload such that:

    • If non null, the Sparkplug body is used to set KuraPayload.setBody()
    • If non null, the Sparkplug seq Metric is converted into a Kura metric with name seq
    • If non null, the Sparkplug timestamp is used as Eclipse Kura's payload timestamp
    • All Sparkplug Metrics are converted into Kura metrics with the same name and the following conversion rules:

      Sparkplug ValueCase Java Type Boolean boolean Bytes byte[] Dataset byte[] Double double Extension byte[] Float float Integer int Long long String String Template byte[] default null

      The metric timestamp is not supported in Eclipse Kura, therefore this information is lost at conversion.

    "},{"location":"cloud-platform/kura-sparkplug/#data-service-layer-configuration","title":"Data Service Layer Configuration","text":"

    The DataService layer used in this component is the org.eclipse.kura.data.DataService implementation. Please refer to the Data Service Configuraion page for further details.

    "},{"location":"cloud-platform/kura-sparkplug/#data-transport-layer-configuration","title":"Data Transport Layer Configuration","text":"

    The Sparkplug Data Transport layer bridges the incoming requests to the underlying Eclipse Paho MQTT v3.1.1 client following the Sparkplug specification. In particular, the Data Transport Layer ensures the following.

    • Edge Node Sparkplug Session Estabilishment: the Edge Node (this Cloud Connection) estabilishes a new Sparkplug session upon connection. An optional Primary Host Application ID can be specified (see picture below) to make the Cloud Connection wait until the Primary Host Application is online before publishing NBIRTH and DBIRTH messages.
    • Edge Node Sparkplug Session Termination: upon disconnection, the Edge Node follows the required specification statements. In particular, multiple space-separated Server URIs can be specified in the component's configuration (see picture below). When a Primary Host Application ID is defined and it receives a STATE message denoting that the configured primary application is offline, then reconnection attempts are made cycling through the Server URIs list. When the last server fails to connect, then the traversal is restarted from the start of the list.
    • Edge Node NCMD handler: upon reception of a valid NCMD message, the Transport layer checks if it contains a Node Control/Rebirth metric, and, if set to true, restarts the session estabilishment procedure without sending an MQTT CONNECT packet (client connection is not closed, only BIRTH messages are re-sent).

    The Sparkplug Data Transport layer is configured to wait for a random period of time between 0sec and 5sec before each connection attempt. This is to ensure that, on large deployments, the target MQTT servers and Host Applications will dilute session estabilishment requests by some margin. This behavior is not part of the Sparkplug specification.

    This cloud connection supports SSL connections to the connecting broker. The option SslManagerService.target allows, as an OSGi target filter, to specify the pid of the SslManagerService instance to use for creating SSL connections for broker URIs that use the ssl:// protocol. The default socket factory is used otherwise.

    "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-implementation-details","title":"Sparkplug Implementation Details","text":""},{"location":"cloud-platform/kura-sparkplug/#edge-node","title":"Edge Node","text":"
    • The NBIRTH message sent by this cloud connection will not contain any metrics, except for the mandatory ones required by the specification.
    • This cloud connection does not send any NDATA messages in its default implementation.
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/","title":"Apache Camel\u2122 as a Kura application","text":"

    As of 2.1.0 Kura provides a set of different ways to implement an application backed by Camel:

    • Simple XML route configuration
    • Custom XML route configuration
    • Custom Java DSL definition
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-cloud-endpoint","title":"Kura cloud endpoint","text":"

    Kura provides a special \"Kura cloud endpoint\" which allows to publish or subscribe to the Kura Cloud API. The default component name for this component is kura-cloud but it may be overridden in the following use cases.

    The default component will only be registered once the default Kura Cloud API is registered with OSGi. This instance is registered with the OSGi property kura.service.pid=org.eclipse.kura.cloud.CloudService.

    If you want to publish to a different cloud service instance you can either manually register a new instance of this endpoint, or e.g. use a functionality like the Simple XML router provides: also see selecting a cloud service.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#endpoint-uri","title":"Endpoint URI","text":"

    The URI syntax of the endpoint is (assuming the default component name): kura-cloud:appid/topic. Where appid is the application ID registered with the Cloud API and topic is the topic to use.

    The following URI parameters are supported by the endpoint:

    Name Type Default Description applicationId String From URI path The application ID used with the Cloud API topic String From URI path The default topic name to publish/subscribe to when no header value is specified qos Integer 0 The QoS value when publishing to MQTT retain Boolean false The default retain flag when publishing to MQTT priority Integer 5 The default priority value control Boolean false Whether to publish/subscribe on the control or data topic hierarchy deviceId String empty The default device ID when publishing/subscribing to control topics

    The following header fields are supported. If a value is not set when publishing it is taken from the endpoint configuration:

    Name Type Description CamelKuraCloudService.topic String The name of the topic to publish to or from which the message was received CamelKuraCloudService.qos Integer The QoS to use when publishing to MQTT CamelKuraCloudService.retain Boolean The value of the retain flag when publishing to MQTT CamelKuraCloudService.control Boolean Whether to publish/subscribe on the control or data topic hierarchy CamelKuraCloudService.deviceId String The device ID when publishing to control topics"},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#cloud-to-cloud-messaging","title":"Cloud to cloud messaging","text":"

    As already described, header values override the endpoint settings. This allows for a finer grained control with Camel messaging. However this can cause unexpected behavior when two Cloud API endpoints are bridged. Camel can received from a Cloud endpoint but also publish to it. Now it is possible to write Camel routes with exchange messages, receiving from one Cloud API, pushing to another.

    -------------------             -------------------\n| Cloud Service A |    <--->    | Cloud Service B |\n-------------------             -------------------\n

    Which could result in a Camel route XML like:

    <route id=\"bridgeLocalToRemote\">\n  <from uri=\"local-cloud:sensor/sensor1\" />\n  <to   uri=\"upstream-cloud:gateway1/all-sensors\" />\n</route>\n

    However the Consumer (from) would set the topic header value with the topic name it received the message from. And the Producer (to) would get its topic from the URI overriden by that header value.

    In order to fix this behavior the header field has to be cleared before publishing:

    <route id=\"bridgeLocalToRemote\">\n  <from uri=\"local-cloud:sensor/sensor1\" />\n  <removeHeaders pattern=\"CamelKuraCloudService.topic\"/>\n  <to   uri=\"upstream-cloud:gateway1/all-sensors\" />\n</route>\n
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#simple-xml-routes","title":"Simple XML routes","text":"

    Eclipse Kura 2.1.0 introduces a new \"out-of-the-box\" component which allows to configure a set of XML based routes. The component is called \"Camel XML router\" and can be configured with a simple set of XML routes.

    The following example logs all messages received on the topic foo/bar to a logger named MESSAGE_FROM_CLOUD:

    <routes xmlns=\"http://camel.apache.org/schema/spring\">\n  <route id=\"cloudConsumer\">\n    <from uri=\"kura-cloud:foo/bar\"/>\n    <to uri=\"log:MESSAGE_FROM_CLOUD\"/>\n  </route>\n</routes>\n

    But it is also possible to generate data and push to upstream to the cloud service:

    <route id=\"route1\">\n  <from uri=\"timer:foo\"/>\n  <setBody>\n    <method ref=\"payloadFactory\" method=\"create('random',${random(10)})\"/>\n  </setBody>\n  <bean ref=\"payloadFactory\" method=\"append('foo','bar')\"/>\n  <to uri=\"stream:out\"/>\n  <to uri=\"kura-cloud:myapp/test\"/>\n</route>\n

    This example to run a timer named \"foo\" every second. It uses the \"Payload Factory\" bean, which is pre-registered, to create a new payload structure and then append a second element to it.

    The output is first sent to a logger and then to the cloud source.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#defining-dependencies-on-components","title":"Defining dependencies on components","text":"

    It is possible to use the Web UI to define a list of Camel components which must be present in order for this configuration to work. For example if the routes make use of the \"milo-server\" adapter for providing OPC UA support then \"milo-server\" can be added and the setup will wait for this component to be registered with OSGi before the Camel context gets started.

    The field contains a list of comma separated component names: e.g. milo-server, timer, log

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#selecting-a-cloud-service","title":"Selecting a cloud service","text":"

    It is also possible to define a map of cloud services which will be available for upstream connectivity. This makes use of Kura's \"multi cloud client\" feature. CloudService instances will get mapped from either a Kura Service PID (kura.service.pid, as shown in the Web UI) or a full OSGi filter. The string is a comma seperated, key=value string, where the key is the name of the Camel cloud the instance will be registered as and the value is the Kura service PID or the OSGi filter string.

    For example: cloud=org.eclipse.kura.cloud.CloudService, cloud-2=foobar

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#custom-camel-routers","title":"Custom Camel routers","text":"

    If a standard XML route configuration is not enough then it is possible to use XML routes in combination with a custom OSGi bundle or a Java DSL based Camel approach. For this to work a Kura development setup is required, please see Getting started for more information.

    The implementation of such Camel components follow the standard Kura guides for developing components, like, for example, the ConfigurableComponent pattern. This section only describes the Camel specifics.

    Of course it is also possible to follow a very simple approach and directly use the Camel OSGi functionalities like org.apache.camel.core.osgi.OsgiDefaultCamelContext.

    Note

    Kura currently doesn't support the OSGi Blueprint approach

    Kura support for Camel is split up in two layers. There is a more basic support, which helps in running a raw Camel router. This is CamelRunner which is located in the package org.eclipse.kura.camel.router. And then there are a few abstract basic components in the package org.eclipse.kura.camel.component which help in creating Kura components based on Camel.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#camel-components","title":"Camel components","text":"

    The base classes in org.eclipse.kura.camel.component are intended to help creating new OSGi DS components base on Camel.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#xml-based-component","title":"XML based component","text":"

    For an XML based approach which can be configured through the Kura ConfigurationService the base class AbstractXmlCamelComponent can be used. The constructor expectes the name of a property which will contain the Camel XML router information when it gets configured through the configuration service. It will automatically parse and apply the Camel routes.

    The method void beforeStart(CamelContext camelContext) may be used in order to configure the Camel context before it gets started.

    Every time the routes get updated using the modified(Map<String, Object>) method, the route XML will be re-parsed and routes will be added, removed or updated according to the new XML.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#java-dsl-based-component","title":"Java DSL based component","text":"

    In order to create a Java DSL based router setup the base class AbstractJavaCamelComponent may be used, which implements and RouteBuilder class, a simple setup might look like:

    import org.eclipse.kura.camel.component.AbstractJavaCamelComponent;\n\nclass MyRouter extends AbstractJavaCamelComponent {\n   public void configure() throws Exception {\n      from(\"direct:test\")\n         .to(\"mock:test\");\n   }\n}\n
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#using-the-camelrunner","title":"Using the CamelRunner","text":"

    The CamelRunner class is not derived from any OSGi or Kura base class and can be used in scenarios where more flexibility is required. It allows to define a set of pre-requisites for the Camel context. It is for example possible to define a dependency on a Kura cloud service instance and a Camel component provider. Once the runner is started it will listen for OSGi services resolving those dependencies and then starting up the Camel context. The following example shows how to set up a Camel context using the CamelRunner:

    // create a new camel Builder\n\nBuilder builder = new CamelRunner.Builder();\n\n// add service dependency\n\nbuilder.cloudService(\"kura.service.pid\", \"my.cloud.service.pid\");\n\n// add Camel component dependency to 'milo-server'\n\nbuilder.requireComponent(\"milo-server\");\n\nCamelRunner runner = builder.build();\n\n// set routes\n\nrunner.setRoutes ( new RouteBuilder() {\n  public void configure() throws Exception {\n    from(\"direct:test\")\n      .to(\"mock:test\");\n  }\n} );\n\n// set routes\n\nrunner.start ();\n

    It is also possible to later update routes with a call to setRoutes:

    // maybe update routes at a later time\n\nrunner.setRoutes ( /* different routes */ );\n
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#examples","title":"Examples","text":"

    The following examples can help in getting started.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-example-publisher","title":"Kura Camel example publisher","text":"

    The Camel example publisher (org.eclipse.kura.example.camel.publisher) can be used as an reference for starting. The final OSGi bundle can be dropped into a Kura application an be started. It allows to configure dynamically during runtime and is capable of switching CloudService instances dynamically.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-quickstart","title":"Kura Camel quickstart","text":"

    The Camel quickstart project (org.eclipse.kura.example.camel.quickstart) shows two components, Java and XML based, working together. The bundle can also be dropped into Kura for testing.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-aggregation","title":"Kura Camel aggregation","text":"

    The Camel quickstart project (org.eclipse.kura.example.camel.aggregation) shows a simple data aggregation pattern with Camel by processing data and publishing the result.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel-cloud/","title":"Apache Camel\u2122 as a Kura cloud service","text":"

    The default way to create a new cloud service instance backed by Camel is to use the new Web UI for cloud services. A new cloud service instance of the type org.eclipse.kura.camel.cloud.factory.CamelFactory has to be created. In addition to that a set of Camel routes have to be provided.

    The interface with the Kura application is the Camel vm component. Information set \"upstream\" from the Kura application can be received by the Camel cloud service instance of the following endpoint vm:camel:example. Where camel is the application id and example is the topic.

    The following code snippet writes out all of the Kura payload structure received on this topic to the logger system:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<routes xmlns=\"http://camel.apache.org/schema/spring\">\n\n  <route id=\"camel-example-topic\">\n    <from uri=\"vm:camel:example\"/>\n    <split>\n      <simple>${body.metrics().entrySet()}</simple>\n      <setHeader headerName=\"item\">\n        <simple>${body.key()}</simple>\n      </setHeader>\n      <setBody>\n        <simple>${body.value()}</simple>\n      </setBody>\n      <toD uri=\"log:kura.data.${header.item}\"/>\n    </split>\n  </route>\n\n</routes>\n

    The snippet splits up the incoming KuraPayload structure and creates a logger called kura.data.<metric> for each metric and writes out the actual value to it. The output in the log file should look like:

    2016-11-14 16:14:34,539 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:34,566 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.226808617581144]\n2016-11-14 16:14:35,575 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:35,602 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.27218775669447]\n2016-11-14 16:14:36,539 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:36,567 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.314456684208022]\n
    "},{"location":"cloud-platform/apache-camel-integration/kura-camel/","title":"Apache Camel\u2122 integration overview","text":"

    Note

    This document describes the Camel integration for Kura 2.1.0

    Kura provides two main integration points for Camel:

    • Camel as a Kura application
    • Camel as a Kura cloud service

    The first allows one to configure Camel to provide data and receive commands from any CloudService instance which is configured in Kura. For example the default CloudService instance which is backed by MQTT.

    The second approach allows one to create a custom CloudService implementation and route data coming from other Kura applications with the routes provided by this Camel context.

    "},{"location":"cloud-platform/apache-camel-integration/kura-camel/#deploying-additional-camel-components","title":"Deploying additional Camel components","text":"

    Kura comes with the following Camel components pre-installed:

    • camel-core
    • camel-core-osgi
    • camel-stream

    If additional Camel components are required, they can be installed using deployment packages (DP), as common with Kura.

    There are pre-packaged DPs available for e.g. AMQP, OPC UA, MQTT and other Camel components outside of the Kura project.

    "},{"location":"connect-field-devices/IO-apis/","title":"I/O APIs","text":"

    The full Eclipse Kura API reference is available here.

    In this page, the developer can find a synthetic grouping of the I/O APIs added starting from Kura 3.1.0.

    • Drivers

      • ChannelDescriptor
      • ChannelListener
      • ChannelRecord
      • ConnectionException
    • Assets

      • AssetConfiguration
      • Channel
    "},{"location":"connect-field-devices/asset-implemetation/","title":"Asset implementation","text":"

    An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

    Assets can be used as Wire Components to access the resources referenced by the defined channels inside a Wire Graph, see the Assets as Wire Components guide for more details.

    "},{"location":"connect-field-devices/asset-implemetation/#channel-example","title":"Channel Example","text":"

    To further describe the concept of Channel and Asset, the following table shows a set of PLC register addresses as provided in a typical PLC documentation.

    Name Entity Address LED1 COILS 2049 LED2 COILS 2050 LED3 COILS 2051 LED4 RED COILS 2052 LED4 GREEN COILS 2053 LED4 BLUE COILS 2054 Counter 3 INPUT REGISTERS 515 Quad Counter INPUT REGISTERS 520 Toggle 4 DISCRETE INPUTS 2052 Toggle 5 DISCRETE INPUTS 2053 Toggle 6 DISCRETE INPUTS 2054 Reset Counter 3 COILS 3075 Reset Quad Counter COILS 3084

    The corresponding Channels definition in the Asset is as follows:

    As shown in the previous image, the Channel definition in an Asset results easily mappable to what available in a generic PLC documentation.

    Once defined the Channels in an Asset, a simple Java application that leverages the Asset API can easily communicate with the Field device by simply referring to the specific Channel of interest.

    "},{"location":"connect-field-devices/asset-implemetation/#channel-definition","title":"Channel Definition","text":"
    • enabled: each channel can be separately enabled using this flag.
    • name: unique user-friendly name for a channel
    • type: represents the type of operation supported. Possible values are: READ, WRITE, READ/WRITE
    • value.type: represents the data type that will be used when creating the Wire Envelope for the connected components and the output value for READ channel.
    • scale: an optional scaling factor to be applied only to the numeric values retrieved from the field. It is parsed as a double. See below for more details.
    • offset: an optional offset value that will be added only to the numeric values retrieved from the field. It is parsed as a double. See below for more details.
    • scaleoffset.type: Allows to customise the way scale and offset is applied. See below for more details.
    • unit: an optional string value that will be added to the asset channel read to represent the unit of measure associated to that specific channel.
    • listen: if supported by the associated driver, allows to receive notifications by the driver on events. This flag currently has effect only inside Kura Wires.

    For the READ and READ/WRITE channels, the Asset typically asks the driver to provide a value based on the value.type. If the scaleoffset.type is LONG or DOUBLE, the type requested will be one of these and the final value (after the scale and offset operation) will be transformed into the data type expected by value.type.

    "},{"location":"connect-field-devices/asset-implemetation/#arithmetic-with-scale-and-offset","title":"Arithmetic with scale and offset","text":"

    The Asset supports applying a scale and offset to the values obtained by the attached Driver during read operations and to the values received in listen mode.

    Warning

    Application of scale and offset for write operations is not supported.

    The following modes are implemented for computing scale and offset:

    • DOUBLE
    • INTEGER
    • LONG
    • FLOAT

    The values of the scale and offset configuration parameters and the value obtained from the Driver are all converted to the mode type using the Java casting and then the following operation is performed: channel_value * scale + offset. The operation result is casted again to value.type to produce the final channel value.

    The values of the scale and offset parameters are parsed from channel configuration as doubles.

    The mode can be selected in the following way:

    • If the scaleoffset.type is set to DOUBLE or LONG the corrisponding mode will be used. The largest representable LONG is 2^53
    • If the scaleoffset.type value is DEFINED_BY_VALUE_TYPE the operation mode is determinated by value.type.

    Example of DOUBLE mode: channel configured with:

    • value.type: INTEGER
    • scaleoffset.type: DOUBLE
    • scale: 3.25
    • offset: 1.5

    Since scaleoffset.type is DOUBLE, the scale and offset mode is forced to DOUBLE.

    If channel_value is 5 (INTEGER) the result is: (int) ((double) channel_value * (double) 3.25d + (double) 1.5d) = 17.

    Example of INTEGER mode: channel configured with:

    • value.type: INTEGER
    • scaleoffset.type: DEFINED_BY_VALUE_TYPE
    • scale: 3.25
    • offset: 1.5

    Since scaleoffset.type is DEFINED_BY_VALUE_TYPE, the mode determined by the value.type parameter (INTEGER) will be used.

    If channel_value is 5 (INTEGER) the result is: (int) (channel_value * (int) 3.25d + (int) 1.5d) = 16.

    Example of DOUBLE mode: channel configured with:

    • value.type: DOUBLE
    • scaleoffset.type: DEFINED_BY_VALUE_TYPE
    • scale: 3.25
    • offset: 1.5

    If channel_value is 5 (DOUBLE) the result is: (double) (channel_value * (double) 3.25d + (double) 1.5d) = 17.75.

    Warning

    If the mode is LONG or INTEGER the decimal part of the scale and offset value will be discarded.

    As the examples show the final result can be different depending on the used mode and value.type.

    "},{"location":"connect-field-devices/asset-implemetation/#driver-specific-parameters","title":"Driver specific parameters","text":"

    The parameters that are not included in list of driver independent parameters above are driver specific. These parameters are used to identify the resource addressed by the channel.

    Driver specific parameters are described in the driver documentation.

    "},{"location":"connect-field-devices/asset-implemetation/#other-asset-configurations","title":"Other Asset Configurations","text":"
    • asset.desc: a user friendly description of the asset
    • emit.all.channels: specifies whether the values of all READ or READ_WRITE channels should be emitted in case of a channel event. If set to true, the values for all channels will be read and emitted, if set to false, only the value for the channel related to the event will be emitted.
    • timestamp.mode: if set to PER_CHANNEL, the component will emit a driver-generated timestamp per channel property. If set to SINGLE_ASSET_GENERATED, the component will emit a single timestamp per request, generated by the Asset itself before emitting the envelope. If set to SINGLE_DRIVER_GENERATED_MAX or SINGLE_DRIVER_GENERATED_MIN, the component will emit a single driver generated timestamp being respectively the max (most recent) or min (oldest) among the timestamps of the channels.
    • emit.errors: Specifies whether errors should be included or not in the emitted envelope. Default is false.
    • emit.on.change: If set to true, this component will include a channel value in the output emitted in Kura Wires only if it is different than the one from the previous read operation or event. Channel errors will always be emitted if emit.errors is set to true.
    • emit.empty.envelopes: If set to false, this component will not emit empty envelopes. This property can be useful if combined with emit.on.change.
    "},{"location":"connect-field-devices/asset-implemetation/#channels-download","title":"Channels download","text":"

    The creation of the channels is a process that could be very time and effort consuming. For this reason, once the user has created the desired channels, it is possible to download the entire list from the web UI, by clicking the Download Channels button:

    The list is downloaded in csv format and is represented in the form of a table, whose columns represent the entries for the options in the channel definition, while each row is equivalent to a specific channel. For example, the resulting csv file retrieved from downloading the list of channels in the image above is:

    The resulting table is composed by some static, pre-defined options (the ones mentioned in Channel Definition section above, from enabled to listen) that are the same for each component and the ones introduced by the specific bundle under analisys. In this case, the modbus driver introduces these driver-specific options:

    • unit.id
    • primary.table
    • memory.address
    • data.order
    • data.type
    • array.data.length

    The download tool is extremely useful in all those situations where the user wants to quickly load a long list of channels at once: for example, it can easily clone the same list in multiple assets, export it to another device, or simply save it locally to always have a copy of the channels ready.

    "},{"location":"connect-field-devices/asset-implemetation/#csv-upload","title":"CSV upload","text":"

    The Upload Channels button allows the user to load a local csv file to create the channel list in one shot: the uploading file must implement the same table-structure described in the \"Channels download\" section above, so with each rows representing a channels, and each column one entry of the channel's options.

    Once the user clicks on the button, will be shown a pop-up in which there are a button to locate the file in the user's filesystem and two checkboxes to allow the uploading phase customization. After clicking on Upload, if the process is succesful, the new channels list will be shown, and the user is just asked to click on the Apply button to save the asset variation.

    "},{"location":"connect-field-devices/asset-implemetation/#force-import-empty-string-policy","title":"Force import empty string policy","text":"

    The Force import empty string policy checkbox forces the parsing of all empty values present in the csv as empty strings, for all those channel's options that are typed to String and \"not required\" by the metatype.

    So, this box must be checked when the user wants that all empty values from the csv are considered as empty strings (\"\") when parsed to driver-specific channel option, defined by the metatype as String type and not required. So, those values that are null in the csv, will be set as \"\" in the channel options. The user must be cautious, because the framework supposes that it's consciously leaving empty values in the csv.

    Otherwise, if the box is unchecked, all the csv empty values will be considered as null strings and parsed to the default value set in the metatype of the driver.

    If, for example, the user downloads the channels list following the steps described in the precious section, it could upload the same csv file into an other asset, or another device, to get the exactly same asset configuration.

    "},{"location":"connect-field-devices/asset-implemetation/#replace-current-channels","title":"Replace current channels","text":"

    The Replace current channels must be checked when the user wants to replace all the channels currently present in the asset, with the ones that will be created by the csv uploading.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/","title":"ASSET-V1 MQTT Namespace","text":"

    The ASSET-V1 namespace allows to perform remote operations on the assets defined in an Kura-powered device. The requests and responses are represented as JSON arrays placed in the body of the MQTT payload.

    The namespace includes the following topics.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#getassets","title":"GET/assets","text":"

    This topic is used to retrieve metadata describing the assets defined on a specific device and their channel configuration.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format","title":"Request format","text":"

    The request can contain JSON array containing a list of asset names for which the metadata needs to be returned. The request JSON must have the following structure:

    [\n  {\n    \"name\": \"asset1\"\n  },\n  {\n    \"name\": \"otherAsset\"\n  }\n]\n

    The request JSON is an array with all elements of the type object. The array object has the following properties:

    • name (string, required): the name of the Asset for which the metadata needs to be returned.

    If the provided array is empty or the request payload is empty, the metadata describing all assets present on the device will be returned.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format","title":"Response format","text":"

    The response payload contains a JSON array with the following structure:

    [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"mode\": \"READ\"\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"mode\": \"READ_WRITE\"\n      },\n      {\n        \"name\": \"other_channel\",\n        \"type\": \"STRING\",\n        \"mode\": \"WRITE\"\n      }\n    ]\n  },\n  {\n    \"name\": \"otherAsset\",\n    \"channels\": []\n  },\n  {\n    \"name\": \"nonExistingAsset\",\n    \"error\": \"Asset not found\"\n  }\n]\n

    All elements of the array are of the type object. The array object has the following properties:

    • name (string, required): the name of the asset
    • error (string): this property is present only if the metadata for a not existing asset was explicitly requested, it contains an error message.
    • channels (array): the list of channels defined on the asset, it can be empty if no channels are defined. This property and the error property are mutually exclusive. This object is an array with all elements of the type object and they have the following properties:
      • name (string, required): the name of the channel.
      • mode (string, required): the mode of the channel. The possible values are READ, WRITE or READ_WRITE.
      • type (string, required): the value type of the channel. The possible values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#execread","title":"EXEC/read","text":"

    This topic is used to perform a read operation on a specified set of assets and channels.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format_1","title":"Request format","text":"

    The request can contain a JSON array with the following structure:

    [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"channel1\"\n      },\n      {\n        \"name\": \"channel2\"\n      },\n      {\n        \"name\": \"otherChannel\"\n      }\n    ]\n  },\n  {\n    \"name\": \"otherAsset\"\n  }\n]\n

    The request JSON is an array with all elements of the type object. If the list is empty or if the request payload is empty all channels of all assets will be read. The array object has the following properties:

    • name (string, required): the name of the asset involved in the read operation
    • channels (array): the list of the names of the channels to be read, if this property is not present or if its value is an empty array, all channels for the specified asset will be read. The object is an array with all elements of the type object. The array object has the following properties:
    • name (string, required): the name of the channel to be read
    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format_1","title":"Response Format","text":"

    The response is returned as a JSON array placed in the body of the response:

    [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"value\": \"432\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"value\": \"true\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"other_channel\",\n        \"error\": \"Read failed\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"binary_channel\",\n        \"type\": \"BYTE_ARRAY\",\n        \"value\": \"dGVzdCBzdHJpbmcK\",\n        \"timestamp\": 1234550\n      }\n    ]\n  },\n  {\n    \"name\": \"nonExistingAsset\",\n    \"error\": \"Asset not found\"\n  }\n]\n

    The response JSON is an array with all elements of the type object. The array object has the following properties:

    • name (string, required): the name of the asset.
    • error (string): an error message. This property is present only if a read operation for a not existing asset was explicitly requested.
    • channels (array): the object is an array with all elements of the type object. The array object has the following properties:
    • name (string, required): the name of the channel.
    • timestamp (integer, required): the device timestamp associated with the result in milliseconds since the Unix Epoch.
    • type (string): the type of the result. This property is present only if the operation succeeded. The possible values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
    • value (string): the result value of the read request encoded as a String. This property is present only if the operation succeeded. If the channel type is BYTE_ARRAY, the result will be represented using the base64 encoding.
    • error (string): an error message. This property is present only if the operation failed.
    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#execwrite","title":"EXEC/write","text":"

    Performs a write operation on a specified set of channels and assets.

    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format_2","title":"Request format","text":"

    The request must contain a JSON array with the following structure:

    [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"value\": \"432\",\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"value\": \"true\",\n      },\n      {\n        \"name\": \"binary_channel\",\n        \"type\": \"BYTE_ARRAY\",\n        \"value\": \"dGVzdCBzdHJpbmcK\",\n      }\n    ]\n  }\n]\n

    The array object has the following properties:

    • name (string, required): the name of the asset.
    • channels (array, required): the list of channel names and values to be written. The object is an array with all elements of the type object. The array object has the following properties:
      • name (string, required): the name of the channel.
      • type (string, required): the type of the value to be written. The allowed values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
      • value (string, required): the value to be written encoded as a String. If the channel type is BYTE_ARRAY, the base64 encoding must be used.
    "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format_2","title":"Response format","text":"

    The response uses the same format as the EXEC/read request, in case of success the type and value properties in the response will report the same values specified in the request.

    "},{"location":"connect-field-devices/driver-and-assets/","title":"Drivers, Assets and Channels","text":"

    Eclipse Kura introduces a model based on the concepts of Drivers and Assets to simplify the communication with the field devices attached to a gateway.

    A Driver encapsulates the communication protocol and its configuration parameters, dealing with the low-level characteristics of the field protocol. It opens, closes and performs the communication with the end field device. It also exposes field protocol specific information that can be used by upper levels of abstraction to simplify the interaction with the end devices.

    An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

    "},{"location":"connect-field-devices/driver-and-assets/#channel-example","title":"Channel Example","text":"

    To further describe the concept of Channel and Asset, the following table shows a set of PLC register addresses as provided in a typical PLC documentation.

    Name Entity Address LED1 COILS 2049 LED2 COILS 2050 LED3 COILS 2051 LED4 RED COILS 2052 LED4 GREEN COILS 2053 LED4 BLUE COILS 2054 Counter 3 INPUT REGISTERS 515 Quad Counter INPUT REGISTERS 520 Toggle 4 DISCRETE INPUTS 2052 Toggle 5 DISCRETE INPUTS 2053 Toggle 6 DISCRETE INPUTS 2054 Reset Counter 3 COILS 3075 Reset Quad Counter COILS 3084

    The corresponding Channels definition in the Asset is as follows:

    As shown in the previous image, the Channel definition in an Asset results easily mappable to what available in a generic PLC documentation.

    Once defined the Channels in an Asset, a simple Java application that leverages the Asset API can easily communicate with the Field device by simply referring to the specific Channel of interest.

    "},{"location":"connect-field-devices/driver-and-assets/#drivers-and-assets-in-kura-administrative-ui","title":"Drivers and Assets in Kura Administrative UI","text":"

    Kura provides a specific section of the UI to allow users to manage the different instances of Drivers and Assets. Using the Kura Web UI the user can instantiate and manage Drivers

    but also can manage Assets instances based on existing drivers.

    The user interface allows also to perform specific reads on the configured Assets' channels clicking on the Data tab for the selected Asset.

    "},{"location":"connect-field-devices/driver-implemetation/","title":"Driver implementation","text":"

    A Driver encapsulates the communication protocol and its configuration parameters.

    The Driver API abstracts the specificities of the end Fieldbus protocols providing a clean and easy to use set of calls that can be used to develop end-applications.

    Using the Driver APIs, an application can simply use the connect and disconnect methods to open or close the connection with the Field device. Furthermore, the read and write methods allow exchanging data with the Field device.

    A Driver instance can be associated with an Asset to abstract even more the low-level specificities and allow an easy and portable development of the Java applications that need to interact with sensors, actuators, and PLCs.

    The Asset will use the Driver's protocol-specific channel descriptor to compose the Asset Channel description.

    "},{"location":"connect-field-devices/driver-implemetation/#driver-configuration","title":"Driver Configuration","text":"

    Generally, a Driver instance is a configurable component which parameters can be updated in the Drivers and Assets section of the Kura Administrative User Interface.

    "},{"location":"connect-field-devices/driver-implemetation/#supported-field-protocols-and-availability","title":"Supported Field Protocols and Availability","text":"

    Drivers will be provided as add-ons available in the Eclipse IoT Marketplace. Please see here for a complete list.

    "},{"location":"connect-field-devices/driver-implemetation/#driver-specific-optimizations","title":"Driver-Specific Optimizations","text":"

    The Driver API provides a simple method to read a list of Channel Records:

    public void read(List<ChannelRecord> records) throws ConnectionException;\n

    Typically, since the records to read do not change until the Asset configuration is changed by the user, a Driver can perform some optimisations to efficiently read the requested records at once. For example, a Modbus driver can read a range of holding registers using a single request.

    Since these operations are costly, the Kura API adds methods to ask the driver to prepare reading a given list of records and execute the prepared read:

    public PreparedRead prepareRead(List<ChannelRecord> records);\n

    Invocation of the preparedRead method will result in a PreparedRead instance returned.

    On a PreparedRead, the execute method will perform the optimized read request.

    "},{"location":"connect-field-devices/eddystone-driver/","title":"Eddystone\u2122 Driver","text":"

    Eclipse Kura offers support for Eddystone\u2122 protocol via a specific driver. It can be used into the Wires framework, the Asset model or directly using the Driver itself.

    "},{"location":"connect-field-devices/eddystone-driver/#features","title":"Features","text":"

    The Eddystone\u2122 driver is designed to listen for incoming beacon packets and to recognise the specific protocol. Of course it's not possible to write data to the beacons, since this is outside the protocol specification. The frame format to be filtered can be chosen from the channel definition. For more information about Eddystone\u2122 frame format, see here.

    "},{"location":"connect-field-devices/eddystone-driver/#installation","title":"Installation","text":"

    As the others Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/eddystone-driver/#instance-creation","title":"Instance creation","text":"

    A new Eddystone Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.eddsytone factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

    "},{"location":"connect-field-devices/eddystone-driver/#channel-configuration","title":"Channel configuration","text":"

    The Eddystone Driver channel can be configured with the following parameters:

    • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.
    • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
    • eddystone.type: the type of the frame. Currently only UID and URL typed are supported.
    "},{"location":"connect-field-devices/field-protocols/","title":"Field Protocols","text":"

    Eclipse Kura provides support for field protocol implementations as add-ons deployable directly from the Eclipse Marketplace for IoT site. Moreover, several devices are supported using the Kura Driver model.

    Currently, the following field protocols and devices are supported and downloadable from the Eclipse Marketplace in form of Kura Drivers:

    Protocol/Device Kura 3.x Kura 4.x Kura 5.x OPC-UA link link link S7 link link link iBeacon N.A. link link Eddystone N.A. link link TiSensorTag link link link GPIO link link link SenseHat link link link"},{"location":"connect-field-devices/gpio-driver/","title":"GPIO Driver","text":"

    The GPIO Driver manages the General Purpose IOs on a gateway using the Driver model. Based on the GPIO Service, the driver can be used in the Wires framework, the Asset model or directly using the Driver itself.

    "},{"location":"connect-field-devices/gpio-driver/#features","title":"Features","text":"

    The GPIO Driver includes the following features:

    • support for digital input and output
    • support for unsolicited inputs
    • the trigger event can be configured directly through the Driver
    "},{"location":"connect-field-devices/gpio-driver/#installation","title":"Installation","text":"

    As the other Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/gpio-driver/#instance-creation","title":"Instance creation","text":"

    A new GPIO Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.gpio factory must be selected and a unique name must be provided for the new instance. Once instantiated, the GPIO Driver is ready to use and no configuration is needed.

    "},{"location":"connect-field-devices/gpio-driver/#channel-configuration","title":"Channel configuration","text":"

    The GPIO Driver channel can be configured with the following parameters:

    • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type. Conversely, in write operations the Driver will accept value of this kind.
    • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
    • resource.name: the name of the GPIO resource as reported by the GPIO Service. The #select resource selection has no effect on the channel.
    • resource.direction: the direction of the GPIO. Possible values are INPUTand OUTPUT. The #select direction selection has no effect on the channel.
    • resource.trigger: the type of event that triggers the listener, if selected. Possible values are:

    • NONE: no event will trigger the listener.

    • RISING_EDGE, FALLING_EDGE, BOTH_EDGES: the listeners will be triggered respectively by a low-high transition, a high-low transition or both.
    • HIGH_LEVEL,LOW_LEVEL, BOTH_LEVELS: the listeners will be triggered respectively by the detection of a high, low or both levels. Please note that these options aren't supported by all the devices.
    "},{"location":"connect-field-devices/gpio-driver/#drive-a-led-using-the-gpio-driver","title":"Drive a LED using the GPIO Driver","text":"

    In this section a simple example on the GPIO Driver using a RaspberryPi will be presented. Before configuring the Driver, arrange a setup as shown in the following picture, using a breadboard, a led, a 120-ohm resistor and some wires. Connect the yellow wire to a ground pin on the RasperryPi connector (i.e. pin 6) and the red one to pin 40 (a.k.a. gpio21).

    From the Drivers and Assets tab, create a new GPIO Driver, call it GPIODriver and add an Asset as shown in the following picture.

    The asset is configured to manage a gpio, called LED, as an output and drives it writing a boolean value. The LED channel is attached to the gpio21 on the RaspberryPi. In the Data tab, fill the Value form with true and press Apply: the green led will switch on. Writing a false will switch off the led.

    "},{"location":"connect-field-devices/ibeacon-driver/","title":"iBeacon\u2122 Driver","text":"

    Eclipse Kura provides a driver specifically developed to manage iBeacon\u2122 protocol. They can be used into the Wires framework, the Asset model or directly using the Driver itself.

    "},{"location":"connect-field-devices/ibeacon-driver/#features","title":"Features","text":"

    The iBeacon\u2122 driver is designed to listen for incoming beacon packets and to recognise the specific protocol. Of course it's not possible to write data to the beacons, since this is outside the protocol specification.

    "},{"location":"connect-field-devices/ibeacon-driver/#installation","title":"Installation","text":"

    As the others Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/ibeacon-driver/#instance-creation","title":"Instance creation","text":"

    A new iBeacon instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ibeacon factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

    "},{"location":"connect-field-devices/ibeacon-driver/#channel-configuration","title":"Channel configuration","text":"

    The iBeacon Driver channel can be configured with the following parameters:

    • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.
    • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
    "},{"location":"connect-field-devices/opcua-driver/","title":"OPC UA Driver","text":"

    This Driver implements the client side of the OPC UA protocol using the Driver model. The Driver can be used to interact as a client with OPC UA servers using different abstractions, such as the Wires framework, the Asset model or by directly using the Driver itself.

    The Driver is distributed as a deployment package on Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/opcua-driver/#features","title":"Features","text":"

    The OPC UA Driver features include:

    • Support for the OPC UA protocol over TCP.
    • Support for reading and writing OPC UA variable nodes by node ID.
    "},{"location":"connect-field-devices/opcua-driver/#instance-creation","title":"Instance creation","text":"

    A new OPC UA instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.opcua factory must be selected and a unique name must be provided for the new instance.

    "},{"location":"connect-field-devices/opcua-driver/#channel-configuration","title":"Channel configuration","text":"

    The OPC UA Driver channel configuration is composed of the following parameters:

    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value type: the Java type of the channel value.
    • node.id: The node id of the variable node to be used, the format of the node id depends on the value of the node.id.type property.
    • node.namespace.index: The namespace index of the variable node to be used.
    • opcua.type: The OPC-UA built-in type of the attribute to be read/written. If set to DEFINED_BY_JAVA_TYPE (default), the driver will attempt to determine the OPC-UA type basing on the value type parameter value. If the read/write operation fails, it may be necessary to use one of the other values of this configuration parameter to explicitly select the type.

      This parameter also lists the OPC-UA types currently supported by the driver.

      Not all value type and opcua.type combinations are valid, the allowed ones are the following:

      opcua.type Allowed value.types Recommended value.type BOOLEAN BOOLEAN BOOLEAN SBYTE INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT16 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT32 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT64 INTEGER, LONG, FLOAT, DOUBLE, STRING LONG BYTE INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER UINT16 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER UINT32 INTEGER, LONG, FLOAT, DOUBLE, STRING LONG UINT64 INTEGER, LONG, FLOAT, DOUBLE, STRING STRING FLOAT FLOAT, STRING FLOAT DOUBLE DOUBLE, STRING DOUBLE STRING STRING STRING BYTE_STRING BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY SBYTE_ARRAY BYTE_ARRAY BYTE_ARRAY

      Using a non allowed value.type will result in read/write operation failures. It should be noted that there is not a one to one match between the opcua.type and Java value.type. It is recommended to compare the allowed ranges for numeric types specified in OPC-UA Reference and Java reference for selecting the best match.

    • node.id.type: The type of the node id (see the Node Id types section)

    • attribute: The attribute of the referenced variable node.

    If the listen flag is enabled for an OPC-UA channel, the driver will request the server to send notifications if it detects changes in the referenced attribute value.

    In order to enable this, the driver will create a global subscription (one per Driver instance), and a monitored item for each channel. See [1] for more details. The Subscription publish interval global configuration parameter can be used to tune the subscription publishing interval.

    • listen.sampling.interval: The sampling interval for the monitored item. See the Sampling interval section of [1] for more details.
    • listen.queue.size: The queue size for the monitored item. See the Queue parameters section of [1] for more details.
    • listen.discard.oldest: The value of the discardOldest flag for the monitored item. See the Queue parameters section of [1] for more details.

    The listen.subscribe.to.children parameter can be used to enable the Subtree Subscription feature.

    [1] MonitoredItem model

    "},{"location":"connect-field-devices/opcua-driver/#node-id-types","title":"Node ID types","text":"

    The Driver supports the following node id types:

    Node ID Type Format of node.id NUMERIC node.id must be parseable into an integer STRING node.id can be any string OPAQUE Opaque node ids are represented by raw byte arrays. In this case node.id must be the base64 encoding of the node id. GUID node.id must be a string conforming to the format described in the documentation of the java.util.UUID.toString() method."},{"location":"connect-field-devices/opcua-driver/#certificate-setup","title":"Certificate setup","text":"

    In order to use settings for Security Policy different than None, the OPCUA driver must be configured to trust the server certificate and a new client certificate - private key pair must be generated for the driver. These items can be placed in a Java keystore that can be referenced from driver configuration. The keystore does not exist by default and without it connections that use Security Policy different than None will fail.

    The following steps can be used to generate the keystore:

    1. Download the certificate used by the server, usually this can be done from server management UI.
    2. Copy the downloaded certificate to the gateway using SSH.
    3. Copy the following example script to the device using SSH, it can be used to import the server certificate and generate the client key pair. It can be modified if needed:

      #!/bin/bash\n\n# the alias for the imported server certificate\nSERVER_ALIAS=\"server-cert\"\n\n# the file name of the generated keystore\nKEYSTORE_FILE_NAME=\"opcua-keystore.ks\"\n# the password of the generated keystore and private keys, it is recommended to change it\nKEYSTORE_PASSWORD=\"changeit\"\n\n# server certificate to be imported is expected as first argument\nSERVER_CERTIFICATE_FILE=\"$1\"\n\n# import existing certificate\nkeytool -import \\\n        -alias \"${SERVER_ALIAS}\" \\\n        -file \"${SERVER_CERTIFICATE_FILE}\" \\\n        -keystore \"${KEYSTORE_FILE_NAME}\" \\\n        -noprompt \\\n        -storepass \"${KEYSTORE_PASSWORD}\"\n\n# alias for client certificate\nCLIENT_ALIAS=\"client-cert\"\n# client certificate distinguished name, it is recommended to change it \nCLIENT_DN=\"CN=MyCn, OU=MyOu, O=MyOrganization, L=Amaro, S=UD, C=IT\"\n# the application id, must match the corresponding parameter in driver configuration\nAPPLICATION_ID=\"urn:kura:opcua:client\"\n\n# generate the client private key and certificate\nkeytool -genkey \\\n        -alias \"${CLIENT_ALIAS}\" \\\n        -keyalg RSA \\\n        -keysize 4096 \\\n        -keystore \"${KEYSTORE_FILE_NAME}\" \\\n        -dname \"${CLIENT_DN}\" \\\n        -ext ku=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment \\\n        -ext eku=clientAuth \\\n        -ext \"san=uri:${APPLICATION_ID}\" \\\n        -validity 1000 \\\n        -noprompt \\\n        -storepass ${KEYSTORE_PASSWORD} \\\n        -keypass ${KEYSTORE_PASSWORD}\n
    4. Update the following parameters in driver configuration:

    5. Keystore Path : Set the absolute path of the opcua-keystore.ks file created at step 3.
    6. **Security Policy ** -> Set the desired option
    7. Client Certificate Alias -> Set the value of the CLIENT_ALIAS script variable (the default is client-cert)
    8. Enable Server Authentication -> true
    9. Keystore type -> JKS
    10. Keystore Password -> Set the value of the KEYSTORE_PASSWORD script variable (the default value is changeit)
    11. Application URI -> Set the value of the APPLICATION_ID script variable (default value should be already ok).

    12. Configurare the server to trust the client certificate generated at step 3. The steps required to do this vary depending on the server. Usually the following steps are needed:

    13. Make a connection attempt using OPC-UA driver, this will likely fail because the server does not trust client certificate.
    14. After the failed connection attempt, the server should display the certificate used by the driver in the administration UI. The server UI should allow to set it as trusted.
    15. Make another connection attempt once the certificate has been set to trusted, this connection attempt should succeed.
    "},{"location":"connect-field-devices/opcua-driver/#substree-subscription","title":"Substree Subscription","text":"

    The driver can be configured to recursively visit the children of a folder node and create a Monitored Item for the value of each discovered variable node with a single channel in Asset configuration.

    Warning: This feature should be used with care since it can cause high load on both the gateway and the server if the referenced folder contains a large number of nodes and/or the notification rate is high.

    "},{"location":"connect-field-devices/opcua-driver/#channel-configuration_1","title":"Channel configuration","text":"

    In order to configure the driver to perform the discovery operation, a single channel can be defined with the following configuration:

    • type: READ
    • value.type: STRING (see below)
    • listen: true
    • node.id: the node id of the root of the subtree to visit
    • node.namespace.index: the namespace index of the root of the subtree to visit
    • node.id.type the node id type of the root of the subtree to visit
    • listen.subscribe.to.children (NEW): true

    The rest of the configuration parameters can be specified in the same way as for the single node subscription use case.

    The listen.sampling.interval, listen.queue.size and listen.discard.oldest parameters of the root will be used for all subscriptions on the subtree.

    "},{"location":"connect-field-devices/opcua-driver/#discovery-procedure","title":"Discovery procedure","text":"

    The driver will consider as folders to visit all nodes that whose type definition is FolderType, or more precisely all nodes with the following reference:

    HasTypeDefinition: * namespace index: 0 * node id: 61 (numeric) * URN: http://opcfoundation.org/UA/

    The driver will subscribe to all the variable nodes found.

    "},{"location":"connect-field-devices/opcua-driver/#event-reporting","title":"Event reporting","text":"

    If the Driver is used by a Wire Asset, it will emit on the wire a single message per received event.

    All emitted events will contain a single property. Depending on the value of the Subtree subscription events channel name format global configuration parameter, the name of this property is the node id or the browsed path of the source OPCUA node relative to the root folder defined in the channel configuration.

    "},{"location":"connect-field-devices/opcua-driver/#type-conversion","title":"Type conversion","text":"

    The current version of the driver tries to convert the values received for all the events on a subtree to the type defined in the value.type configuration parameter.

    Since the value types of the discovered nodes are heterogeneous, the conversion might fail if the types are not compatible (e.g. if value.type is set to INTEGER and the received value is a string).

    Setting value.type to STRING should allow to perform safe conversions for most data types.

    "},{"location":"connect-field-devices/s7-driver/","title":"S7comm Driver","text":"

    This Driver implements the s7comm protocol and can be used to interact with Siemens S7 PLCs using different abstractions, such as the Wires framework, the Asset model or by directly using the Driver itself.

    The Driver is distributed as a deployment package on the Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/s7-driver/#features","title":"Features","text":"

    The S7comm Plc Driver features include:

    • Support for the s7comm protocol over TCP.
    • Support for reading and writing data from the Data Blocks (DB) memory areas.
    • The driver is capable of automatically aggregating reads/writes for contiguous data in larger bulk requests in order to reduce IO times.
    "},{"location":"connect-field-devices/s7-driver/#instance-creation","title":"Instance creation","text":"

    A new S7comm driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.s7plc factory must be selected and a unique name must be provided for the new instance.

    "},{"location":"connect-field-devices/s7-driver/#channel-configuration","title":"Channel configuration","text":"

    The S7 Driver channel configuration is composed of the following parameters:

    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value type: the Java type of the channel value.
    • s7.data.type: the S7 data type involved in channel operation.
    • data.block.no: the data block number involved in channel operation.
    • offset: the start address of the data.
    • byte.count: the size in bytes of the transferred data. This parameter is required only if the value type parameter is set to STRING or BYTE_ARRAY. In the other cases, this parameter is ignored and the data size is automatically derived from the s7.data.type.
    • bit.index: the index of the bit involved in channel operation inside its containing byte, index 0 is the least significant bit. This parameter is required only if the value type parameter is set to BOOLEAN and s7.data.type is set to BOOL. In the other cases, this parameter is ignored.
    "},{"location":"connect-field-devices/s7-driver/#data-types","title":"Data Types","text":"

    When performing operations that deal with numeric data, two data types are involved:

    1. The Java primitive type that is used in the ChannelRecords exchanged between the driver and Java applications. (the Java type of the value received/supplied by external applications from/to the Driver in case of a read/write operation). This value type is specified by the value type configuration property.

    2. The S7 type of the data on the PLC. This value type is specified by the s7.data.type configuration property. The following S7 data types are supported:

      S7 Data Type Size Sign INT 16 bits signed DINT 32 bits signed WORD 16 bits unsigned DWORD 32 bits unsigned REAL 32 bits signed BOOL 1 bit n.d. BYTE 1 byte unsigned CHAR 1 byte (only supported as char arrays using the String Java data type) n.d.

    The Driver automatically adapts the data type used by external applications and the S7 device depending on the value of the two configuration properties mentioned above.

    The adaptation process involves the following steps:

    • Each device data type is internally converted by the driver from/to a Java type large enough to represent the value of the device data without losing precision. The type mappings are the following:

      S7 Data Type Java Type INT int DINT int WORD int DWORD long REAL float BOOL boolean BYTE int
    • If the value type of the channel does not match the Java type specified in mapping above, a conversion is performed by the Driver to convert it to/from the matching type, choosing appropriately between the Number.toInt(), Number.toLong(), Number.toFloat() or Number.toDouble() methods. Precision losses may occur if the Java type used by the external application is not suitable to represent all possible values of the device data type.

    "},{"location":"connect-field-devices/s7-driver/#array-data","title":"Array Data","text":"

    The driver supports transferring data as raw byte arrays or ASCII strings:

    • Byte arrays: For transferring data as byte arrays the channel value type property must be set to BYTE_ARRAY, the data.type configuration property must be set to BYTE and the byte.count property must be set to the data length in bytes.
    • Strings: For transferring data as ASCII strings the channel value type property must be set to STRING, the data.type configuration property must be set to CHAR and the array.data.length property must be set to the data length in bytes.
    "},{"location":"connect-field-devices/s7-driver/#writing-single-bits","title":"Writing Single Bits","text":"

    The Driver supports setting the value of single bits without overwriting the other bits of the same byte. This operation can be performed defining a channel having BOOLEAN as value type, BOOL as s7.data.type and the proper index set to the bit.index property.

    The Driver will fetch the byte containing the bit to be written, update its contents and then write back the obtained value. If multiple bits on the same byte need to be modified, the driver will perform only one read and write for that byte. If bits that need to be changed are located in contiguous bytes, the driver will perform only one bulk read and one bulk write transferring all the required data in a single request.

    "},{"location":"connect-field-devices/sensehat-driver/","title":"RaspberryPi SenseHat driver","text":"

    The SenseHat driver allows to interact to a RaspberryPi SenseHat device using Kura Driver, Asset and Wires frameworks. The driver allows access to the following resources:

    • Sensors
    • Joystick
    • LED Matrix

    The driver-specific channel configuration contains a single parameter, resource, which allows to select the specific device resource that the channel is addressing (a sensor, a joystick event, etc).

    Note about running on OpenJDK

    If some exceptions reporting Locked by other application are visible in the log and the driver fails to start, try switching to the Oracle JVM by installing the oracle-java8-jdk package. For more information on the problem, please see this GitHub issue.

    "},{"location":"connect-field-devices/sensehat-driver/#installation","title":"Installation","text":"

    As the others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

    In order to use the driver, the Sensehat Support Library Bundle for Eclipse Kura needs to be installed as a prerequisite dependency. It is available from Eclipse Marketplace here.

    "},{"location":"connect-field-devices/sensehat-driver/#sensors","title":"Sensors","text":"

    The following values of the resource parameters refer to device sensors:

    Resource Unit Description ACCELERATION_X, ACCELERATION_Y, ACCELERATION_Z G The proper acceleration for each axis GYROSCOPE_X, GYROSCOPE_Y, GYROSCOPE_Z rad/S The angular acceleration for each axis MAGNETOMETER_X, MAGNETOMETER_Y, MAGNETOMETERE_Z uT The magnetometer value for each axis HUMIDITY %rH The relative humidity PRESSURE mbar The pressure value TEMPERATURE_FROM_HUMIDITY \u00b0C The temperature obtained from the humidity sensor TEMPERATURE_FROM_PRESSURE \u00b0C The temperature obtained from the pressure sensor

    The channels referencing sensor resources can only be used for reads and only in polling mode. The driver will attempt to convert the value obtained from the sensor into the data type selected using the value.type parameter

    Example sensors Asset configuration:

    "},{"location":"connect-field-devices/sensehat-driver/#joystick","title":"Joystick","text":"

    The SenseHat joystick provides four buttons:

    • UP
    • DOWN
    • LEFT
    • RIGHT
    • ENTER

    For each button, the driver allows to listen to the following events:

    • PRESS: Fired once when a button is pressed
    • RELEASE: Fired once when a button is released
    • HOLD: Fired periodically every few seconds if the button is kept pressed

    The values of the resource parameter related to joystick have the following structure:

    JOYSTICK_{BUTTON}_{EVENT}

    Channels referencing joystick events must use LONG as value.type. The channel value supplied by the driver is the Java timestamp in milliseconds of the Joystick event, a value of 0 signifies that no events have been observed yet.

    Joystick related channels can be only used for reading and both in polling and event-driven mode.

    Example joystick Asset configuration:

    "},{"location":"connect-field-devices/sensehat-driver/#led-matrix","title":"LED Matrix","text":"

    The driver allows accessing the SenseHat LED matrix in two ways:

    • Using a monochrome framebuffer
    • Using a RGB565 framebuffer
    "},{"location":"connect-field-devices/sensehat-driver/#coordinates","title":"Coordinates","text":"

    The coordinate system used by the driver defines the x coordinate as increasing along the direction identified by the joystick RIGHT button and the y coordinate increasing along the direction identified by the DOWN joystick button.

    "},{"location":"connect-field-devices/sensehat-driver/#monochrome-framebuffer","title":"Monochrome framebuffer","text":"

    In monochrome mode, only two colors will be used: the front color and the back color.

    "},{"location":"connect-field-devices/sensehat-driver/#front-and-back-colors","title":"Front and back colors","text":"

    Front and back colors can be configured using channels having the following values for the resource parameter:

    • LED_MATRIX_FRONT_COLOR_R
    • LED_MATRIX_FRONT_COLOR_G
    • LED_MATRIX_FRONT_COLOR_B
    • LED_MATRIX_BACK_COLOR_R
    • LED_MATRIX_BACK_COLOR_G
    • LED_MATRIX_BACK_COLOR_B

    These channel types allow to set the rgb components of the front and back color and can only be used in write mode. The supplied value must be a floating point number between 0 (led turned off) and 1 (full brightness). Note: Front and back colors are internally represented using the RGB565 format. The available colors are only the ones that can be represented using RGB565 and are supported by the device. Front and back color are retained for successive draw operations, the initial value for both colors is black (led turned off).

    "},{"location":"connect-field-devices/sensehat-driver/#drawing","title":"Drawing","text":"

    The following resources can be used for modifying framebuffer contents:

    • LED_MATRIX_FB_MONOCHROME:

      A channel of this type allows to set the framebuffer contents in monochrome mode. It can only be used in write mode and its value.type must be BYTE_ARRAY.

      The supplied value must be a byte array of length 64 that represent the state of the 8x8 led matrix. The offset in the array of a pixel having coordinates (x, y) is y*8 + x. The back/front color will be used for a pixel if the corresponding byte in the array is zero/non-zero.

    • LED_MATRIX_CHARS:

      A channel of this type allows showing a text message using the LED matrix. It can only be used for writing and its value.type must be STRING. The characters of the message will be rendered using the front color and the background using the back color.

    "},{"location":"connect-field-devices/sensehat-driver/#rgb565-framebuffer","title":"RGB565 framebuffer","text":"

    The following resource allows writing the framebuffer using the RGB565 format:

    • LED_MATRIX_FB_RGB565:

      A channel of this type allows to set the framebuffer contents in RGB565 mode. It can only be used in write mode and its value.type must be BYTE_ARRAY.

      The supplied value must be a byte array of length 128 that represents the state of the 8x8 led matrix. Each pixel is represented by two consecutive bytes in the following way:

      |    MSB    |    LSB    |\n|  RRRRRGGG |  GGGBBBBB |\n|  15 ... 8 |  7 ... 0  |\n

      The LSB must be stored first in the array, the offset of the LSB and MSB for a pixel at coordinates (x, y) is the following:

      • LSB: 2*(y*8 + x)
      • MSB: 2*(y*8 + x) + 1
    "},{"location":"connect-field-devices/sensehat-driver/#mode-independent-resources","title":"Mode independent resources","text":"
    • LED_MATRIX_CLEAR:

      Writing anything to a LED_MATRIX_CLEAR channel will clear the framebuffer turning off all leds.

    • LED_MATRIX_ROTATION:

      Allows to rotate the framebuffer of 0, 90, 180, and 170 degrees clockwise. Rotation setting will be retained for successive draw operations. The default value is 0. Writes to a LED_MATRIX_ROTATION channel can be performed using any numeric type as value.type.

    Example framebuffer Asset configuration:

    "},{"location":"connect-field-devices/sensehat-driver/#examples","title":"Examples","text":"

    This section contains some examples describing how to use the driver using Kura Wires and the Wires Script filter.

    "},{"location":"connect-field-devices/sensehat-driver/#moving-a-pixel-using-the-joystick","title":"Moving a pixel using the Joystick","text":"
    1. Open the Wires section of the Kura Web UI.
    2. Create an Asset that emits joystick events like the one described in the Joystick section. Only left_release, right_release, up_release and down_release channels will be used. Make sure to enable the listen flag for the channels.
    3. Create the Asset described in the LED Matrix section.
    4. Create a Script Filter and paste the code below in the script field.
    5. Connect the Joystick Asset to the input port of the Script Filter, and the output port of the Script filter to the framebuffer Asset.
    6. Open the Drivers and Assets section of the Kura Web UI, select the framebuffer Asset, click on the Data tab and select front and back colors. For example for using green as front color and red as back color, write 1 as Value for the front_g and back_r channels.
    7. You should now be able to move the green pixel with the joystick.
    var FB_SIZE = 8\n\nif (typeof(state) === 'undefined') {\n  // framebuffer as byte array (0 -> back color, non zero -> front color)\n  var fb = newByteArray(FB_SIZE*FB_SIZE|0)\n  // record to be emitted for updating the fb\n  var outRecord = newWireRecord()\n  // property in emitted record containing fb data\n  // change the name if needed\n  // should match the name of a channel configured as LED_MATRIX_FB_MONOCHROME\n  outRecord['fb_mono'] = newByteArrayValue(fb)\n\n  state = {\n    fb: fb,\n    outRecord: outRecord,\n    x: 0, // current pixel position\n    y: 0,\n    dx: 0, // deltas to be added to pixel position\n    dy: 0,\n  }\n}\n\nif (typeof(actions) === 'undefined') {\n  // associations between input property names\n  // and position update actions,\n  // input can be supplied using an Asset\n  // with joystick event channels\n  actions = {\n    'up_release' : function () {\n      // decrease y coordinate\n      state.dy = -1\n    },\n    'down_release' : function () {\n      // increase y coordinate\n      state.dy = 1\n    },\n    'left_release' : function () {\n      // decrease x coordinate\n      state.dx = -1\n    },\n    'right_release' : function () {\n      // increase x coordinate\n      state.dx = 1\n    }\n  }\n}\n\nif (input.records.length) {\n  var input = input.records[0]\n  var update = false\n\n  for (var prop in input) {\n    var action = actions[prop]\n    // if there is an action associated with the received property, execute it\n    if (action) {\n      action()\n      // request framebuffer update\n      update = true\n    }\n  }\n\n  if (update) {\n    // framebuffer update requested\n    // clear old pixel\n    state.fb[state.y*FB_SIZE + state.x] = 0\n    // compute new pixel position\n    state.x = (state.x + state.dx + FB_SIZE) % FB_SIZE\n    state.y = (state.y + state.dy + FB_SIZE) % FB_SIZE\n    // set new pixel\n    state.fb[state.y*FB_SIZE + state.x] = 1\n    // clear deltas\n    state.dx=0\n    state.dy=0\n    // emit record\n    output.add(state.outRecord)\n  }\n}\n
    "},{"location":"connect-field-devices/sensehat-driver/#using-rgb565-framebuffer","title":"Using RGB565 framebuffer","text":"
    1. Open the Wires section of the Kura Web UI.
    2. Create the Asset described in the LED Matrix section.
    3. Create a Script Filter and paste the code below in the script field.
    4. Create a Timer that ticks every 16 milliseconds.
    5. Connect the Timer to the input port of the Script Filter, and the output port of the Script filter to the framebuffer Asset.
    6. The framebuffer should now display an animation, the animation can be changed by modifying the wave parameters and setting script.context.drop to false.
    var FB_SIZE = 8\nvar BYTES_PER_PIXEL = 2\n\nif (typeof(state) === 'undefined') {\n  // framebuffer as RGB565 byte array\n  var fb = newByteArray(FB_SIZE * FB_SIZE * BYTES_PER_PIXEL | 0)\n  // record to be emitted for updating the fb\n  var outRecord = newWireRecord()\n  // property in emitted record containing fb data\n  // change the name if needed\n  // must match the name of a channel configured as LED_MATRIX_FB_565\n  outRecord['fb_rgb565'] = newByteArrayValue(fb)\n\n  // framebuffer array and output record\n  state = {\n    fb: fb,\n    outRecord: outRecord,\n  }\n\n  RMASK = ((1 << 5) - 1)\n  GMASK =  ((1 << 6) - 1)\n  BMASK = RMASK\n\n  // converts the r, g, b values provided as a floating point\n  // number between 0 and 1 to RGB565 and stores the result\n  // inside the output array\n  function putPixel(off, r, g, b) {\n    var _r = Math.floor(r * RMASK) & 0xff\n    var _g = Math.floor(g * GMASK) & 0xff\n    var _b = Math.floor(b * BMASK) & 0xff\n\n    var b0 = (_r << 3 | _g >> 3)\n    var b1 = (_g << 5 | _b)\n\n    state.fb[off + 1] = b0\n    state.fb[off] = b1\n  }\n\n  // parameters for 3 sin waves, one per color component\n  RED_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 0\n  }\n\n  GREEN_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 1\n  }\n\n  BLUE_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 2\n  }\n\n  function wave(x, y, t, params) {\n    return Math.abs(Math.sin(2*Math.PI*(t + x/params.b + y/params.c + params.d)/params.a))\n  }\n}\n\nvar t = new Date().getTime() / 1000\n\nvar off = 0\nfor (var y = 0; y < FB_SIZE; y++)\n  for (var x = 0; x < FB_SIZE; x++) {\n    var r = wave(x, y, t, RED_WAVE_PARAMS)\n    var g = wave(x, y, t, GREEN_WAVE_PARAMS)\n    var b = wave(x, y, t, BLUE_WAVE_PARAMS)\n    putPixel(off, r, g, b)\n    off += 2\n  }\n\noutput.add(state.outRecord)\n
    "},{"location":"connect-field-devices/sensortag-driver/","title":"TI SensorTag Driver","text":"

    Eclipse Kura provides a specific driver that can be used to interact with Texas Instruments SensorTag devices. It can be used in the Wires framework, the Asset model or directly using the Driver itself.

    Warning

    The SensorTag driver can be used only with TI SensorTags with firmware version >1.20. If your device has an older firmware, please update it.

    "},{"location":"connect-field-devices/sensortag-driver/#features","title":"Features","text":"

    The SensorTag Driver can be used to get the values from all the sensor installed on the tag (both in polling mode and notification):

    • ambient and target temperature
    • humidity
    • pressure
    • three-axis acceleration
    • three-axis magnetic field
    • three-axis orientation
    • light
    • push buttons

    Moreover, the following resources can be written by the driver: - read and green leds - buzzer

    When a notification is enabled for a specific channel (sensor), the notification period can be set.

    "},{"location":"connect-field-devices/sensortag-driver/#installation","title":"Installation","text":"

    As the other Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here and here. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/sensortag-driver/#instance-creation","title":"Instance creation","text":"

    A new TiSensorTag Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ble.sensortag factory must be selected and a unique name must be provided for the new instance. Once instantiated, the SensorTag Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

    "},{"location":"connect-field-devices/sensortag-driver/#channel-configuration","title":"Channel configuration","text":"

    The SensorTag Driver channel can be configured with the following parameters:

    • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).
    • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type. Conversely, in write operations the Driver will accept value of this kind.
    • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
    • sensortag.address: the address of the specific SensorTag (i.e. aa:bb:cc:dd:ee:ff)
    • sensor.name: the name of the sensor. It can be selected from a drop-down list
    • notification.period: the period in milliseconds used to receive notification for a specific sensor. The value will be ignored it the listen option is not checked.
    "},{"location":"connect-field-devices/xdk-driver/","title":"Xdk Driver","text":"

    Eclipse Kura provides a specific driver that can be used to interact with Bosch Xdk110 devices. The driver is available only for gateways that support the new Bluetooth LE APIs. It can be used in to the Wires framework, the Asset model or directly using the Driver itself.

    Info

    The Xdk driver can only be used with Xdk110 with VirtualXdkDemo installed and with firmware version > 3.5.0. If your device has an older firmware, please update it.

    "},{"location":"connect-field-devices/xdk-driver/#features","title":"Features","text":"

    The Xdk Driver can be used to get the values from all the sensor provider by the xdk110 (both in polling mode and notification):

    • three-axis acceleration (or four-axis quaternion rappresentation)
    • three-axis gyroscope (or four-axis quaternion rappresentation)
    • light
    • noise
    • pressure
    • ambient temperature
    • humidity
    • sd-card detect status
    • push buttons
    • three-axis magnetic field
    • magnetometer resistence
    • led status
    • rms voltage of LEM sensor
    Resource Unit Description ACCELERATION_X, ACCELERATION_Y, ACCELERATION_Z G The proper acceleration for each axis* GYROSCOPE_X, GYROSCOPE_Y, GYROSCOPE_Z rad/S The angular acceleration for each axis* LIGHT lux The light value NOISE DpSpl The acustic pressure value PRESSURE Pa The pressure value TEMPERATURE C The temperature value HUMIDITY %rH The relative humidity SD_CARD_DETECTION_STATUS boolean SD-Card detect status PUSH_BUTTONS bit Button status, encoded as bit field: Bit 0, Button 1; Bit 1, Button 2 MAGNETOMETER_X, MAGNETOMETER_Y, MAGNETOMETERE_Z uT The magnetometer value for each axis MAGNETOMETER_RESISTENCE ohm The magnetometer resistence value LED_STATUS bit Led status, encoded as a bit field: Bit 0: yellow Led; Bit 1: orange Led; Bit 2: red Led VOLTAGE_LEM mV RMS voltage of LEM sensor

    *If the Quaternion rappresentation is selected, then:

    Resource Unit Description QUATERNION_M, QUATERNION_X, QUATERNION_Y, QUATERNION_Z number The rotation-quaternion for each axis

    When a notification is enabled for a specific channel (sensor), the notification period can't be set. Use the Timer in Wire Graph to set polling.

    "},{"location":"connect-field-devices/xdk-driver/#documentation","title":"Documentation","text":"

    All the information regarding the Xdk110 is available in the Xdk Bosch Connectivity here website. The XDK-Workbench is the tool that can be used to develop software for the Xdk110. It can be downloaded from here. XDK-Workbench is required to install VirtualXdkDemo.

    Warning

    We found connection problems with Xdk, probably due to issues with XDK-Workbench version 3.6.0. We recommend installing version 3.5.0.

    The Virtual XDK application user guide contains all the information regarding the XDK110 UUIDs and data formats here.

    Info

    To switch between quaternion and sensor representation, the XDK110 needs to be instructed using the 55b741d5-7ada-11e4-82f8-0800200c9a66 UUID. Set it to 0x01 to enable Quaternions.

    If quaternion representation is enabled, the data format is as follows:

    Bytes Data Type 0,1,2 & 3 Rotation Quaternion M float 4,5,6 & 7 Rotation Quaternion X float 8,9,10 & 11 Rotation Quaternion Y float 12,13,14 & 15 Rotation Quaternion Z float"},{"location":"connect-field-devices/xdk-driver/#installation","title":"Installation","text":"

    As the others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

    "},{"location":"connect-field-devices/xdk-driver/#instance-creation","title":"Instance creation","text":"

    A new Xdk Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ble.xdk factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Xdk Driver has to be configured setting the Bluetooth interface name (i.e. hci0). Other options available are related to the quaternion/sensor representation and the sensor sampling rate (in Hz).

    "},{"location":"connect-field-devices/xdk-driver/#channel-configuration","title":"Channel configuration","text":"

    The Xdk Driver channel can be configured with the following parameters:

    • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
    • name: the channel name.
    • type: the channel type, (READ, WRITE, or READ_WRITE).

      Warning

      The Xdk driver can only be used with READ.

    • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.

    • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
    • xdk.address: the address of the specific xdk (i.e. aa:bb:cc:dd:ee:ff)
    • sensor.name: the name of the sensor. It can be selected from a drop-down list.
    "},{"location":"core-services/active-mq-artemis-broker/","title":"ActiveMQ Artemis MQTT Broker","text":"

    Apart from using the simple ActiveMQ-7 MQTT instance available in the Simple Artemis MQTT Broker Service, this service allows to configure, in a more detailed way, the characteristics of the ActiveMQ-7 broker instance running in Kura.

    This service exposes the following configuration parameters:

    • Enabled - (Required) - Enables the broker instance
    • Broker XML - Broker XML configuration. An empty broker configuration will disable the broker.
    • Required protocols - A comma seperated list of all required protocol factories (e.g. AMQP or MQTT)
    • User configuration - (Required) - User configuration in the format: user=password|role1,role2,...
    • Default user name - The name of the default user

    Please refer to the official documentation for more details on how to configure the ActiveMQ broker service.

    "},{"location":"core-services/active-mq-artemis-broker/#service-usage-example","title":"Service Usage Example","text":"

    Setting the Broker XML field as follows:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration xmlns=\"urn:activemq\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"\nurn:activemq https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-server/src/main/resources/schema/artemis-server.xsd\nurn:activemq:core https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-server/src/main/resources/schema/artemis-configuration.xsd\nurn:activemq:jms https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-jms-server/src/main/resources/schema/artemis-jms.xsd\n\">\n\n    <core xmlns=\"urn:activemq:core\">\n        <persistence-enabled>false</persistence-enabled>\n\n        <acceptors>\n            <acceptor name=\"netty-acceptor\">tcp://localhost:61616</acceptor>\n            <acceptor name=\"amqp-acceptor\">tcp://localhost:5672?protocols=AMQP</acceptor>\n            <acceptor name=\"mqtt-acceptor\">tcp://localhost:1883?protocols=MQTT</acceptor>\n        </acceptors>\n\n        <resolve-protocols>false</resolve-protocols>\n\n        <security-settings>\n        <!-- WARNING: this is only for testing and completely insecure -->\n            <security-setting match=\"#\">\n                <permission type=\"createDurableQueue\" roles=\"guest\"/>\n                <permission type=\"deleteDurableQueue\" roles=\"guest\"/>\n                <permission type=\"createNonDurableQueue\" roles=\"guest\"/>\n                <permission type=\"deleteNonDurableQueue\" roles=\"guest\"/>\n                <permission type=\"consume\" roles=\"guest\"/>\n                <permission type=\"send\" roles=\"guest\"/>\n            </security-setting>\n        </security-settings>\n\n    </core>\n\n    <jms xmlns=\"urn:activemq:jms\">\n    <topic name=\"TEST.T.1\" />\n    </jms>\n\n</configuration>\n

    the User configuration to:

    guest=test12|guest\n

    while setting the Default user name to:

    guest\n

    will determine that the TCP ports 1883, 5672 and 61616 are now open (you can verify that via netstat -antup).

    Configuring the MqttDataTransport in System -> Cloud Services -> MqttDataTransport to use:

    • broker-url - mqtt://localhost:1883
    • username - guest
    • password - test12

    Clicking on the Connect button will result in a successful connection of Kura cloud service to the ActiveMQ-7 MQTT broker.

    Note

    The XML configuration above only allows connections originating from the gateway itself. In order to allow external connection the bind URLs specified using the acceptor tag must be modified by specifying an external accessible address instead of localhost. If the bind address is set to 0.0.0.0, the broker will listen on all available addresses.

    "},{"location":"core-services/clock-service/","title":"Clock Service","text":"

    The ClockService handles the date and time management of the system. If enabled, it tries to update the system date and time using a Network Time Protocol (NTP) server. NTP can use NTS as authentication mechanism through chrony.

    "},{"location":"core-services/clock-service/#service-configuration","title":"Service Configuration","text":"

    To manage the system date and time, select the ClockService option located in the Services area as shown in the screen capture below.

    The ClockService provides the following configuration parameters:

    • enabled: sets whether or not this service is enabled or disabled (Required field).

    • clock.set.hwclock: defines if the hardware clock of the gateway must be synced after the system time is set. If enabled, the service calls the Linux command hwclock --utc --systohc.

    • clock.provider: specifies one among Java NTP client (java-ntp), Linux chrony command (chrony-advanced), Linux ntpdate command (ntpd) (Required field). If chrony-advanced is used, Kura will not change system and/or hardware clock directly, delegating these operations to chrony.

    • clock.ntp.host: sets a valid NTP server host address.

    • clock.ntp.port: sets a valid NTP port number (not supported by ntpd, use the port 123 in that case).

    • clock.ntp.timeout: specifies the NTP timeout in milliseconds.

    • clock.ntp.max-retry: defines the number of retries when a sync fails (retry at every minute). Subsequently, the next retry occurs on the next refresh interval.

    • clock.ntp.retry.interval: defines the interval in seconds between each retry when a sync fails. If the clock.ntp.refresh-interval parameter is less than zero, there is no update. If the clock.ntp.refresh-interval parameter is equal to zero, there is only one try at startup (Required field).

    • clock.ntp.refresh-interval: defines the frequency (in seconds) at which the service tries to sync the clock. Note that at the start of Kura, when the ClockService is enabled, it tries to sync the clock every minute until it is successful. After a successful sync, this operation is performed at the frequency defined by this parameter. If the value is less than zero, there is no update. If the value is equal to zero, syncs only once at startup.

    • chrony.advanced.config: specifies the content of the chrony configuration file. If this field is left blank, the default system configuration will be used. All fields above will be ignored. To obtain the hardware clock synchronization the directive rtcsync could be used. The rtcsync directive provides the hardware clock synchronization made by the linux kernel every 11 minutes. For further information reference the chrony website.

    Two example configuration are shown below.

    "},{"location":"core-services/clock-service/#nts-secure-configuration-example","title":"NTS Secure configuration example","text":"
    server time.cloudflare.com iburst nts\nserver nts.sth1.ntp.se iburst nts\nserver nts.sth2.ntp.se iburst nts\n\nsourcedir /etc/chrony/sources.d\n\ndriftfile /var/lib/chrony/chrony.drift\n\nlogdir /var/log/chrony\n\nmaxupdateskew 100.0\n\nrtcsync\n\nmakestep 1 -1\n\nleapsectz right/UTC\n

    Note

    If the system stays disconnected from the network for a long time or if the backup battery is not working properly or is depleted, there is the possibility of a synchronization failure due to the client inability to verify the server certificates.

    If this happens and no counter action has been taken in the chrony configuration file, the risk is that the gateway will be unable to synchronise again its date and therefore will not be able to connect to the cloud and/or be fully operational.

    A possible way to prevent this issue is to temporary disable the certificate verification using the directive nocerttimecheck. This directory will disable the security checks of the activation and expiration times of certificates for the specified number of clock updates and should be used with caution due to the important security implications.

    As reported by the official Chrony documentation, disabling the time checks has important security implications and should be used only as a last resort, preferably with a minimal number of trusted certificates. The default value is 0, which means the time checks are always enabled.

    An example of the directive is nocerttimecheck 1 This would disable the time checks until the clock is updated for the first time, assuming the first update corrects the clock and later checks can work with correct time.

    "},{"location":"core-services/clock-service/#simple-configuration-example","title":"Simple configuration example","text":"
    # Use public NTP servers from the pool.ntp.org project.\npool pool.ntp.org iburst\n\n# Record the rate at which the system clock gains/losses time.\ndriftfile /var/lib/chrony/drift\n\n# Allow the system clock to be stepped in the first three updates\n# if its offset is larger than 1 second.\nmakestep 1 -1\n\n# Enable kernel synchronization of the real-time clock (RTC).\nrtcsync\n
    "},{"location":"core-services/command-service/","title":"Command Service","text":"

    The Command Service provides methods for running system commands from the Kura web console or from Kapua. In the Kura web console, the service is available clicking on the Command tab under the Device section, while for the cloud platform please refer to the official documentation.

    To run a command simply fill the Execute field with the command and click the Execute button. The service also provides the ability for a script to execute using the File option of the Command tab in the Kura web console or the Kapua Cloud Console. This script must be compressed into a zip file with the eventual, associated resource files.

    Once the file is selected and Execute is clicked, the zip file is sent embedded in an MQTT message on the device. The Command Service in the device stores the file in /tmp, unzips it, and tries to execute a shell script if one is present in the file. Note that in this case, the Execute parameter cannot be empty; a simple command, such as ls -l /tmp, may be entered.

    Warning

    When decompressed, the script loses its executable attribute. To fix this problem, if you plan to execute a script, the command entered into the Execute line must trigger the execution: ** bash [name of the script] **.

    The configuration of the service is in the CommandService tab located in the Services area as shown in the screen capture below.

    The Command Service provides the following configuration parameters:

    • Command Enable: sets whether this service is enabled or disabled in the cloud platform (Required field).

    • Command Password Value: sets a password to protect this service.

    • Command Working Directory: specifies the working directory where the command execution is performed.

    • Command Timeout: sets the timeout (in seconds) for the command execution.

    • Command Environment: supplies a space-separated list of environment variables in the format key=value.

    • Privileged/Unprivileged Command Service Selection: sets the modality of the command service. When set to privileged, the commands are run using the (privileged) user that started Kura, tipically kurad or root. When set to unprivileged, a standard user will run the commands.

    When a command execution is requested in the cloud platform, it sends an MQTT control message to the device requesting that the command be executed. On the device, the Command Service opens a temporary shell in the command.working.directory, sets the command.environment variables (if any), and waits command.timeout seconds to get command response.

    "},{"location":"core-services/configuration-service/","title":"Configuration Service","text":"

    The Configuration Service is responsible to manage the framework configuration by creating and persisting the framework snapshot. Built on top of the OSGi Configuration Admin and Metatype services, it is also responsible to track and manage the creation and deletion of service instances as well as OSGi component factories.

    The ConfigurationService is accessible using the following REST APIs and cloud request handlers:

    • Configuration v1 REST APIs (deprecated) and CONF-V1 MQTT Request Handler (deprecated)
    • Configuration v2 REST APIs and CONF-V2 MQTT Request Handler
    "},{"location":"core-services/container-orchestration-image-auth/","title":"Eclipse Eclipse Kura Container Image Authenticity and Allowlist Enforcement feature","text":"

    Containerized applications are becoming increasingly popular in the software ecosystem as a way to deploy and distribute applications. As a result, ensuring the security of software supply chains has become a critical concern. Implementing best practices, such as signing and verifying images to mitigate man-in-the-middle (MITM) attacks and validating their authenticity and freshness, play a pivotal role in safeguarding the integrity of the software supply chain.

    To ensure the authenticity and integrity of the code within the container images, binding the image to a specific entity or organization via signature verification is crucial and increasingly common.

    The purpose of this document is to provide a high-level understanding of the Eclipse Kura container authenticity and allowlist enforcement feature introduced with Eclipse Kura version 5.5.0.

    "},{"location":"core-services/container-orchestration-image-auth/#high-level-flow","title":"High-level flow","text":"

    The Eclipse Kura container authenticity and allowlist enforcement feature is designed to address container authenticity concerns by providing a mechanism to perform the signature verification of container images and restricting which container images can be deployed on the Eclipse Kura instance.

    This is achieved by implementing the following flow:

    Description of the flow:

    • Monitoring: Eclipse Kura monitors the container engine events. When a \"container start\" event is triggered it intercepts the information regarding the image used to spin up the container and starts performing the container authenticity checks.
    • Allowlist: Firstly, it checks if the digest of the image used to run the container can be found in the Eclipse Kura allowlist. If so, the container is allowed to run. If not, Eclipse Kura proceeds with the image signature verification.
    • Signature verification: If no trust anchor (i.e. a public key or a X509 certificate depending on the signature mechanism) is available for the verification process to happen, the container is not allowed to run. If one is available, the image signature is verified and the allowlist is updated accordingly.

    This flow applies to both containers managed by Eclipse Kura itself and containers ran directly by the user via CLI, allowing for a fine-grained control over the container images that can be deployed on the Eclipse Kura instance.

    • Unmanaged containers: Eclipse Kura allows the user to define a list of container images that can be deployed on the platform. This is done by adding the image digests to the allowlist. When a container is started, Eclipse Kura checks if the image digest is in the allowlist and, if so, allows the container to run. This is meant to be used by users who, for any reason, need to run a container outside the Eclipse Kura framework but still want the safety guarantees provided by pinning the images to a specific digest.
    • Managed containers: For containers ran by the framework, Eclipse Kura also allows the user to provide a trust anchor to be used for the signature verification process. This allows the user to use a mutable tag when specifying the container image, without giving up the required authenticity checks. Once the image is verified, its digest is stored within the Eclipse Kura allowlist, permitting it to be ran both from Eclipse Kura and the CLI without the need for an Internet connection. When a new image is published, the user can simply trigger the pull. The verification process will update the digest automatically.

    Note that the user can still directly provide an image digest when running an Eclipse Kura-managed container. This will prevent the Eclipse Kura automatic signature verification process to run. This feature is meant to be used when:

    • the user already ensured the image authenticity and does not want to go through the verification process again
    • the image was not signed but the user still wants the enforcement trust guarantees provided by Eclipse Kura
    "},{"location":"core-services/container-orchestration-image-auth/#unmanaged-containers","title":"Unmanaged containers","text":"

    Regarding the containers not managed by Eclipse Kura, the Container Orchestration service provides a security feature which enforces the containers allowed to run on the system. This feature can be enabled or disabled through the Allowlist Enforcement Enabled option.

    This mechanism leverages the concept of digest, which is a unique and immutable identifier for a container image: it is therefore an excellent mean to identify the image from which a container was created.

    Given the above, Eclipse Kura allows the user to provide a list of container image digests in the Container Image Allowlist field, in the form of newline-separated strings: when a container is started on the host system (whether it is launched by Eclipse Kura or by a terminal CLI), Eclipse Kura retrieves the image from which the container was created, extracts the digest and verifies that it is present in the allowlist provided during service configuration.

    If the image digest is present and matches the container's one, it is then allowed to run without interference. Otherwise, it is immediately stopped and deleted from the host system.

    A similar behaviour is performed at Eclipse Kura startup or when the enforcement is enabled at runtime: if there are already containers on the device (whether they are started or stopped) and the enforcement is activated, Eclipse Kura extracts the digests from the containers and compares them with those in the allowlist. Containers that have been created from images whose digests are in the allowlist will be left running, otherwise they will be stopped and deleted from the descriptors list.

    The verification is performed by intersecting the list of digests extracted by the containers and the one provided in the configuration: in this way, an empty intersection will result in a failing verification, while a non-empty one means that the image digests is equal to one of the allowlist entries.

    "},{"location":"core-services/container-orchestration-image-auth/#example-scenarios","title":"Example scenarios","text":"

    A user wants to leverage the container enforcement in order to let only docker containers started from an image named foo_image to be run on the device. To do this, they should enable the Container Enforcement by setting the Allowlist Enforcement Enabled to true, and fill the Container Image Allowlist field with the digest of the foo_image docker image (i.e.sha256:0000000000000000000000000000000000000000000000000000000000000000 in the example below).

    "},{"location":"core-services/container-orchestration-image-auth/#startup-of-the-enforcement-feature","title":"Startup of the Enforcement Feature","text":"

    Let's suppose that on the device there are already two containers running, one created from the foo_image an one from an image named unwanted_image with digest sha256:9999999999999999999999999999999999999999999999999999999999999999. Once the enforcement starts with the configuration previously described, it extracts the digests of both containers and checks if they're included in the provided allowlist: in this case, the container originating from the foo_image will be allowed to run, while the one created from the unwanted_image will be stopped and deleted, because its digest is not included in the allowlist.

    The same happens also for already stopped containers: if the digests is verified, they'll be left in the descriptors list in the Stopped state, otherwise they'll be deleted.

    "},{"location":"core-services/container-orchestration-image-auth/#running-container-after-enforcement-feature-startup","title":"Running Container After Enforcement Feature Startup","text":"

    After the starting phase just described, Eclipse Kura will continuously monitor the activity of the docker engine. Whenever a container is started on the device, it will check the image digest from which the container was created, comparing it to the ones inside the allowlist: if the container is created from the foo_image docker image, it will be allowed to start, otherwise it will be stopped and deleted. If the user wants to add more than one allowed image, for example one named wanted_image with digest sha256:1111111111111111111111111111111111111111111111111111111111111111), it just needs to add it to the newline-separated list.

    "},{"location":"core-services/container-orchestration-image-auth/#managed-containers","title":"Managed containers","text":"

    As explained in the previous section, Managed Containers (i.e. org.eclipse.kura.container.provider.ContainerInstances) support Container Signature validation. To do so a new API has been introduced: the ContainerSignatureValidationService.

    Currently, there are three main mechanism for container signature verification:

    • Docker Content Trust (DCT) a.k.a. Notary V1
    • Notation a.k.a. Notary V2
    • Sigstore's Cosign

    This newly introduced service allows Eclipse Kura to have multiple different Services implementing this interface and, thus, support all major signature mechanisms. A reference implementation for the ContainerSignatureValidationService is the DummyContainerSignatureValidationService available in Kura examples folder for anyone wanting to implement an alternative ContainerSignatureValidationService.

    When a ContainerSignatureValidationService is installed on Eclipse Kura, it gets automatically registered among the available validators and will be used to perform the signature validation. Under the hood, the ContainerInstance has a list of the available ContainerSignatureValidationService providers that, upon receiving a configuration update, it interrogates to check whether the requested container image is authentic using the provided Trust Anchor.

    • If even only one service reports the signature as valid, the image signature is considered valid.
    • If none of the available services reports the signature as valid, the image signature is considered not valid.

    This means that Eclipse Kura doesn't need to prompt the user for selecting the correct ContainerSignatureValidationService when performing the check.

    Once the image signature is validated, the image digest is stored in the Eclipse Kura snapshot and added to the ContainerOrchestratorService allowlist (see section above). A container whose image digest is available in the allowlist won't be validated again, therefore after the initial check, the internet connection is no longer required for it to work.

    "},{"location":"core-services/container-orchestration-image-auth/#example-scenarios_1","title":"Example Scenarios","text":""},{"location":"core-services/container-orchestration-image-auth/#container-instance-digest","title":"Container Instance Digest","text":"

    Let's suppose to have a device on which Eclipse Eclipse Kura is running with the Container Orchestration Service enabled, its Enforcement feature disabled and a Container Instance named test-container up and running on the system.

    A user wants to activate the Enforcement feature, so it enables the Allowlist Enforcement Enabled option in the Container Orchestration Service, but leaves the Container Image Allowlist option blank because it wants that no containers are started outside the framework.

    As the feature starts, it checks all the container running on the device, including the test-container, which will be stopped and deleted: this happens because the container digest is not included in the Container Orchestration Service Allowlist and the Container Instance is not providing any information through its Container Image Enforcement Digest option.

    In order to allow the Container Instance, the user should provide the correct digest in the instance settings. Let's suppose that the digest of the image from which the container is created is sha256:0000000000000000000000000000000000000000000000000000000000000000 and that the user fills the corresponding option with it: once the Container Instance is updated, the provided digest is included in the Enforcement feature allowlist, so the container will be allowed to run without interference. This case can be summarised as:

    Once the instance digest is added to the enforcement feature, it can be used also to authorise container run by the Command Line Interface, or other instances on Eclipse Eclipse Eclipse Kura without providing the digest option. But what happen if the Container Instance is disabled? The aim of the Container Instance Enforcement Digest is to be used as an authorization method of the instance itself: this means that its digest is added to the enforcement feature allowlist only if the instance is enabled.

    As it can be seen from the image, the merged Enforcement Allowlist box doesn't contain the digest associated to the Container Instance, because, being it disabled, the corresponding digest is ignored. If the enforcement is enabled and a container with digest DIGEST Z is started, it will then be stopped and deleted, because its digest won't be included in the allowlist.

    Finally, everytime a Container Image Enforcement Digest option is modified, or the ContainerInstance is disabled or deleted, the enforcement feature will perform a check on all the already running containers. This is done because, if the digest that was previously provided has changed after an instance update, or removed due to disabling or deleting the instance, those containers that were previously authorised by this digest are no longer allowed to run. So they must be stopped and deleted.

    Warning

    The digests provided through Container Instances allow running also container started by the CommandLineInterface or by other Container Instances in the framework, even without providing the digest.

    It has to be considered that, if the ContainerInstance is disabled (or its digest option changes), the enforcement feature will stop and delete the containers that are no longer matching the provided digest.

    The user needs to be careful, then, to rely only on the digests set in the ContainerInstances options. If the user will need to run containers from the CLI, it is preferable to use the allowlist of the Container Orchestration Service. For the Container Instances, is a good practise to provide a digest.

    "},{"location":"core-services/container-orchestration-image-auth/#container-signature-verification","title":"Container Signature Verification","text":"

    If the Container Image Enforcement Digest option is not provided, Eclipse Eclipse Kura will proceed with the Signature Verification: this process tries to extract the digest of the image from which the container was started.

    Let's then suppose that we are back at the beginning of the previous example, with a blank Container Orchestration Service allowlist and a not given Container Instance Digest, but this time a Trust Anchor option is given: in this case Eclipse Eclipse Kura will start the Signature Verification Process, in order to extract the digest.

    If, for whatever reason, the Signature Verification fails, no digests will be added to the allowlist: if the enforcement feature is enabled, the started container will be then stopped and deleted by the Enforcement Monitor, because no digests are included in the final Enforcement Allowlist.

    While, if the procedure completes correctly, the extracted digest will be added to the Enforcement Allowlist and written in the snapshot: in this way the container will pass the enforcement feature check, and the obtained digest will be stored as part of the Container Instance's configuration just started.

    If the user wants to check again the digest through the Signature Verification Service, it just needs to erase the Container Image Enforcement Digest option from the configuration of the Container Instance: in this way the Signature feature will be triggered again and the digest recalculated.

    Warning

    Even in this situation, the digest could be used to authenticate container started from the CommandLineInterface: also in this case keep in mind that if the Container Instance is disabled, stopped or updated with different digest, those CLI-based container could be stopped and deleted.

    "},{"location":"core-services/container-orchestration-provider-apis/","title":"Container Orchestration Provider APIs","text":""},{"location":"core-services/container-orchestration-provider-apis/#java-api","title":"Java API","text":""},{"location":"core-services/container-orchestration-provider-apis/#containerorchestrationservice","title":"ContainerOrchestrationService","text":"

    The ContainerOrchestrationService is used to directly communicate with the running container engine. It exposes methods for listing, creating, and stopping containers. This class utilizes an instantiated ContainerConfiguration object as a parameter for container creation.

    "},{"location":"core-services/container-orchestration-provider-apis/#containerconfiguration","title":"ContainerConfiguration","text":"

    The ContainerConfiguration class, allows you to define a container to create. Using the embedded builder class, one can define many container-related parameters such as name, image, ports and volume mounts.

    "},{"location":"core-services/container-orchestration-provider-apis/#containerinstancedescriptor","title":"ContainerInstanceDescriptor","text":"

    The ContainerInstanceDescriptor class is used to describe a container that has already been created. This class contains runtime information such as the ID of the container.

    "},{"location":"core-services/container-orchestration-provider-apis/#containerstate","title":"ContainerState","text":"

    The ContainerState is a class that exposes an enum of container states tracked by the framework.

    "},{"location":"core-services/container-orchestration-provider-apis/#passwordregistrycredentials","title":"PasswordRegistryCredentials","text":"

    The PasswordRegistryCredentials class stores password credentials when provisioning a container to pull from an alternative password-protected registry.

    "},{"location":"core-services/container-orchestration-provider-apis/#passwordregistrycredentials_1","title":"PasswordRegistryCredentials","text":"

    The PasswordRegistryCredentials class stores password credentials when provisioning a container to pull from an alternative password-protected registry.

    Note

    The Container Orchestration Provider exports an MQTT-Namespace API. This API can be used to manage containers via MQTT requests from external applications. Please visit the Remote Gateway Inventory via MQTT documentation for more information.

    "},{"location":"core-services/container-orchestration-provider-authenticated-registries/","title":"Container Orchestration Provider Authenticated Registries","text":"

    The Container Orchestrator provider allows the user to pull images from private and password-protected registries. The following document will provide examples of how to connect to some popular registries.

    Note

    These guides make the following two assumptions.

    1. That you have already configured the Container Orchestrator and have a container instance already created. Please see the usage doc, to learn the basics of the orchestrator.
    2. That the image you are trying to pull supports the architecture of the gateway.
    "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#private-docker-hub-registries","title":"Private Docker-Hub Registries","text":""},{"location":"core-services/container-orchestration-provider-authenticated-registries/#preparation","title":"Preparation:","text":"
    • have a Docker Hub account (its credentials), and a private image ready to pull.
    "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#procedure","title":"Procedure:","text":"
    1. Populate the image name field. The username containing the private image must be placed before the image name separated by a forward slash. This is demonstrated below:
    2. **Image Name: ** <Docker-Hub username>/<image name> for exampleeclipse/kura.

    3. Populate the credential fields:

    4. Authentication Registry URL: This field should be left blank.
    5. Authentication Username: Your Docker Hub username.
    6. Password: Your Docker Hub password.

    "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#amazon-web-services-elastic-container-registries-aws-ecr","title":"Amazon Web Services - Elastic Container Registries (AWS-ECR)","text":""},{"location":"core-services/container-orchestration-provider-authenticated-registries/#preparation_1","title":"Preparation:","text":"
    • Have access to an Amazon ECR instance.
    • Have the AWS-CLI tool installed and appropriately configured on your computer.
    • Have access to your AWS ECR web console.
    "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#procedure_1","title":"Procedure:","text":"
    1. Sign in to your amazon web console, navigate to ECR and identify which container you will like to pull onto the gateway. Copy the URI of the container. This URI will reveal the information required for the following steps. Here is how to decode the URI <identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/<image name>:<image tag>.

    2. Generating an AWS-ECR access password. Open a terminal window on the machine with aws-cli installed and enter the following command aws ecr get-login-password --region <ecr-region>. Your ECR region can be found by inspecting the container URI string copied in the previous step. This command will return a long string which will be used as the repo password in the gateway.

    3. Populating information on the gateway.

    4. **Image Name: ** enter the full URI without the tag.<identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/<image name>
    5. Image Tag: enter only the image tag found at the end of the URI <image tag>
    6. Authentication Registry URL: Paste only the part of the URI before the image name <identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/
    7. Authentication Username: will be AWS
    8. Password: will be the string created in step two.

    A fully configured container set to pull AWS will look like the following.

    "},{"location":"core-services/container-orchestration-provider-usage/","title":"Container Orchestration Provider Usage","text":""},{"location":"core-services/container-orchestration-provider-usage/#before-starting","title":"Before Starting","text":"

    For this bundle to function appropriately, the gateway must have a supported container engine installed and running. Currently, the only officially supported engine is Docker.

    "},{"location":"core-services/container-orchestration-provider-usage/#starting-the-service","title":"Starting the Service","text":"

    To use this service select the ContainerOrchestrationService option located in the Services area. The ContainerOrchestrationService provides the following parameters:

    • Enabled--activates the service when set to true
    • Container Engine Host URL--provides a string that tells the service where to find the container engine (best left to the default value).
    • Allowlist Enforcement Enabled--activates the container enforcement of the service, which let only the allowed containers to run
    • Container Image Allowlist Content--the comma-separated list of conainer's digests allowed to be run

    "},{"location":"core-services/container-orchestration-provider-usage/#creating-your-first-container","title":"Creating your first container.","text":"

    To create a container, select the + icon (Create a new component) under services. A popup dialogue box will appear. In the field Factory select org.eclipse.kura.container.provider.ContainerInstance from the drop-down. Then, use the Name field to specify a name for the container.

    Note

    The name specified in the 'Name' field will also be the name of the container when it is spun up by the orchestrator.

    After pressing submit, a new component will be added under the services tab, with the name that was selected in the dialogue. Select this component to finish configuring the container.

    "},{"location":"core-services/container-orchestration-provider-usage/#configuring-the-container","title":"Configuring the container","text":"

    To begin configuring the container, look under Services and select the item which has the name set in the previous step. Containers may be configured using the following fields:

    • Enabled - When true, the service will create the defined container. When false the API will not create the container or will destroy the container if already running.

    • Image Name - Describes the image that will be used to create the container. Remember to ensure that the selected image supports the architecture of the host machine, or else the container will not be able to start.

    • Image Tag - Describes the version of the container image that will be used to create the container.

    • Trust Anchor - Trust anchor used to verify the container image Signature Verification

    • Verify in transparency log - Sets the transparency log verification, to be used when a container image signature has been uploaded to the transparency log.

    • Container Image Enforcement Digest - A string representing the digest for the image that will be allowed to run by this container instance (eg: sha256:0000000000000000000000000000000000000000000000000000000000000000). It is used in the Container Enforcement service provided by the Container Orchestration Service.

    • Authentication Registry URL - URL for an alternative registry to pull images from. (If the field is left blank, credentials will be applied to Docker-Hub). Please see the Authenticated Registries document for more information about connecting to different popular registries.

    • Authentication Username - Describes the username to access the container registry entered above.

    • Password - Describes the password to access the alternative container registry.

    • Image Download Retries - Describes the number of retries the framework will attempt to pull the image before giving up.

    • Image Download Retry Interval - Describes the amount of time the framework will wait before attempting to pull the image again.

    • Image Download Timeout - Describes the amount of time the framework will let the image download before timeout.

    • Internal Ports - This field accepts a comma-separated list of ports that will be internally exposed on the spun-up container. In this field, you can also specify which protocol to run at the port by appending a port with a colon and typing in the name of the network protocol. Example: 80, 443:tcp, 8080:udp.

    • External Ports - This field accepts a comma-separated list of ports that will be externally exposed on the host machine.

    • Privileged Mode - This flag if enabled will give the container root capabilities to all devices on the host system. Please be aware that setting this flag can be dangerous, and must only be used in exceptional situations.

    • Environment Variables (optional) - This field accepts a comma-separated list of environment variables, which will be set inside the container when spun up.

    • Entrypoint Override (optional) - This field accepts a comma-separated list which is used to override the command used to start a container. Example: ./test.sh,-v,-d,--human-readable.

    • Memory (optional) - This field allows the configuration of the maximum amount of memory the container can use in bytes. The value is a positive integer, optionally followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes. The minimum and default values depends by the native container orchestrator. If left empty, the memory assigned to the container will be set to a default value.

    • CPUs (optional) - This value specifies how many CPUs a container can use. Decimal values are allowed, so if set to 1.5, the container will use at most one and a half cpu resource.

    • GPUs (optional) - This field configures how many Nvidia GPUs a container can use. Allowed values are all or an integer number. If there's no Nvidia GPU installed, leave it empty. The Nvidia Container Toolkit must be installed on the system to correctly configure the service, otherwise the container will not start. If the Nvidia Container Runtime is used, leave the field empty.

    • Volume Mount (optional) - This field accepts a comma-separated list of system-to-container file mounts. This allows for the container to access files on the host machine.

    • Peripheral Device (optional) - This field accepts a comma-separated list of device paths. This parameter allows devices to be passed through from the host to the container.

    • Runtime (optional): Specifies the fully qualified name of an alternate OCI-compatible runtime, which is used to run commands specified by the 'run' instruction. Example: nvidia corresponds to --runtime=nvidia. Note: when using the Nvidia Container Runtime, leave the GPUs field empty. The GPUs available on the system will be accessible from the container by default.

    • Networking Mode (optional) - Use this field to specify what networking mode the container will use. Possible Drivers include: bridge, none, container:{container id}, host. Please note that this field is case-sensitive. This field can also be used to connect to any of the networks listed by the cli command docker network ls.

    • Logger Type - This field provides a drop-down selection of supported container logging drivers.

    • Logger Parameters (optional) - This field accepts a comma-separated list of logging parameters. More information can be found in the container-engine logger documentation, for instance here.

    • Restart Container On Failure - A boolean that tells the container engine to automatically restart the container when it has failed or shut down.

    After specifying container parameters, ensure to set Enabled to true and press Apply. The container engine will then pull the respective image, spin up and start the container. If the gateway or the framework is power cycled, and the container and Container Orchestration Service are set to enabled, the framework will automatically start the container again upon startup.

    "},{"location":"core-services/container-orchestration-provider-usage/#stopping-the-container","title":"Stopping the container","text":"

    Warning

    Stopping a container will delete it in an irreversible way. Please be sure to only use stateless containers and/or save their data in external volumes.

    To stop the container without deleting the component, set the Enabled field to false, and then press Apply. This will delete the running container, but leave this component available for running the container again in the future. If you want to completely remove the container and component, press the Delete button to the top right of the screen, and press Yes on the confirmation dialogue.

    "},{"location":"core-services/container-orchestration-provider-usage/#container-management-dashboard","title":"Container Management Dashboard","text":"

    The Container Orchestration service also provides the user with an intuitive container dashboard. This dashboard shows all containers running on a gateway, including containers created with the framework and those created manually through the command-line interface. To utilize this dashboard the org.eclipse.container.orchestration.provider (ContainerOrchestrationService) must be enabled, and the dashboard can be opened by navigating to Device > Containers.

    "},{"location":"core-services/container-orchestration-provider/","title":"Container Orchestration Provider","text":"

    The Container Orchestration Provider allows Kura to manage Docker. With this tool, you can arbitrarily pull and deploy containerized software packages and run them on your gateway. This Service allows the user to create, configure, start, and stop containers all from the browser. The bundle will also restart containers if the gateway is restarted.

    The feature is composed of two bundles, one that exposes APIs for container management and one that implements those APIs. This API is exposed so that you can leverage it to implement containerization in your own Kura plugins.

    "},{"location":"core-services/deployment-service/","title":"Deployment Service","text":"

    The Deployment Service allows to download files to the gateway and to perform actions on them. In the configuration tab it is possible to specify which is the directory that has to be used to store the downloaded files and the list of actions declared as deployment hooks that will be invoked when a corresponding metric is received with the download request.

    The configuration requires to specify two parameters:

    • downloads.directory - The directory to be used to store the downloaded files;
    • deployment.hook.associations - The list of DeploymentHook associations in the form <request_type>=<hook_pid>, where <hook_pid> is the Kura Service Pid of a DeploymentHook instance and <request_type> is the value of the request.type metric received with the request.
    "},{"location":"core-services/device-configuration-changes/","title":"Device Configuration Changes","text":"

    Kura can detect changes to the components and publish them using a selected Cloud Publisher. There are two main components that enable this:

    • org.eclipse.kura.configuration.change.manager, and
    • org.eclipse.kura.event.publisher

    The org.eclipse.kura.configuration.change.manager is responsible for detecting changes to any of the configurations currently running on the system and to publish a notification to a user-defined cloud publisher. By default, the org.eclipse.kura.event.publisher is used.

    "},{"location":"core-services/device-configuration-changes/#configuration-change-manager","title":"Configuration Change Manager","text":"

    The configuration change manager allows to collection of the PIDs of the components that have changed in the system. The configurable properties of this component are:

    • Enable: whether to enable or not this component.
    • CloudPublisher Target Filter: allows to specify the cloud publisher to use for sending the produced messages.
    • Notification send delay (sec): allows to stack the changed PIDs for a given time before sending. In other words, it allows to group together sequences of PIDs that are changed in that time frame. This is to prevent message flooding at reboot or rollback.

    The collected PIDs are sent to the Cloud Publisher as a KuraMessage with a payload body in JSON format. The timestamp of the KuraMessage is set to the last detected configuration change event. An example of message body is:

    [\n    {\n        \u201cpid\u201d: \u201corg.eclipse.kura.clock.ClockService\u201d\n    },\n    {\n        \u201cpid\u201d: \u201corg.eclipse.kura.log.filesystem.provider.FilesystemLogProvider\u201d\n    }\n]\n

    In the example above, a ClockService update triggered the delay timer, which was then reset by a configuration update on the FilesystemLogProvider. Afterward, no configuration updates reset the timer so the message containing the two PIDs was sent after expiration.

    "},{"location":"core-services/device-configuration-changes/#event-publisher","title":"Event Publisher","text":"

    By default, the org.eclipse.kura.event.publisher used by the configuration change manager does the actual publishing on a user-defined topic of the form:

    $EVT/#account_id/#client_id/CONF/V1/CHANGE

    This topic is adjusted for integration with EC, but the $EVT and the CONF/V1/CHANGE parts can be customized according to user needs by tweaking the Topic prefix and Topic properties.

    The #account_id and #client_id fields are substituted at runtime with the account name and client ID at the Data Transport Service layer of the associated Cloud Connection.

    The Qos, Retain, and Priority properties are the usual ones defined in the standard Cloud Publisher.

    "},{"location":"core-services/h2db-service/","title":"H2Db Service","text":"

    Kura integrates a Java SQL database named H2. The main features of this SQL Database are:

    • Very fast, open source, JDBC API
    • Embedded and server modes; in-memory databases
    • Browser-based Console application
    • Small footprint
    "},{"location":"core-services/h2db-service/#supported-features","title":"Supported Features","text":"

    Kura supports the following H2 database features:

    • Persistence modes: The H2 implementation currently supports in-memory and file-based database instances. See the Persistence Modes section for more details.

    • Multiple database instances: It is possible to create and configure multiple database instances from the Kura Administration UI, these instances can be selectively consumed by applications. A default database instance is created automatically.

    • TCP Server: The current implementation allows external processes to access the database instances managed by Kura using TCP. This enables the integration of external applications that can share data with Kura components using the database.

    • Web-based console: It is possible to start the H2 Web console directly from the Kura Administration UI. The console can be used to inspect the database contents and perform arbitrary queries for debug purposes.

    • Basic credential management: The current implementation allows to change the password of the admin DB user from the Kura Administration UI. This allows the access restriction to the existing database instances.

    By default, the DataService in Kura uses the H2 database to persist the messages.

    "},{"location":"core-services/h2db-service/#limitations","title":"Limitations","text":"
    • Private in-memory instances: Only named in-memory instances are supported (e.g. jdbc:h2:mem:<dbname>, where <dbname> is not the empty string), private instances represented by the jdbc:h2:mem: URL are currently not supported.

    • Remote connections: The current implementation only supports embedded database instances. Connecting to remote instances using the jdbc:h2:tcp:* and jdbc:h2:ssl:* connector URLs is not supported.

    Note

    The new DbWireRecordFilter and DbWireRecordStore Wire components have been added. These components provide the same functionalities offered by the old H2DbWireRecordFilter and H2DbWireRecordStore components, but they can be used for connectiong to a generic relational database (i.e. H2DB, MySQL or MariaDB). The legacy components will continue to be available in order to keep backward compatibility, but will be deprecated since Kura 5.2.0 and should not be used for new installations.

    "},{"location":"core-services/h2db-service/#usage","title":"Usage","text":""},{"location":"core-services/h2db-service/#creating-a-new-h2-database-instance","title":"Creating a new H2 database instance","text":"

    To create a new H2 database instance, use the following procedure:

    1. Open the Administrative UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
    2. Select org.eclipse.kura.core.db.H2DbService from the Factory drop-down list, enter an arbitrary name for the new instance and click Apply.
    3. An entry for the newly created instance should appear in the side menu under Services, click on it to review its configuration. It is not possible to create different DB instances that manage the same DB URL. When creating a new instance please make sure that the URL specified in the field db.connector.url is not managed by another instance.
    "},{"location":"core-services/h2db-service/#configuration-parameters","title":"Configuration Parameters","text":"

    The H2DbService provides the following configuration parameters:

    • Connector URL: JDBC connector URL of the database instance. Passing the USER and PASSWORD parameters in the connector URL is not supported, these paramters will be ignored if present. Please use the db.user and db.password fields to provide the credentials.

    Warning

    If the database is created in persisted mode, please make sure that the Linux user running Eclipse Kura has the permissions required to create the database file. If the permissions are not ok, Eclipse Kura may be able to create the database (by default it runs with the CAP_DAC_OVERRIDE capability) but it may not be able to perform the periodic defragmentation process, this may cause the database file size to grow expecially if the write rate is high.

    Executing the following commands as root can be useful to detect potential issues, replace database_parent_directory with the parent directory of the database file and kura_linux_user with the Linux user that is executing Eclipse Kura (e.g. kurad):

    export TARGET=\"$(readlink -f database_parent_directory)\"\nexport KURA_USER=\"kura_linux_user\"\nsudo -u \"${KURA_USER}\" sh -c \"touch '${TARGET}/.testfile' && rm '${TARGET}/.testfile'\"\n

    If command fails it may be necessary to change the database directory or adjust the permissions.

    • User: Specifies the user for the database connection. Furthermore

    • Password: Specifies the password. The default password is the empty string.

    • Checkpoint interval (seconds): H2DbService instances support running periodic checkpoints to ensure data consistency. This parameter specifies the interval in seconds between two successive checkpoints. This setting has no effect for in-memory database instances.

    • Defrag interval (minutes): H2DbService instances support running periodic defragmentation (compaction). This parameter specifies the interval in minutes between two successive checkpoints, set to zero to disable. This setting has no effect for in-memory database instances. Existing database connections will be closed during the defragmentation process and need to be reopened by the applications.

    • Connection pool max size: The H2DbService manages connections using a connection pool. This parameter defines the maximum number of connections for the pool

    "},{"location":"core-services/h2db-service/#selecting-a-database-instance-for-existing-components","title":"Selecting a database instance for existing components","text":"

    A database instance is identified by its Kura service PID. The PID for the default instance is org.eclipse.kura.db.H2DbService while the PID for instances created using the Web UI is the string entered in the Name field at step 2 of the previous section.

    The built-in components that use database functionalities allow to specify which instance to use in their configuration. These components are the DataService component of the cloud stack, the DbWireRecordFilter and DbWireRecordStore wire components. The configuration of each component contains a property that allows to specify the service PID of the desired instance.

    "},{"location":"core-services/h2db-service/#usage-through-wires","title":"Usage through Wires","text":"

    It is possible to store and extract Wire Records into/from a H2 database instance using the Wire Record Store and Wire Record Query wire components.

    When a Wire Record is received by a Wire Record Store attached to a H2 based database instance, the data will be stored in a table whose name is the current value of the Record Collection Name configuration parameter of the Wire Component.

    Each property contained in a Wire Record will be appended to a column with the same name as the property key. A new column will be created if it doesn't already exists.

    Note

    Storing wire record properties with the FLOAT data type using the Wire Record Store is not recommended since the type information will be lost. Values inserted as FLOAT using the Wire Record Store will be retrieved as DOUBLE using the Wire Record Query component.

    Warning

    It is not recommended to store Wire Records having properties with the same key and different value type. If the value type changes, the target column will be dropped and recreated with the type derived from the last received record. All existing data in the target column will be lost. The purpose of this is to allow changing the type of a column with a Wire Graph configuration update.

    "},{"location":"core-services/h2db-service/#enabling-the-tcp-server","title":"Enabling the TCP Server","text":"

    Danger

    This feature is intended to be used only for debugging/development purposes. The server created by H2 is not running on a secure protocol. Only enable the server for a limited time and make sure to properly secure the firewall ports on which it is running.

    The TCP server can be used by creating a H2DbServer instance:

    1. Open the Web UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
    2. Select org.eclipse.kura.core.db.H2DbServer from the Factory drop-down\u200b list, enter an arbitrary name for the new instance and click Apply.
    3. Clicking on the name of the new server instance on the left side of the Web UI\u200b. The configuration of the server component will appear.
    4. Set the db.server.type field to TCP.
    5. Review the server options under db.server.commandline, check the official documentation for more information about the available options.
    6. Set the db.server.enabled to true.

    The server, with the default configuration, will be listening on port 9123.

    Tip

    Make sure to review the firewall configuration in order to ensure that the server is reachable from an external process.

    "},{"location":"core-services/h2db-service/#enabling-the-web-console","title":"Enabling the Web Console","text":"

    Danger

    This feature is intended to be used only for debugging/development purposes. The server created by H2 is not running on a secure protocol. Only enable the server for a limited time and make sure to properly secure the firewall ports on which it is running.

    In order to enable the H2 Web console, proceed as follows:

    1. Create a new H2DbServer instance.
    2. Set the db.server.type field to WEB.
    3. Enter appropriate parameters for the Web server in the db.server.commandline field. An example of valid settings can be: -webPort 9123 -webAllowOthers -ifExists -webExternalNames <device-ip>.
    4. Set the db.server.enabled to true.

    The server is now listening on the specified port.

    Tip

    Make sure to review the firewall configuration in order to ensure that the server is reachable from an external process.

    Use a browser to access the console. Open the http://: URL, where is the IP address of the gateway and is the port specified at step 3.

    Enter the DB URL as specified in the Kura configuration in the JDBC URL field and the credentials. Click on Connect, you should be able to access the console.

    "},{"location":"core-services/h2db-service/#change-the-database-password","title":"Change the Database Password","text":"

    To change the database password the System Administrator needs to:

    1. Open the configuration of the desired database instance in the Web UI.
    2. Enter the new password in the db.password field.
    3. Click Apply.

    Warn

    If the H2DbServer instance fails to open a database, it will delete and recreate all database files. This behavior\u200b is aimed at preventing potential issues caused by incorrect credentials in the configuration snapshots. It is highly recommended to perform a backup of an existing database before trying to open it using a H2DbService instance and before changing the password.

    "},{"location":"core-services/h2db-service/#persistence-modes","title":"Persistence Modes","text":"

    The H2 database supports several persistence modes.

    "},{"location":"core-services/h2db-service/#in-memory","title":"In Memory","text":"

    An in-memory database instance can be created using the following URL structure: jdbc:h2:mem:, where is a non-empty string that represents the database name. This configuration is suggested for database instances that are frequently updated. Examples:

    • jdbc:h2:mem:kuradb
    • jdbc:h2:mem:mydb

    The default database instance is in-memory by default and uses the jdbc:h2:mem:kuradb URL.

    "},{"location":"core-services/h2db-service/#persistent","title":"Persistent","text":"

    A persistent database instance can be created using the jdbc:h2:file:, where is a non-empty string that represents the database path.

    If no URL parameters are supplied the database will enable the transaction log by default. The transaction log is used to restore the database to a consistent state after a crash or power failure. This provides good protection against data losses but causes a lot of writes to the storage device, reducing both performance and the lifetime of flash-based storage devices.

    Examples: - jdbc:h2:file:/opt/db/mydb

    Make sure to use absolute paths in the DB URL since H2 does not support DB paths relative to the working directory.

    "},{"location":"core-services/introduction/","title":"Introduction","text":"

    This section describes the administrative tools available using the Gateway Administration Console. This web interface provides the ability to configure all services and applications that are installed and running on the gateway.

    "},{"location":"core-services/network-status-rest-v1/","title":"Network Status Service V1 REST APIs and MQTT Request Handler","text":"

    The NET-STATUS-V1 cloud request handler and the corresponding REST APIs allow to retrieve the current status of the network interfaces available on the system.

    This cloud request handler and rest API is available only on the systems that provide a NetworkStatusService implementation, at the moment this corresponds to the devices that include the NetworkManager integration.

    Accessing the REST APIs requires to use an identity with the rest.network.status permission assigned.

    • Request definitions
      • GET/interfaceIds
      • GET/status
      • POST/status/byInterfaceId
    • JSON definitions
      • InterfaceIds
      • InterfaceStatusList
      • LoopbackInterfaceStatus
      • EthernetInterfaceStatus
      • WifiInterfaceStatus
      • ModemInterfaceStatus
      • NetworkInterfaceStatus
      • IPAddressString
      • HardwareAddress
      • NetworkInterfaceIpAddress
      • NetworkInterfaceIpAddressStatus
      • WifiChannel
      • WifiAccessPoint
      • ModemModePair
      • Sim
      • Bearer
      • ModemPorts
      • NetworkInterfaceType
      • NetworkInterfaceState
      • WifiCapability
      • WifiMode
      • WifiSecurity
      • ModemPortType
      • ModemCapability
      • ModemMode
      • ModemBand
      • SimType
      • ESimStatus
      • BearerIpType
      • ModemConnectionType
      • ModemConnectionStatus
      • AccessTechnology
      • RegistrationStatus
      • FailureReport
      • GenericFailureReport
    "},{"location":"core-services/network-status-rest-v1/#request-definitions","title":"Request definitions","text":""},{"location":"core-services/network-status-rest-v1/#getinterfaceids","title":"GET/interfaceIds","text":"
    • REST API path : /services/networkStatus/v1/interfaceIds
    • description : Returns the identifiers of the network interfaces detected in the system. For Ethernet and WiFi interfaces, the identifier is typically the interface name. For the modems, instead, it is the usb or pci path.
    • responses :
      • 200
        • description : The list of the identifiers of the network interfaces detected in the system.
        • response body :
          • InterfaceIds
      • 500
        • description : If an unexpected failure occurs while retrieving the interface list.
        • response body :
          • GenericFailureReport
    "},{"location":"core-services/network-status-rest-v1/#getstatus","title":"GET/status","text":"
    • REST API path : /services/networkStatus/v1/status
    • description : Returns the status for all interfaces currently available on the system. Failures in retrieving the status of specific interfaces will be reported using the failures field of the response.
    • responses :
      • 200
        • description : The status of the network interfaces in the system.
        • response body :
          • InterfaceStatusList
      • 500
        • description : If an unexpected failure occurs while retrieving the interface list.
        • response body :
          • GenericFailureReport
    "},{"location":"core-services/network-status-rest-v1/#poststatusbyinterfaceid","title":"POST/status/byInterfaceId","text":"
    • REST API path : /services/networkStatus/v1/status/byInterfaceId
    • description : Returns the status for the network interfaces whose id is specified in the request. Failures in retrieving the status of specific interfaces, or the fact that an interface with the requested id cannot be found will be reported using the failures field of the responses.
    • request body :
      • InterfaceIds
    • responses :
      • 200
        • description : The status of the network interfaces in the system.
        • response body :
          • InterfaceStatusList
      • 400
        • description : If the request object does not contain the interfaceIds field, it is null or if it contains empty or null interface ids.
        • response body :
          • GenericFailureReport
      • 500
        • description : If an unexpected failure occurs while retrieving the interface list.
        • response body :
          • GenericFailureReport
    "},{"location":"core-services/network-status-rest-v1/#json-definitions","title":"JSON definitions","text":""},{"location":"core-services/network-status-rest-v1/#interfaceids","title":"InterfaceIds","text":"

    An object containing a list of network interface identifiers

    Properties:

    • interfaceIds: array The list of network interface identifiers

      • array element type: string An interface identifier
    {\n  \"interfaceIds\": [\n    \"lo\"\n  ]\n}\n
    "},{"location":"core-services/network-status-rest-v1/#interfacestatuslist","title":"InterfaceStatusList","text":"

    An object reporting a list of network interface status. If a failure occurs retrieving the status for a specific interface, the reason will be reported in the failures list.

    Properties:

    • interfaces: array A list of network interface status

      • array element type: variant
        • Variants:
          • object
            • LoopbackInterfaceStatus
          • object
            • EthernetInterfaceStatus
          • object
            • WifiInterfaceStatus
          • object
            • ModemInterfaceStatus
    • failures: array

      • array element type: object
        • FailureReport

    {\n  \"failures\": [],\n  \"interfaces\": [\n    {\n      \"autoConnect\": true,\n      \"driver\": \"unknown\",\n      \"driverVersion\": \"\",\n      \"firmwareVersion\": \"\",\n      \"hardwareAddress\": \"00:00:00:00:00:00\",\n      \"id\": \"lo\",\n      \"interfaceIp4Addresses\": {\n        \"addresses\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"prefix\": 8\n          }\n        ],\n        \"dnsServerAddresses\": []\n      },\n      \"interfaceName\": \"lo\",\n      \"mtu\": 65536,\n      \"state\": \"UNMANAGED\",\n      \"type\": \"LOOPBACK\",\n      \"virtual\": true\n    }\n  ]\n}\n
    {\n  \"failures\": [\n    {\n      \"interfaceId\": \"foo\",\n      \"reason\": \"Not found.\"\n    }\n  ],\n  \"interfaces\": []\n}\n

    "},{"location":"core-services/network-status-rest-v1/#loopbackinterfacestatus","title":"LoopbackInterfaceStatus","text":"

    Object that contains specific properties to describe the status of an Loopback interface. It contains also all of the properties specified by NetworkInterfaceStatus.

    Properties:

    {\n  \"autoConnect\": true,\n  \"driver\": \"unknown\",\n  \"driverVersion\": \"\",\n  \"firmwareVersion\": \"\",\n  \"hardwareAddress\": \"00:00:00:00:00:00\",\n  \"id\": \"lo\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"127.0.0.1\",\n        \"prefix\": 8\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"lo\",\n  \"mtu\": 65536,\n  \"state\": \"UNMANAGED\",\n  \"type\": \"LOOPBACK\",\n  \"virtual\": true\n}\n
    "},{"location":"core-services/network-status-rest-v1/#ethernetinterfacestatus","title":"EthernetInterfaceStatus","text":"

    Object that contains specific properties to describe the status of an Ethernet interface. It contains also all of the properties specified by NetworkInterfaceStatus.

    Properties:

    • linkUp: bool
    {\n  \"autoConnect\": true,\n  \"driver\": \"igb\",\n  \"driverVersion\": \"5.6.0-k\",\n  \"firmwareVersion\": \"3.25, 0x800005d0\",\n  \"hardwareAddress\": \"00:E0:C7:0A:5F:89\",\n  \"id\": \"eno1\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"172.16.0.1\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": [],\n    \"gateway\": \"0.0.0.0\"\n  },\n  \"interfaceName\": \"eno1\",\n  \"linkUp\": true,\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"ETHERNET\",\n  \"virtual\": false\n}\n
    "},{"location":"core-services/network-status-rest-v1/#wifiinterfacestatus","title":"WifiInterfaceStatus","text":"

    Object that contains specific properties to describe the status of a WiFi interface. It contains also all of the properties specified by NetworkInterfaceStatus.

    Properties:

    • capabilities: array

      • array element type: string (enumerated)
        • WifiCapability
    • channels: array

      • array element type: object
        • WifiChannel
    • countryCode: string

    • mode: string (enumerated)

      • WifiMode
    • activeWifiAccessPoint: object (optional)

      • WifiAccessPoint
    • availableWifiAccessPoints: array

      • array element type: object
        • WifiAccessPoint

    {\n  \"activeWifiAccessPoint\": {\n    \"channel\": {\n      \"channel\": 11,\n      \"frequency\": 2462\n    },\n    \"hardwareAddress\": \"11:22:33:44:55:66\",\n    \"maxBitrate\": 130000,\n    \"mode\": \"INFRA\",\n    \"rsnSecurity\": [\n      \"GROUP_CCMP\",\n      \"KEY_MGMT_PSK\",\n      \"PAIR_CCMP\"\n    ],\n    \"signalQuality\": 100,\n    \"signalStrength\": -20,\n    \"ssid\": \"MyAccessPoint\",\n    \"wpaSecurity\": [\n      \"NONE\"\n    ],\n    \"flags\": [\n      \"PRIVACY\"\n    ]\n  },\n  \"autoConnect\": true,\n  \"availableWifiAccessPoints\": [\n    {\n      \"channel\": {\n        \"channel\": 11,\n        \"frequency\": 2462\n      },\n      \"hardwareAddress\": \"11:22:33:44:55:66\",\n      \"maxBitrate\": 130000,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 100,\n      \"signalStrength\": -20,\n      \"ssid\": \"MyAccessPoint\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\"\n      ]\n    },\n    {\n      \"channel\": {\n        \"channel\": 5,\n        \"frequency\": 2432\n      },\n      \"hardwareAddress\": \"22:33:44:55:66:77\",\n      \"maxBitrate\": 270000,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 42,\n      \"signalStrength\": -69,\n      \"ssid\": \"OtherSSID\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\"\n      ]\n    }\n  ],\n  \"capabilities\": [\n    \"CIPHER_WEP40\",\n    \"WPA\",\n    \"AP\",\n    \"FREQ_VALID\",\n    \"ADHOC\",\n    \"RSN\",\n    \"CIPHER_TKIP\",\n    \"CIPHER_WEP104\",\n    \"CIPHER_CCMP\",\n    \"FREQ_2GHZ\"\n  ],\n  \"channels\": [\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 1,\n      \"disabled\": false,\n      \"frequency\": 2412,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    },\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 2,\n      \"disabled\": false,\n      \"frequency\": 2417,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    }\n  ],\n  \"countryCode\": \"IT\",\n  \"driver\": \"brcmfmac\",\n  \"driverVersion\": \"7.45.98.94\",\n  \"firmwareVersion\": \"01-3b33decd\",\n  \"hardwareAddress\": \"44:55:66:77:88:99\",\n  \"id\": \"wlan0\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"192.168.0.113\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"wlan0\",\n  \"mode\": \"INFRA\",\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"WIFI\",\n  \"virtual\": false\n}\n
    {\n  \"activeWifiAccessPoint\": {\n    \"channel\": {\n      \"channel\": 1,\n      \"frequency\": 2412\n    },\n    \"hardwareAddress\": \"44:55:66:77:88:99\",\n    \"maxBitrate\": 0,\n    \"mode\": \"INFRA\",\n    \"rsnSecurity\": [\n      \"GROUP_CCMP\",\n      \"KEY_MGMT_PSK\",\n      \"PAIR_CCMP\"\n    ],\n    \"signalQuality\": 0,\n    \"signalStrength\": -104,\n    \"ssid\": \"kura_gateway_raspberry_pi\",\n    \"wpaSecurity\": [\n      \"NONE\"\n    ],\n    \"flags\": [\n      \"PRIVACY\"\n    ]\n  },\n  \"autoConnect\": true,\n  \"availableWifiAccessPoints\": [\n    {\n      \"channel\": {\n        \"channel\": 1,\n        \"frequency\": 2412\n      },\n      \"hardwareAddress\": \"44:55:66:77:88:99\",\n      \"maxBitrate\": 0,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 0,\n      \"signalStrength\": -104,\n      \"ssid\": \"kura_gateway_raspberry_pi\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\",\n        \"WPS\"\n      ]\n    }\n  ],\n  \"capabilities\": [\n    \"CIPHER_WEP40\",\n    \"WPA\",\n    \"AP\",\n    \"FREQ_VALID\",\n    \"ADHOC\",\n    \"RSN\",\n    \"CIPHER_TKIP\",\n    \"CIPHER_WEP104\",\n    \"CIPHER_CCMP\",\n    \"FREQ_2GHZ\"\n  ],\n  \"channels\": [\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 1,\n      \"disabled\": false,\n      \"frequency\": 2412,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    },\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 2,\n      \"disabled\": false,\n      \"frequency\": 2417,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    }\n  ],\n  \"countryCode\": \"00\",\n  \"driver\": \"brcmfmac\",\n  \"driverVersion\": \"7.45.98.94\",\n  \"firmwareVersion\": \"01-3b33decd\",\n  \"hardwareAddress\": \"44:55:66:77:88:99\",\n  \"id\": \"wlan0\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"172.16.1.1\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"wlan0\",\n  \"mode\": \"MASTER\",\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"WIFI\",\n  \"virtual\": false\n}\n

    "},{"location":"core-services/network-status-rest-v1/#modeminterfacestatus","title":"ModemInterfaceStatus","text":"

    Object that contains specific properties to describe the status of a Modem interface. It contains also all of the properties specified by NetworkInterfaceStatus.

    Properties:

    • model: string

    • manufacturer: string

    • serialNumber: string

    • softwareRevision: string

    • hardwareRevision: string

    • primaryPort: string

    • ports: object

      • ModemPorts
    • supportedModemCapabilities: array

      • array element type: string (enumerated)
        • ModemCapability
    • currentModemCapabilities: array

      • array element type: string (enumerated)
        • ModemCapability
    • powerState: string (enumerated)

      • ModemPowerState
    • supportedModes: array

      • array element type: object
        • ModemModePair
    • currentModes: object

      • ModemModePair
    • supportedBands: array

      • array element type: string (enumerated)
        • ModemBand
    • currentBands: array

      • array element type: string (enumerated)
        • ModemBand
    • gpsSupported: bool

    • availableSims: array

      • array element type: object
        • Sim
    • simLocked: bool

    • bearers: array

      • array element type: object
        • Bearer
    • connectionType: string (enumerated)

      • ModemConnectionType
    • connectionStatus: string (enumerated)

      • ModemConnectionStatus
    • accessTechnologies: array

      • array element type: string (enumerated)
        • AccessTechnology
    • signalQuality: number

    • signalStrength: number

    • registrationStatus: string (enumerated)

      • RegistrationStatus
    • operatorName: string

    {\n  \"accessTechnologies\": [\n    \"LTE\"\n  ],\n  \"autoConnect\": true,\n  \"availableSims\": [\n    {\n      \"active\": true,\n      \"primary\": true,\n      \"eSimStatus\": \"UNKNOWN\",\n      \"eid\": \"\",\n      \"iccid\": \"1111111111111111111\",\n      \"imsi\": \"111111111111111\",\n      \"operatorName\": \"MyOperator\",\n      \"operatorIdentifier\": \"12345\",\n      \"simType\": \"PHYSICAL\"\n    }\n  ],\n  \"bearers\": [\n    {\n      \"apn\": \"apn.myoperator.com\",\n      \"bytesReceived\": 0,\n      \"bytesTransmitted\": 0,\n      \"connected\": true,\n      \"ipTypes\": [\n        \"IPV4\"\n      ],\n      \"name\": \"wwp11s0f2u3i4\"\n    }\n  ],\n  \"connectionStatus\": \"CONNECTED\",\n  \"connectionType\": \"DirectIP\",\n  \"currentBands\": [\n    \"DCS\",\n    \"EUTRAN_3\",\n    \"EUTRAN_19\",\n    \"EUTRAN_40\",\n    \"EUTRAN_26\",\n    \"EUTRAN_28\",\n    \"EUTRAN_41\",\n    \"UTRAN_6\",\n    \"EUTRAN_13\",\n    \"EUTRAN_25\",\n    \"EUTRAN_5\",\n    \"EUTRAN_7\",\n    \"EUTRAN_8\",\n    \"UTRAN_2\",\n    \"EUTRAN_38\",\n    \"UTRAN_1\",\n    \"EUTRAN_12\",\n    \"UTRAN_8\",\n    \"EUTRAN_18\",\n    \"UTRAN_19\",\n    \"G850\",\n    \"EUTRAN_20\",\n    \"UTRAN_5\",\n    \"EUTRAN_1\",\n    \"EUTRAN_39\",\n    \"EUTRAN_2\",\n    \"EUTRAN_4\",\n    \"EGSM\",\n    \"PCS\",\n    \"UTRAN_4\"\n  ],\n  \"currentModemCapabilities\": [\n    \"GSM_UMTS\",\n    \"LTE\"\n  ],\n  \"currentModes\": {\n    \"modes\": [\n      \"MODE_2G\",\n      \"MODE_3G\",\n      \"MODE_4G\"\n    ],\n    \"preferredMode\": \"MODE_4G\"\n  },\n  \"driver\": \"qmi_wwan, option1\",\n  \"driverVersion\": \"\",\n  \"firmwareVersion\": \"\",\n  \"gpsSupported\": true,\n  \"hardwareAddress\": \"00:00:00:00:00:00\",\n  \"hardwareRevision\": \"10000\",\n  \"id\": \"1-3\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"1.2.3.4\",\n        \"prefix\": 30\n      }\n    ],\n    \"dnsServerAddresses\": [\n      \"1.2.3.6\",\n      \"1.2.3.7\"\n    ],\n    \"gateway\": \"1.2.3.5\"\n  },\n  \"interfaceName\": \"wwp11s0f2u3i4\",\n  \"manufacturer\": \"QUALCOMM INCORPORATED\",\n  \"model\": \"QUECTEL Mobile Broadband Module\",\n  \"mtu\": 1500,\n  \"operatorName\": \"MyOperator\",\n  \"ports\": {\n    \"cdc-wdm0\": \"QMI\",\n    \"ttyUSB0\": \"QCDM\",\n    \"ttyUSB1\": \"GPS\",\n    \"ttyUSB2\": \"AT\",\n    \"ttyUSB3\": \"AT\",\n    \"wwp11s0f2u3i4\": \"NET\"\n  },\n  \"powerState\": \"ON\",\n  \"primaryPort\": \"cdc-wdm0\",\n  \"registrationStatus\": \"HOME\",\n  \"serialNumber\": \"111111111111111\",\n  \"signalQuality\": 55,\n  \"signalStrength\": -80,\n  \"simLocked\": true,\n  \"softwareRevision\": \"EG25GGBR07A08M2G\",\n  \"state\": \"ACTIVATED\",\n  \"supportedBands\": [\n    \"DCS\",\n    \"EUTRAN_3\",\n    \"EUTRAN_19\",\n    \"EUTRAN_40\",\n    \"EUTRAN_26\",\n    \"EUTRAN_28\",\n    \"EUTRAN_41\",\n    \"UTRAN_6\",\n    \"EUTRAN_13\",\n    \"EUTRAN_25\",\n    \"EUTRAN_5\",\n    \"EUTRAN_7\",\n    \"EUTRAN_8\",\n    \"UTRAN_2\",\n    \"EUTRAN_38\",\n    \"UTRAN_1\",\n    \"EUTRAN_12\",\n    \"UTRAN_8\",\n    \"EUTRAN_18\",\n    \"UTRAN_19\",\n    \"G850\",\n    \"EUTRAN_20\",\n    \"UTRAN_5\",\n    \"EUTRAN_1\",\n    \"EUTRAN_39\",\n    \"EUTRAN_2\",\n    \"EUTRAN_4\",\n    \"EGSM\",\n    \"PCS\",\n    \"UTRAN_4\"\n  ],\n  \"supportedModemCapabilities\": [\n    \"NONE\"\n  ],\n  \"supportedModes\": [\n    {\n      \"modes\": [\n        \"MODE_4G\"\n      ],\n      \"preferredMode\": \"NONE\"\n    },\n    {\n      \"modes\": [\n        \"MODE_2G\",\n        \"MODE_3G\",\n        \"MODE_4G\"\n      ],\n      \"preferredMode\": \"MODE_4G\"\n    }\n  ],\n  \"type\": \"MODEM\",\n  \"virtual\": false\n}\n
    "},{"location":"core-services/network-status-rest-v1/#networkinterfacestatus","title":"NetworkInterfaceStatus","text":"

    This object contains the common properties contained by the status object reported for a network interface. A network interface is identified by Kura using the id field. It is used to internally manage the interface. The interfaceName, instead, is the IP interface as it may appear on the system. For Ethernet and WiFi interfaces the two values coincide (i.e. eth0, wlp1s0, ...). For modems, instead, the id is typically the usb or pci path, while the interfaceName is the IP interface created when they are connected. When the modem is disconnected the interfaceName can have a different value.

    Properties:

    • id: string

    • interfaceName: string

    • hardwareAddress: string

      • HardwareAddress
    • driver: string

    • driverVersion: string

    • firmwareVersion: string

    • virtual: bool

    • state: string (enumerated)

      • NetworkInterfaceState
    • autoConnect: bool

    • mtu: number

    • interfaceIp4Addresses: array (optional)

      • array element type: object
        • NetworkInterfaceIpAddressStatus
    • interfaceIp6Addresses: array (optional)

      • array element type: object
        • NetworkInterfaceIpAddressStatus
    "},{"location":"core-services/network-status-rest-v1/#ipaddressstring","title":"IPAddressString","text":"

    Represents an IPv4 or IPv6 addresses as a string.

    "},{"location":"core-services/network-status-rest-v1/#hardwareaddress","title":"HardwareAddress","text":"

    Represents an hardware address as its bytes reported as two characters hexadecimal strings separated by the ':' character. e.g. 00:11:22:33:A4:FC:BB.

    "},{"location":"core-services/network-status-rest-v1/#networkinterfaceipaddress","title":"NetworkInterfaceIpAddress","text":"

    This object describes an IP address with its prefix. It can be used for IPv4 or IPv6 addresses.

    Properties:

    • address: string

      • IPAddressString
    • prefix: number

    "},{"location":"core-services/network-status-rest-v1/#networkinterfaceipaddressstatus","title":"NetworkInterfaceIpAddressStatus","text":"

    This class describes the IP address status of a network interface: a list of IP addresses, an optional gateway and a list of DNS servers address. It can be used for IPv4 or IPv6 addresses.

    Properties:

    • addresses: array

      • array element type: object
        • NetworkInterfaceIpAddress
    • gateway: string (optional This field can be missing if the interface has no gateway.)

      • IPAddressString
    • dnsServerAddresses: array

      • array element type: string
        • IPAddressString
    "},{"location":"core-services/network-status-rest-v1/#wifichannel","title":"WifiChannel","text":"

    This class represent a WiFi channel, providing the channel number, frequency, status and other useful information.

    Properties:

    • channel: number

    • frequency: number

    • disabled: bool (optional)

    • attenuation: number (optional)

    • noInitiatingRadiation: bool (optional)

    • radarDetection: bool (optional)

    "},{"location":"core-services/network-status-rest-v1/#wifiaccesspoint","title":"WifiAccessPoint","text":"

    This object describes a Wifi Access Point. It can be used both for describing a detected AP after a WiFi scan when in Station mode and the provided AP when in Master (or Access Point) mode.

    Properties:

    • ssid: string The Service Set IDentifier of the WiFi network

    • hardwareAddress: string

      • HardwareAddress
    • channel: object

      • WifiChannel
    • mode: string (enumerated)

      • WifiMode
    • maxBitrate: number The maximum bitrate this access point is capable of.

    • signalQuality: number The current signal quality of the access point in percentage.

    • signalStrength: number The current signal strength of the access point in dBm.

    • wpaSecurity: array The WPA capabilities of the access point

      • array element type: string (enumerated)
        • WifiSecurity
    • rsnSecurity: array The RSN capabilities of the access point

      • array element type: string (enumerated)
        • WifiSecurity
    • flags: array The capabilities of the access point

      • array element type: string (enumerated)
        • WifiFlags
    "},{"location":"core-services/network-status-rest-v1/#modemmodepair","title":"ModemModePair","text":"

    This object represents a pair of Modem Mode list and a preferred one.

    Properties:

    • modes: array

      • array element type: string (enumerated)
        • ModemMode
    • preferredMode: string (enumerated)

      • ModemMode
    "},{"location":"core-services/network-status-rest-v1/#sim","title":"Sim","text":"

    This class contains all relevant properties to describe a SIM (Subscriber Identity Module).

    Properties:

    • active: bool

    • primary: bool

    • iccid: string

    • imsi: string

    • eid: string

    • operatorName: string

    • operatorIdentifier: string

    • simType: string (enumerated)

      • SimType
    • eSimStatus: string (enumerated)

      • ESimStatus
    "},{"location":"core-services/network-status-rest-v1/#bearer","title":"Bearer","text":"

    This object describes the Bearer or Context associated to a modem connection.

    Properties:

    • name: string

    • connected: bool

    • apn: string

    • ipTypes: array

      • array element type: string (enumerated)
        • BearerIpType
    • bytesTransmitted: number

    • bytesReceived: number

    "},{"location":"core-services/network-status-rest-v1/#modemports","title":"ModemPorts","text":"

    An object representing a set of modem ports. The members of this object represent modem ports, the member names represent the modem port name. This object can have a variable number of members.

    Properties:

    • propertyName: string (enumerated)
      • ModemPortType
    "},{"location":"core-services/network-status-rest-v1/#networkinterfacetype","title":"NetworkInterfaceType","text":"

    The type of a network interface.

    • Possible values
      • UNKNOWN: The device type is unknown
      • ETHERNET: The device is a wired Ethernet device.
      • WIFI: The device is an 802.11 WiFi device.
      • UNUSED1
      • UNUSED2
      • BT: The device is a Bluetooth device.
      • OLPC_MESH: The device is an OLPC mesh networking device.
      • WIMAX: The device is an 802.16e Mobile WiMAX device.
      • MODEM: The device is a modem supporting one or more of analog telephone, CDMA/EVDO, GSM/UMTS/HSPA, or LTE standards to access a cellular or wireline data network.
      • INFINIBAND: The device is an IP-capable InfiniBand interface.
      • BOND: The device is a bond master interface.
      • VLAN: The device is a VLAN interface.
      • ADSL: The device is an ADSL device.
      • BRIDGE: The device is a bridge master interface.
      • GENERIC: This is a generic support for unrecognized device types.
      • TEAM: The device is a team master interface.
      • TUN: The device is a TUN or TAP interface.
      • TUNNEL: The device is an IP tunnel interface.
      • MACVLAN: The device is a MACVLAN interface.
      • VXLAN: The device is a VXLAN interface.
      • VETH: The device is a VETH interface.
      • MACSEC: The device is a MACsec interface.
      • DUMMY: The device is a dummy interface.
      • PPP: The device is a PPP interface.
      • OVS_INTERFACE: The device is a Open vSwitch interface.
      • OVS_PORT: The device is a Open vSwitch port
      • OVS_BRIDGE: The device is a Open vSwitch bridge.
      • WPAN: The device is a IEEE 802.15.4 (WPAN) MAC Layer Device.
      • SIXLOWPAN: The device is a 6LoWPAN interface.
      • WIREGUARD: The device is a WireGuard interface.
      • WIFI_P2P: The device is an 802.11 Wi-Fi P2P device.
      • VRF: The device is a VRF (Virtual Routing and Forwarding) interface.
      • LOOPBACK: The device is a loopback device.
    "},{"location":"core-services/network-status-rest-v1/#networkinterfacestate","title":"NetworkInterfaceState","text":"

    The state of a network interface.

    • Possible values
      • UNKNOWN: The device is in an unknown state.
      • UNMANAGED: The device cannot be used (carrier off, rfkill, etc).
      • UNAVAILABLE: The device is not connected.
      • DISCONNECTED: The device is preparing to connect.
      • PREPARE: The device is being configured.
      • CONFIG: The device is awaiting secrets necessary to continue connection.
      • NEED_AUTH: The IP settings of the device are being requested and configured.
      • IP_CONFIG: The device's IP connectivity ability is being determined.
      • IP_CHECK: The device is waiting for secondary connections to be activated.
      • SECONDARIES: The device is waiting for secondary connections to be activated.
      • ACTIVATED: The device is active.
      • DEACTIVATING: The device's network connection is being turn down.
      • FAILED: The device is in a failure state following an attempt to activate it.
    "},{"location":"core-services/network-status-rest-v1/#wificapability","title":"WifiCapability","text":"

    The capability of a WiFi interface.

    • Possible values
      • NONE: The device has no encryption/authentication capabilities
      • CIPHER_WEP40: The device supports 40/64-bit WEP encryption.
      • CIPHER_WEP104: The device supports 104/128-bit WEP encryption.
      • CIPHER_TKIP: The device supports the TKIP encryption.
      • CIPHER_CCMP: The device supports the AES/CCMP encryption.
      • WPA: The device supports the WPA1 encryption/authentication protocol.
      • RSN: The device supports the WPA2/RSN encryption/authentication protocol.
      • AP: The device supports Access Point mode.
      • ADHOC: The device supports Ad-Hoc mode.
      • FREQ_VALID: The device reports frequency capabilities.
      • FREQ_2GHZ: The device supports 2.4GHz frequencies.
      • FREQ_5GHZ: The device supports 5GHz frequencies.
      • MESH: The device supports mesh points.
      • IBSS_RSN: The device supports WPA2 in IBSS networks
    "},{"location":"core-services/network-status-rest-v1/#wifimode","title":"WifiMode","text":"

    Modes of operation for wifi interfaces

    • Possible values
      • UNKNOWN: Mode is unknown.
      • ADHOC: Uncoordinated network without central infrastructure.
      • INFRA: Client mode - Coordinated network with one or more central controllers.
      • MASTER: Access Point Mode - Coordinated network with one or more central controllers.
    "},{"location":"core-services/network-status-rest-v1/#wifisecurity","title":"WifiSecurity","text":"

    Flags describing the security capabilities of an access point.

    • Possible values
      • NONE: None
      • PAIR_WEP40: Supports pairwise 40-bit WEP encryption.
      • PAIR_WEP104: Supports pairwise 104-bit WEP encryption.
      • PAIR_TKIP: Supports pairwise TKIP encryption.
      • PAIR_CCMP: Supports pairwise CCMP encryption.
      • GROUP_WEP40: Supports a group 40-bit WEP cipher.
      • GROUP_WEP104: Supports a group 104-bit WEP cipher.
      • GROUP_TKIP: Supports a group TKIP cipher.
      • GROUP_CCMP: Supports a group CCMP cipher.
      • KEY_MGMT_PSK: Supports PSK key management.
      • KEY_MGMT_802_1X: Supports 802.1x key management.
      • SECURITY_NONE: Supports no encryption.
      • SECURITY_WEP: Supports WEP encryption.
      • SECURITY_WPA: Supports WPA encryption.
      • SECURITY_WPA2: Supports WPA2 encryption.
      • SECURITY_WPA_WPA2: Supports WPA and WPA2 encryption.
    "},{"location":"core-services/network-status-rest-v1/#wififlags","title":"WifiFlags","text":"

    Flags describing the capabilities of an access point.

    • Possible values
      • NONE: None
      • PRIVACY: supports authentication and encryption
      • WPS: supports WPS (Wi-Fi Protected Setup)
      • WPS_PBC: supports push-button based WPS
      • WPS_PIN: supports PIN based WPS
    "},{"location":"core-services/network-status-rest-v1/#modemporttype","title":"ModemPortType","text":"

    The type of a modem port.

    • Possible values
      • UNKNOWN
      • NET
      • AT
      • QCDM
      • GPS
      • QMI
      • MBIM
      • AUDIO
      • IGNORED
    "},{"location":"core-services/network-status-rest-v1/#modemcapability","title":"ModemCapability","text":"

    The generic access technologies families supported by a modem.

    • Possible values
      • NONE: The modem has no capabilities.
      • POTS: The modem supports the Plain Old Telephone Service (analog wired telephone network).
      • EVDO: The modem supports EVDO revision 0, A or B.
      • GSM_UMTS: The modem supports at least one of GSM, GPRS, EDGE, UMTS, HSDPA, HSUPA or HSPA+ technologies.
      • LTE: The modem has LTE capabilities.
      • IRIDIUM: The modem supports Iridium technology.
      • FIVE_GNR: The modem supports 5GNR.
      • TDS: The modem supports TDS.
      • ANY: The modem supports all capabilities.
    "},{"location":"core-services/network-status-rest-v1/#modemmode","title":"ModemMode","text":"

    The generic access mode a modem supports.

    • Possible values
      • NONE
      • CS
      • MODE_2G
      • MODE_3G
      • MODE_4G
      • MODE_5G
      • ANY
    "},{"location":"core-services/network-status-rest-v1/#modemband","title":"ModemBand","text":"

    The radio bands supported by a modem when connected to a mobile network.

    • Possible values
      • UNKNOWN
      • EGSM
      • DCS
      • PCS
      • G850
      • UTRAN_1
      • UTRAN_3
      • UTRAN_4
      • UTRAN_6
      • UTRAN_5
      • UTRAN_8
      • UTRAN_9
      • UTRAN_2
      • UTRAN_7
      • G450
      • G480
      • G750
      • G380
      • G410
      • G710
      • G810
      • EUTRAN_1
      • EUTRAN_2
      • EUTRAN_3
      • EUTRAN_4
      • EUTRAN_5
      • EUTRAN_6
      • EUTRAN_7
      • EUTRAN_8
      • EUTRAN_9
      • EUTRAN_10
      • EUTRAN_11
      • EUTRAN_12
      • EUTRAN_13
      • EUTRAN_14
      • EUTRAN_17
      • EUTRAN_18
      • EUTRAN_19
      • EUTRAN_20
      • EUTRAN_21
      • EUTRAN_22
      • EUTRAN_23
      • EUTRAN_24
      • EUTRAN_25
      • EUTRAN_26
      • EUTRAN_27
      • EUTRAN_28
      • EUTRAN_29
      • EUTRAN_30
      • EUTRAN_31
      • EUTRAN_32
      • EUTRAN_33
      • EUTRAN_34
      • EUTRAN_35
      • EUTRAN_36
      • EUTRAN_37
      • EUTRAN_38
      • EUTRAN_39
      • EUTRAN_40
      • EUTRAN_41
      • EUTRAN_42
      • EUTRAN_43
      • EUTRAN_44
      • EUTRAN_45
      • EUTRAN_46
      • EUTRAN_47
      • EUTRAN_48
      • EUTRAN_49
      • EUTRAN_50
      • EUTRAN_51
      • EUTRAN_52
      • EUTRAN_53
      • EUTRAN_54
      • EUTRAN_55
      • EUTRAN_56
      • EUTRAN_57
      • EUTRAN_58
      • EUTRAN_59
      • EUTRAN_60
      • EUTRAN_61
      • EUTRAN_62
      • EUTRAN_63
      • EUTRAN_64
      • EUTRAN_65
      • EUTRAN_66
      • EUTRAN_67
      • EUTRAN_68
      • EUTRAN_69
      • EUTRAN_70
      • EUTRAN_71
      • EUTRAN_85
      • CDMA_BC0
      • CDMA_BC1
      • CDMA_BC2
      • CDMA_BC3
      • CDMA_BC4
      • CDMA_BC5
      • CDMA_BC6
      • CDMA_BC7
      • CDMA_BC8
      • CDMA_BC9
      • CDMA_BC10
      • CDMA_BC11
      • CDMA_BC12
      • CDMA_BC13
      • CDMA_BC14
      • CDMA_BC15
      • CDMA_BC16
      • CDMA_BC17
      • CDMA_BC18
      • CDMA_BC19
      • UTRAN_10
      • UTRAN_11
      • UTRAN_12
      • UTRAN_13
      • UTRAN_14
      • UTRAN_19
      • UTRAN_20
      • UTRAN_21
      • UTRAN_22
      • UTRAN_25
      • UTRAN_26
      • UTRAN_32
      • ANY
      • NGRAN_1
      • NGRAN_2
      • NGRAN_3
      • NGRAN_5
      • NGRAN_7
      • NGRAN_8
      • NGRAN_12
      • NGRAN_13
      • NGRAN_14
      • NGRAN_18
      • NGRAN_20
      • NGRAN_25
      • NGRAN_26
      • NGRAN_28
      • NGRAN_29
      • NGRAN_30
      • NGRAN_34
      • NGRAN_38
      • NGRAN_39
      • NGRAN_40
      • NGRAN_41
      • NGRAN_48
      • NGRAN_50
      • NGRAN_51
      • NGRAN_53
      • NGRAN_65
      • NGRAN_66
      • NGRAN_70
      • NGRAN_71
      • NGRAN_74
      • NGRAN_75
      • NGRAN_76
      • NGRAN_77
      • NGRAN_78
      • NGRAN_79
      • NGRAN_80
      • NGRAN_81
      • NGRAN_82
      • NGRAN_83
      • NGRAN_84
      • NGRAN_86
      • NGRAN_89
      • NGRAN_90
      • NGRAN_91
      • NGRAN_92
      • NGRAN_93
      • NGRAN_94
      • NGRAN_95
      • NGRAN_257
      • NGRAN_258
      • NGRAN_260
      • NGRAN_261
    "},{"location":"core-services/network-status-rest-v1/#simtype","title":"SimType","text":"

    The SIM (Subscriber Identity Module) type.

    • Possible values
      • UNKNOWN
      • PHYSICAL
      • ESIM
    "},{"location":"core-services/network-status-rest-v1/#esimstatus","title":"ESimStatus","text":"

    The status of an ESIM.

    • Possible values
      • UNKNOWN
      • NO_PROFILES
      • WITH_PROFILES
    "},{"location":"core-services/network-status-rest-v1/#beareriptype","title":"BearerIpType","text":"

    The type of Bearer or Context associated to a modem connection.

    • Possible values
      • NONE
      • IPV4
      • IPV6
      • IPV4V6
      • NON_IP
      • ANY
    "},{"location":"core-services/network-status-rest-v1/#modemconnectiontype","title":"ModemConnectionType","text":"
    • Possible values
      • PPP: Point to Point Protocol
      • DirectIP: Direct IP
    "},{"location":"core-services/network-status-rest-v1/#modemconnectionstatus","title":"ModemConnectionStatus","text":"

    The status of a modem.

    • Possible values
      • FAILED: The modem is unavailable
      • UNKNOWN: The modem is in an unknown state.
      • INITIALIZING: The modem is being initialised.
      • LOCKED: The modem is locked.
      • DISABLED: The modem is disabled and powered off.
      • DISABLING: The modem is disabling.
      • ENABLING: The modem is enabling..
      • ENABLED: The modem is enabled but not registered to a network provider.
      • SEARCHING: The modem is searching for a network provider.
      • REGISTERED: The modem is registered to a network provider.
      • DISCONNECTING: The modem is disconnecting.
      • CONNECTING: The modem is connecting.
      • CONNECTED: The modem is connected.
    "},{"location":"core-services/network-status-rest-v1/#accesstechnology","title":"AccessTechnology","text":"

    The specific technology types used when a modem is connected or registered to a network.

    • Possible values
      • UNKNOWN
      • POTS
      • GSM
      • GSM_COMPACT
      • GPRS
      • EDGE
      • UMTS
      • HSDPA
      • HSUPA
      • HSPA
      • HSPA_PLUS
      • ONEXRTT
      • EVDO0
      • EVDOA
      • EVDOB
      • LTE
      • FIVEGNR
      • LTE_CAT_M
      • LTE_NB_IOT
      • ANY
    "},{"location":"core-services/network-status-rest-v1/#registrationstatus","title":"RegistrationStatus","text":"

    The registration status of a modem when connected to a mobile network.

    • Possible values
      • IDLE
      • HOME
      • SEARCHING
      • DENIED
      • UNKNOWN
      • ROAMING
      • HOME_SMS_ONLY
      • ROAMING_SMS_ONLY
      • EMERGENCY_ONLY
      • HOME_CSFB_NOT_PREFERRED
      • ROAMING_CSFB_NOT_PREFERRED
      • ATTACHED_RLOS
    "},{"location":"core-services/network-status-rest-v1/#failurereport","title":"FailureReport","text":"

    An object reporting a failure while retrieving the status of a specific network interface

    Properties:

    • interfaceId: string The identifier of the interface whose status cannot be retrieved.

    • reason: string A message describing the reason of the failure.

    "},{"location":"core-services/network-status-rest-v1/#genericfailurereport","title":"GenericFailureReport","text":"

    An object reporting a failure message.

    Properties:

    • message: string A message describing the failure.
    "},{"location":"core-services/nvidia-triton-server-inference-engine/","title":"Nvidia\u2122 Triton Server Inference Engine","text":"

    The Nvidia\u2122 Triton Server is an open-source inference service software that enables the user to deploy trained AI models from any framework on GPU or CPU infrastructure. It supports all major frameworks like TensorFlow, TensorRT, PyTorch, ONNX Runtime, and even custom framework backend. With specific backends, it is also possible to run Python scripts, mainly for pre-and post-processing purposes, and exploit the DALI building block for optimized operations. For more detail about the Triton Server, please refer to the official website.

    Kura provides three components for exposing the Triton Server service functionality which implement the inference engine APIs and provides methods for interacting with a local or remote Nvidia\u2122 Triton Server:

    • TritonServerRemoteService: provides methods for interacting with a remote Nvidia\u2122 Triton Server without managing the server lifecycle. Can be used both for connecting to a remote instance or a local non-managed instance. It exposes a simpler but more limited configuration.
    • TritonServerNativeService: provides methods for interacting with a local native Nvidia\u2122 Triton Server. Requires the Triton Server executable to be already available on the device and offers more options and features (like AI Model Encryption).
    • TritonServerContainerService: provides methods for interacting with a local container running Nvidia\u2122 Triton Server. Requires the Triton Server container image to be already available on the device and offers more options and features (like AI Model Encryption).
    • TritonServerService: provides methods for interacting with a local or remote Nvidia\u2122 Triton Server within the same component. Note: deprecated since 5.2.0
    "},{"location":"core-services/nvidia-triton-server-inference-engine/#nvidiatm-triton-server-installation","title":"Nvidia\u2122 Triton Server installation","text":"

    Before running Kura's Triton Server Service, you must install the Triton Inference Server. Here you can find the necessary steps for the available suggested installation methods.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#native-triton-installation-on-jetson-devices","title":"Native Triton installation on Jetson devices","text":"

    A release of Triton for JetPack is provided in the tar file in the Triton Inference Server release notes. Full documentation is available here.

    Installation steps:

    • Before running the executable you need to install the Runtime Dependencies for Triton.
    • After doing so you can extract the tar file and run the executable in the bin folder.
    • It is highly recommended to add the tritonserver executable to your path or symlinking the executable to /usr/local/bin.
    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-docker-image-installation","title":"Triton Docker image installation","text":"

    Before you can use the Triton Docker image you must install Docker. If you plan on using a GPU for inference you must also install the NVIDIA Container Toolkit.

    Pull the image using the following command.

    $ docker pull nvcr.io/nvidia/tritonserver:<xx.yy>-py3\n

    Where <xx.yy> is the version of Triton that you want to pull.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#native-triton-installation-on-supported-devices","title":"Native Triton installation on supported devices","text":"

    The official docs mention the possibility to perform a native installation on supported platform by extracting the binaries from the Docker images. To do so you must install the necessary dependencies (some can be found in the Jetson runtime dependencies docs) on the system. For Triton to support NVIDIA GPUs you must install CUDA, cuBLAS and cuDNN referencing the support matrix.

    Note

    For Python models the libraries available to the Python model are the ones available for the user running the Triton server. Therefore you'll need to install the libraries through pip for the kurad user.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-setup","title":"Triton Server setup","text":"

    The Triton Inference Server serves models from one or more model repositories that are specified when the server is started. The model repository is the directory where you place the models that you want Triton to serve. Be sure to follow the instructions to setup the model repository directory.

    Further information about an example Triton Server setup can be found in the official documentation.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-remote-service-component","title":"Triton Server Remote Service component","text":"

    The Kura Triton Server Remote Service component is the implementation of the inference engine APIs and provides methods for interacting with a remote (i.e. unmnanaged) Nvidia\u2122 Triton Server. As presented below, the component enables the user to communicate to an external server to load specific models. With this component the server lifecycle (startup, shutdown) won't be handled by Kura and it's the user responsibility to make it available to Kura for connecting.

    The parameters used to configure the Triton Service are the following:

    • Nvidia Triton Server address: the address of the Nvidia Triton Server.
    • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
    • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
    • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
    • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance. By default, size of 4194304 bytes (= 4.19 MB) is used. Increase this value to be able to send large amounts of data as input to the Triton server (like Full HD images). The Kura logs will show the following error when exceeding such limit:
      io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 4194304\n

    Note

    Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-native-service-component","title":"Triton Server Native Service component","text":"

    The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local native Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway and handles its lifecycle. This operating mode supports more features for interacting with the server like the AI Model Encryption.

    Note

    Requirement: tritonserver executable needs to be available in the path to the kurad user. Be sure to have a working Triton Server installation before configuring the local native Triton Server instance through Kura UI.

    The parameters used to configure the Triton Service are the following:

    • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
    • Local model repository path: Specify the path on the filesystem where the models are stored.
    • Local model decryption password: Specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
    • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
    • Local backends path: Specify the path on the filesystem where the backends are stored.
    • Optional configuration for the local backends: A semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
    • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
    • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

    Note

    Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-container-service-component","title":"Triton Server Container Service component","text":"

    The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local container running the Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway and handles its lifecycle. This operating mode supports more features for interacting with the server like the AI Model Encryption.

    Note

    Requirement: 1. Triton Server container image already installed on the device. For instructions refer to the installation section in this page. 2. Kura's Container Orchestration Service enabled.

    The parameters used to configure the Triton Service are the following:

    • Container Image: The image the container will be created with.
    • Container Image Tag: Describes which image version that should be used for creating the container.
    • Nvidia Triton Server ports: The ports used to connect to the server for HTTP, GRPC, and Metrics services.
    • Local model repository path: Specify the path on the filesystem where the models are stored.
    • Local model decryption password: Specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
    • Inference Models: A comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
    • Local Backends Path: Specifies the host filesystem path where the backends are stored. This folder will be mounted as a volume inside the Triton container and will override the existing backends. If left blank, the backends provided by the Triton container will be used.
    • Optional configuration for the local backends: A semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
    • Memory: The maximum amount of memory the container can use in bytes. Set it as a positive integer, optionally followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes. The minimum allowed value is platform dependent (i.e. 6m). If left empty, the memory assigned to the container will be set to a default value by the native container orchestrator.
    • CPUs: Specify how many CPUs the Triton container can use. Decimal values are allowed, so if set to 1.5, the container will use at most one and a half cpu resource.
    • GPUs: Specify how many Nvidia GPUs the Triton container can use. Allowed values are 'all' or an integer number. If there's no Nvidia GPU installed, leave the field empty. If the Nvidia Container Runtime is used, leave the field empty.
    • Runtime: Specifies the fully qualified name of an alternate OCI-compatible runtime, which is used to run commands specified by the 'run' instruction for the Triton container. Example: nvidia corresponds to --runtime=nvidia. Note: when using the Nvidia Container Runtime, leave the GPUs field empty. The GPUs available on the system will be accessible from the container by default.
    • Devices: A comma-separated list of device paths passed to the Triton server container (e.g. /dev/video0).
    • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
    • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

    Note

    Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are typically used by Kura for debug purposes.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-service-component-deprecated-since-520","title":"Triton Server Service component [deprecated since 5.2.0]","text":"

    The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local or remote Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway or to communicate to an external server to load specific models.

    The parameters used to configure the Triton Service are the following:

    • Local Nvidia Triton Server: If enabled, a local native Nvidia Triton Server is started on the gateway. In this case, the model repository and backends path are mandatory. Moreover, the server address property is overridden and set to localhost. Be aware that the Triton Server has to be already installed on the system.
    • Nvidia Triton Server address: the address of the Nvidia Triton Server.
    • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
    • Local model repository path: Only for a local instance, specify the path on the filesystem where the models are stored.
    • Local model decryption password: Only for local instance, specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
    • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
    • Local backends path: Only for a local instance, specify the path on the filesystem where the backends are stored.
    • Optional configuration for the local backends: Only for local instance, a semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
    • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
    • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

    Note

    Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-local-native-triton-server-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a local native Triton Server with Triton Server Service component [deprecated since 5.2.0]","text":"

    Note

    Requirement: tritonserver executable needs to be available in the path to the kurad user. Be sure to have a working Triton Server installation before configuring the local native Triton Server instance through Kura UI.

    When the Local Nvidia Triton Server option is set to true, a local instance of the Nvidia\u2122 Triton Server is started on the gateway. The following configuration is required:

    • Local Nvidia Triton Server: true
    • Nvidia Triton Server address: localhost
    • Nvidia Triton Server ports: mandatory
    • Local model repository path: mandatory
    • Inference Models: mandatory. Note that the models have to be already present on the filesystem.
    • Local backends path: mandatory

    The typical command used to start the Triton Server is like this:

    tritonserver --model-repository=<model_repository_path> \\\n--backend-directory=<backend_repository_path> \\\n--backend-config=<backend_config> \\\n--http-port=<http_port> \\\n--grpc-port=<grpc_port> \\\n--metrics-port=<metrics_port> \\\n--model-control-mode=explicit \\\n--load-model=<model_name_1> \\\n--load-model=<model_name_2> \\\n...\n
    "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-local-triton-server-running-in-a-docker-container-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a local Triton Server running in a Docker container with Triton Server Service component [deprecated since 5.2.0]","text":"

    If the Nvidia\u2122 Triton Server is running as a Docker container in the gateway, the following configuration is required:

    • Local Nvidia Triton Server: false
    • Nvidia Triton Server address: localhost
    • Nvidia Triton Server ports: \\<mandatory>
    • Inference Models: \\<mandatory>. The models have to be already present on the filesystem.

    In order to correctly load the models at runtime, configure the server with the --model-control-mode=explicit option. The typical command used for running the docker container is as follows. Note the forward of the ports to not interfere with Kura.

    docker run --rm \\\n-p4000:8000 \\\n-p4001:8001 \\\n-p4002:8002 \\\n--shm-size=150m \\\n-v path/to/models:/models \\\nnvcr.io/nvidia/tritonserver:[version] \\\ntritonserver --model-repository=/models --model-control-mode=explicit\n
    "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-remote-triton-server-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a remote Triton Server with Triton Server Service component [deprecated since 5.2.0]","text":"

    When the Nvidia\u2122 Triton Server is running on a remote server, the following configuration is needed:

    • Local Nvidia Triton Server: false
    • Nvidia Triton Server address: mandatory
    • Nvidia Triton Server ports: mandatory
    • ** Inference Models**: mandatory. The models have to be already present on the filesystem.
    "},{"location":"core-services/nvidia-triton-server-inference-engine/#ai-model-encryption-support","title":"AI Model Encryption Support","text":"

    For ensuring inference integrity and providing copyright protection of deep-learning models on edge devices, Kura provides decryption capabilities for trained models to be served through the Triton Server.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#how-it-works","title":"How it works","text":"

    Prerequisites: a deep-learning trained model (or more) exists with the corresponding necessary configuration for running on the Triton Server without encryption. A folder containing the required files (model, configuration etc) has been tested on a Triton Server.

    Restrictions: if model encryption is used, the following restrictions apply:

    • model encryption support is only available for a local Triton Server instance
    • all models in the folder containing the encrypted models must be encrypted
    • all models must be encrypted with OpenPGP-compliant AES 256 cipher algorithm
    • all models must be encrypted with the same password

    Once the development of the deep-learning model is complete, the developer who wants to deploy the model on the edge device in a secure manner can proceed with encrypting the Triton model using the procedure detailed below. After encrypting the model he/she can transfer the file on the edge device using his/her preferred method.

    Kura will keep the stored model protected at all times and have the model decrypted in runtime only for use by the Inference Server Runtime. As soon as the model is correctly loaded into memory the decrypted model will be removed from the filesystem.

    As an additional security measure, the Model Repository containing the decrypted models will be stored in a temporary subfolder and will feature restrictive permission such that only Kura, the Inference Server and the root user will be able to access it.

    "},{"location":"core-services/nvidia-triton-server-inference-engine/#encryption-procedure","title":"Encryption procedure","text":"

    Given a trained model inside the folder tf_autoencoder_fp32 (for example) with the following layout (see the official documentation for details):

    tf_autoencoder_fp32\n\u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 model.savedmodel\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 assets\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 keras_metadata.pb\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 saved_model.pb\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 variables\n\u2502\u00a0\u00a0         \u251c\u2500\u2500 variables.data-00000-of-00001\n\u2502\u00a0\u00a0         \u2514\u2500\u2500 variables.index\n\u2514\u2500\u2500 config.pbtxt\n

    Compress the model into a zip archive with the following command:

    zip -vr tf_autoencoder_fp32.zip tf_autoencoder_fp32/\n

    then encrypt it with the AES 256 algorithm using the following gpg command:

    gpg --armor --symmetric --cipher-algo AES256 tf_autoencoder_fp32.zip\n

    The resulting archive tf_autoencoder_fp32.zip.asc can be transferred to the Local Model Repository Path on the target machine and will be decrypted by Kura.

    "},{"location":"core-services/position-service/","title":"Position Service","text":"

    The PositionService provides the geographic position of the gateway if a GPS component is available and enabled.

    When this service is enabled and provides a valid geographic position, this position is published in the gateway birth certificate with its location.

    The GPS connection parameters must be defined in order to allow the service to receive the GPS frames. The PositionService supports direct access to gps device or the connection to that through gpsd.

    For a device that is not connected to a GPS, it is possible to define a static position by entering latitude, longitude,, altitude and GNSS Type. In this case, the position, date and time information is returned by the PositionService as if it were an actual GPS position. This may be useful when a gateway is installed in a known place and does not move.

    To use this service, select the PositionService option located in the Services area as shown in the screen capture below.

    This service provides the following configuration parameters:

    • enabled - defines whether or not this service is enabled or disabled. (Required field.)

    • static - specifies true or false whether to use a static position instead of a GPS. (Required field.)

    • provider - species which position provider use, can be gpsd or serial.

      • gpsd - gpsd service daemon if is available on the system.
      • serial - direct access to gps device through serial or usb port.
    • gpsd.host - host where gpsd service deamon is running. (required only if gpsd provider is selected.)

    • gpsd.port - port where gpsd service is listening. (required only if gpsd provider is selected.)

    • latitude - provides the static latitude value in degrees.

    • longitude - provides the static longitude value in degrees.

    • altitude - provides the static altitude value in meters.

    • GNSS Type - provides the gnss system used to retrieve static information.

    • port - supplies the USB or serial port of the GPS device.

    • baudRate - supplies the baud rate of the GPS device.

    • bitsPerWord - sets the number of bits per word (databits) for the serial communication to the GPS device.

    • stopbits - sets the number of stop bits for the serial communication to the GPS device.

    • parity - sets the parity for the serial communication to the GPS device.

    "},{"location":"core-services/rest-service/","title":"REST Service","text":"

    Kura provides a built-in REST Service based on the osgi-jax-rs-connector project.

    By default, REST service providers register their services using the context path /services. The REST service provides the BASIC Authentication support and HTTPS client certificate authentication support.

    Starting from Kura 5.4.0, REST service provides built in session management support, the Session REST APIs can be used to establish a session. Using sessions is the recommended way for interacting with Kura REST APIs from a browser based application, for this use case it is also possible to disable the default session-less BASIC and certificate based authentication (see the Basic Authentication Enabled and Enable Certificate Authentication Whitout Session Management configuration parameters below).

    REST API access is available on all HTTP ports defined in the HTTP/HTTPS Configuration section, unless access is restricted to dedicated ports using the corresponding configuration parameter (see below).

    Certificate authentication support is only available on the HTTPS With Certificate Authentication Ports configured in HTTP/HTTPS Configuration section.

    Kura Identity names and passwords can be used for BASIC Authentication. Certificate authentication follows the same rules as Gateway Administration Console Authentication.

    Warning

    If the forced password change feature for a given identity is enabled, REST API password authentication will be blocked for that identity until the password is updated by the user or the feature is manually disabled. Certificate authentication will continue to be allowed even if the forced password change feature is enabled.

    JAX-RS roles are mapped to Kura permissions, the name of a permission associated with a JAX-RS role is the rest. prefix followed by the role name. For example the assets role is mapped to the rest.assets permission. REST related permissions can be assigned to an identity using the Gateway Administration Console in the Identities section.

    "},{"location":"core-services/rest-service/#rest-service-configuration","title":"Rest Service configuration","text":"

    The available configuration parameters are the following:

    • Allowed Ports: If set to a non empty list, REST API access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. Please make sure that the allowed ports are open in HttpService and Firewall configuration. (Default: empty)

    • Password Authentication Enabled: Enables or disables the built-in password authentication support. (Default: true)

    • Certificate Authentication Enabled: Enables or disables the built-in certificate authentication support. (Default: true)

    • Session Based Authentication Enabled: If set to true, enables authentication using the dedicated /services/session/v1 endpoints and cookie based session management. (Default: true)

    • Session Inactivity Interval (Seconds): The session inactivity interval, sessions will expire if no request is performed for the amount of time specified by this parameter in seconds. This parameter is ignored if Session Based Authentication Enabled is set to false. (Default: 900)

    • Basic Authentication Enabled: Allows to perform authentication by providing identity name and password as BASIC credentials in the request to any resource endpoint. Requires that the Password Authentication Enabled parameter is set to true. (Default: true)

    • Enable Certificate Authentication Without Session Management: If set to true, calling /services/session/v1/certificate to create a session will not be necessary in order to perform certificate based authentication. Presenting a valid HTTPS client certificate and accessing resource endpoint directly is enough for authentication to succeed. Requires that the Certificate Authentication Enabled parameter is set to true. (Default: true)

    "},{"location":"core-services/rest-service/#custom-authentication-methods","title":"Custom authentication methods","text":"

    Starting from Kura 5.2.0 it is also possible to develop custom REST authentication method providers by registering an implementation of the org.eclipse.kura.rest.auth.AuthenticationProvider interface as an OSGi service. The org.eclipse.kura.example.rest.authentication.provider bundle in Kura repository provides an example on how to implement a custom authentication method.

    "},{"location":"core-services/rest-service/#assets-rest-apis","title":"Assets REST APIs","text":"

    Kura exposes REST APIs for the Asset instances instantiated in the framework. Assets REST APIs are available in the context path /services/assets. Following, the supported REST endpoints.

    Method Path Allowed roles Encoding Request parameters Description GET / assets JSON None Returns the list of available assets GET /{pid} assets JSON None Returns the list of available channels for the selected asset (specified by the corresponding PID) GET /{pid}/_read assets JSON None Returns the read for all the READ channels in the selected Asset POST /{pid}/_read assets JSON The list of channels where the READ operation should be performed. Returns the result of the read operation for the specified channels POST /{pid}/_write assets JSON The list of channels and associated values that will be used for the WRITE operation. Performs the write operation for the specified channels returning the result of the operation."},{"location":"core-services/simple-artemis-mqtt-broker/","title":"Simple Artemis MQTT Broker","text":"

    By default, this instance is disabled but, selecting the Simple Artemis MQTT Broker option in Services it is possible to enable a basic instance of an \u200bActiveMQ-7 broker with MQTT capabilities.

    The service has the following configuration fields:

    • Enabled - (Required) - Enables the broker instance
    • MQTT address - MQTT broker listener address. In order to allow access to the broker from processes running on external nodes, make sure to bind the server to an externally accessible address. Setting this parameter to 0.0.0.0 binds to all addresses.
    • MQTT port - (Required) - MQTT broker port, make sure to open the specified port in the firewall configuration section if external access to the broker is required.
    • User name - The username\u200b required to access to the broker
    • Password of the user - The password required to connect. If the password is empty, no password will be required to connect.
    "},{"location":"core-services/sqlite-db-service/","title":"SQLite Db Service","text":"

    Starting from version 5.3, Kura provides provides an integration of the SQLite database, based on the org.xerial:sqlite-jdbc wrapper .

    The database integration is not included in the official distribution, but it can be downloaded from Eclipse Marketplace as a deployment package.

    Warning

    Note about Raspberry PI: Recent versions of Raspberry Pi OS 32 bit on Raspberry PI 4 will use by default a 64 bit kernel with a 32 bit userspace. This can cause issues to applications that use the result of uname -m to decide which native libraries to load (see https://github.com/raspberrypi/firmware/issues/1795). This currently affects the Kura SQLite database connector. It should be possible to solve by switching to the 32 bit kernel setting arm_64bit=0 in /boot/config.txt and restarting the device.

    "},{"location":"core-services/sqlite-db-service/#supported-features","title":"Supported Features","text":"

    Kura supports the following SQLite database features:

    • Persistence modes: The implementation currently supports in-memory and file-based database instances.

    • Multiple database instances: It is possible to create and configure multiple database instances from the Kura Administration UI, these instances can be selectively consumed by applications.

    • Journaling modes: The implementation currently supports the WAL and rollback journal journaling modes.

    • Periodic database defrag and checkpoint: The implementations currently supports periodic defrag (VACUUM) and periodic WAL checkpoint.

    "},{"location":"core-services/sqlite-db-service/#usage","title":"Usage","text":""},{"location":"core-services/sqlite-db-service/#creating-a-new-sqlite-database-instance","title":"Creating a new SQLite database instance","text":"

    To create a new SQLite database instance, use the following procedure:

    1. Open the Administrative UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
    2. Select org.eclipse.kura.db.SQLiteDbService from the Factory drop-down list, enter an arbitrary name for the new instance and click Apply.
    3. An entry for the newly created instance should appear in the side menu under Services, click on it to review its configuration. It is not possible to create different DB instances that manage the same database file.
    "},{"location":"core-services/sqlite-db-service/#configuration-parameters","title":"Configuration Parameters","text":"

    The SQLite DB provides the following configuration parameters:

    • Database Mode: Defines the database mode. If In Memory is selected, the database will not be persisted on the filesystem, all data will be lost if Kura is restarted and/or the database instance is deleted. If Persisted is selected, the database will be stored on disk in the location specified by the Persisted Database Path parameter.

    • Persisted Database Path: Defines the path to the database file (it must include the database file name). This parameter is only relevant for persisted databases.

    • Encryption Key: Allows to specify a key/passphrase for encrypting the database file. This feature requires a SQLite binary with an encryption extension, and is only relevant for persisted databases. The key format can be specified using the Encryption Key Format parameter. If the value of this parameter is changed, the encryption key of the database will be updated accordingly. This parameter can be left empty to create an unencrypted database or to decrypt an encrypted one.

    Note

    The sqlite-jdbc version distributed with Kura does not contain any encryption extension, encryption features will not be available out of the box. See sqlite-jdbc documentaton for instructions about how to use a security extension.

    • Encryption Key Format: Allows to specify the format of the Encryption Key parameter value. The possible values are ASCII (an ASCII string), Hex SSE (the key is an hexadecimal string to be used with the SSE extension) or Hex SQLCipher (the key is an hexadecimal string to be used with the SQLCipher extension).

    • Journal Mode: The database journal mode. The following options are available:

      • Rollback Journal: The database instance will use the rollback journal for more details. More specifically, the DELETE argument will be passed to the pragma journal_mode command.

      • WAL: The database instance will use WAL mode. This is the default mode.

      The WAL mode description page contains a comparison of the two modes.

    • Defrag enabled: Enables or disables the database defragmentation. Use the Defrag Interval parameter to specify the interval.

    • Defrag interval (seconds): The implementation supports running periodic defragmentation using the VACUUM command. This parameter specifies the interval in minutes between two successive checkpoints, set to zero to disable. This parameter is only relevant for persisted databases.

    Warning

    The total disk space used by SQLite database files might temporarily increase during defragmentation and/or if transactions that modify a lot of data are performed (including deletion). In particular:

    • Defragmentation is implemented by copying all non-free database pages to a new file and then deleting the original file.
    • If transactions that involve a lot of data are performed, in case of rollback journal the old content of the modified pages will be stored in the journal until the transaction is completed, and in WAL mode the new content of modified pages will be stored to the WAL file until the next checkpoint is performed.

    It is recommended to perform some tests to determine the maximum database files size that will be used by the application and ensure that the size of the partition containing the database is at least twice as the expected db size.

    • Checkpoint enabled: Enables or disables checkpoints in WAL journal mode. Use the WAL Checkpoint Interval parameter to specify the interval.
    • WAL Checkpoint Interval (Seconds): The implementation supports running periodic periodic WAL checkpoints. Checkpoints will be performed in TRUNCATE mode. This parameter specifies the interval in seconds between two consecutive checkpoints, set to zero to disable. This parameter is only relevant for persisted databases in WAL Journal Mode. In WAL mode a checkpoint will be performed after a periodic defragmentation regardless of the value of this parameter.

    • Connection pool max size: The implementation manages connections using a connection pool. This parameter defines the maximum number of connections for the pool. If a new connection is requested

    • Delete Database Files On Failure: If set to true, the database files will be deleted in case of failure in opening a persisted database. This is intended as a last resort measure for keeping the database service operational, especially in the case when it is used as a cloud connection message store.

    • Debug Shell Access Enabled: Enables or disables the interaction with this database instance using the sqlitedbg:excuteQuery OSGi console command. (see #debug-shell)

    "},{"location":"core-services/sqlite-db-service/#selecting-a-database-instance-for-existing-components","title":"Selecting a database instance for existing components","text":"

    A database instance is identified by its Kura service PID. The PID for instances created using the Web UI is the string entered in the Name field at step 2 of the previous section.

    The built-in components that use database functionalities allow to specify which instance to use in their configuration. These components are the DataService component of the cloud stack, the Wire Record Store and Wire Record Query wire components. The configuration of each component contains a property that allows to specify the Kura Service PID of the desired instance.

    Warning

    Using SQLite database instances through the deprecated DbFilter and DbStore components is not supported.

    "},{"location":"core-services/sqlite-db-service/#usage-through-wires","title":"Usage through Wires","text":"

    It is possible to store and extract Wire Records into/from a SQLite database instance using the Wire Record Store and Wire Record Query wire components.

    When a Wire Record is received by a Wire Record Store attached to a SQLite based database instance, the data will be stored in a table whose name is the current value of the Record Collection Name configuration parameter of the Wire Component.

    Each property contained in a Wire Record will be appended to a column with the same name as the property key. A new column will be created if it doesn't already exists.

    Since it is not possible to establish a one to one mapping between SQLite storage classes and the data types available on the Wires, the implementation will assign a custom type name to the created columns in order to keep track of the inserted Wire Record property type.

    The custom types will be assigned according to the following table:

    Wires Data Type Column Type Name Storage Class Type Affinity BOOLEAN BOOLEAN INTEGER INTEGER INT INTEGER LONG BIGINT INTEGER FLOAT FLOAT REAL DOUBLE DOUBLE REAL STRING TEXT TEXT BYTE_ARRAY BLOB BLOB

    The custom column type makes it possible to preserve the original type when data is extracted with the Wire Record Query component. Please note that the resulting type may change in case of queries that build computed columns.

    Warning

    It is not recommended to store Wire Records having properties with the same key and different value type. If the value type changes, the target column will be dropped and recreated with the type derived from the last received record. All existing data in the target column will be lost. The purpose of this is to allow changing the type of a column with a Wire Graph configuration update.

    "},{"location":"core-services/sqlite-db-service/#debug-shell","title":"Debug shell","text":"

    It is possible to inspect the contents of the database file using the OSGi console with the following command:

    sqlitedbg:executeQuery dbServicePid 'query'\n

    or more simply

    executeQuery dbServicePid 'query'\n

    where dbServicePid is the user defined pid of the target database instance and query is an arbitrary SQL query to be executed (make sure to properly quote the query string with the ' character). The command will print the result set or changed row count on the console.

    It is only possible to access database instances whose Debug Shell Access Enabled configuration parameter is set to true. The default of this parameter is false.

    The OSGi console is disabled by default, it can be accessed by starting the framework with the /opt/eclipse/kura/bin/start_kura_debug.sh script and running the following command on the gateway:

    telnet localhost 5002\n

    Warning

    This feature is intended for debug purposes only, the Debug Shell Access Enabled parameter as well as the OSGi console should not be left enabled on production systems.

    "},{"location":"core-services/watchdog-service/","title":"Watchdog Service","text":"

    The WatchdogService provides methods for starting, stopping, and updating a hardware watchdog if it is present on the system. Once started, the watchdog must be updated to prevent the system from rebooting.

    To use this service, select the WatchdogService option located in the Services area as shown in the screen capture below.

    This service provides the following configuration parameters:

    • enabled - sets whether or not this service is enabled or disabled. (Required field)

    • pingInterval - defines the maximum time interval between two watchdogs' refresh to prevent the system from rebooting. (Required field)

    • Watchdog device path - sets the watchdog device path. (Required field)

    • Reboot Cause File Path - sets the path to the file that will contain the reboot cause information. (Required field)

    "},{"location":"gateway-configuration/authentication-and-authorization/","title":"Authentication and authorization","text":"

    Kura 5 introduces a centralized authentication and authorization framework based on the OSGi UserAdmin specification. This framework introduces the concepts of identities and permissions:

    • Identity: A Kura identity is related to authentication, an identity has a name and a set of associated credentials, for example a password.

    • Permission: A Kura permission is related to authorization. Zero or more permissions can be assigned to a given identity. Each permission allows to access a set of resources and/or perform certain operations. Permissions can be defined by applications.

    Examples of applications that use the new authentication and authorization framework are the Web Console and the REST API framework:

    • Kura Web Console provides multi user support, Web Console users are mapped to Kura identities, the Web Console also defines a set of permissions that allow to restrict the operations that a given identity is allowed to perform.
    • The Kura REST API framework users are now mapped to Kura identities and REST roles are mapped to Kura permissions. The old ConfigurationService based user and role definition mechanism has been dropped.

    The authentication and authorization framework only allows to define and store identities and permissions, it does not provide implementation of authentication methods and/or session management. These aspects are left to applications.

    "},{"location":"gateway-configuration/authentication-and-authorization/#permission-and-identity-representation","title":"Permission and identity representation","text":"

    Permissions and identities are implemented on top of the UserAdmin User and Group concepts. See OSGi UserAdmin specification for more details on the Role, User and Group concepts.

    "},{"location":"gateway-configuration/authentication-and-authorization/#identityservice-java-apis","title":"IdentityService Java APIs","text":"

    Kura 5.5 introduces a new set of Java APIs that allow to manage Kura identities, the implementation is based on the UserAdmin conventions described in this page, and allows to manipulate Kura identities without interacting directly with the UserAdmin. Please refer to the Javadoc for more details.

    The new APIs also provide the capability to implement and register IdentityConfigurationExtensions in the framework.

    An IdentityConfigurationExtension can define additional custom configuration parameters for each identity. The custom configuration can be inspected and modified in the Identities section of Kura Web UI and using the identity/v2 rest APIs and MQTT request handler. The IdentityConfigurationExtension implementation can store the additional configuration in the way that is most suitable for the application, for example by adding custom UserAdmin properties or credentials.

    "},{"location":"gateway-configuration/authentication-and-authorization/#identities","title":"Identities","text":"

    A Kura identity is represented as a UserAdmin User with the following properties:

    • The User name must be in the kura.user.${identity_name} form where ${identity_name} is a non empty string representing the identity name. The name of an User representing a Kura identity must start with the kura.user. prefix.

    • A password may be assigned to an identity by defining a property in User credentials with the following format:

    • The property name must be kura.password
    • The property value must be a string containing SHA256 hash of the password, as computed by the
      String org.eclipse.kura.crypto.CryptoService.sha256Hash(String)\n
      method.

    Starting from Kura 5.1, it is possible to force an identity to change the password at next login by setting the following property in User properties: - kura.need.password.change : true encoded as a JSON string. - The property will be cleared automatically after a successful password change on next login.

    Starting from Kura 5.5 the following restrictions will be applied by the IdentityService:

    • New Identity Names:
      • must be at least 3 and at most 255 characters long.
      • can only be composed by one or more sequences of alphanumeric characters ([A-Za-z0-9]+) separated by the dot or underscore symbols, dot and underscore is not allowed at the beginning or at the end of the permission name, sequences of consecutive dots and/or underscores are not allowed (examples of valid names are foo1.bAr, foo, a.b.c, foo.bar_baz).
    • New Passwords:
      • cannot be empty.
      • must satisfy the password strenght requirements configured on the system.
      • the maximum allowed length is 255 characters.
      • cannot contain whitespace characters.
    "},{"location":"gateway-configuration/authentication-and-authorization/#permissions","title":"Permissions","text":"

    A Kura permission is represented as a UserAdmin Group with the following properties:

    • The Group name must be in the kura.permission.${permission_name} form where ${permission_name} is a non empty string representing the permission name. The name of a Group representing a Kura permission must start with the kura.permission. prefix.

    • Assigning a permission to a specific identity can be done by adding the User representing the identity to the basic members of the Group representing the permission.

    Starting from Kura 5.5 the following restrictions will be applied by the IdentityService to the name new permissions:

    • must be at least 3 and at most 255 characters long.
    • can only be composed by one or more sequences of alphanumeric characters ([A-Za-z0-9]+) separated by the dot symbol, the dot is not allowed at the beginning or at the end of the permission name (examples of valid permission names are foo1.bAr, foo, a.b.c).
    "},{"location":"gateway-configuration/authentication-and-authorization/#useradmin-persistence","title":"UserAdmin persistence","text":"

    The org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl Kura service allows to persist the UserAdmin state in Kura configuration snapshot, this includes the defined identities and permissions.

    The following configuration properties are used to store the UserAdmin concepts in Json format:

    • Role configuration (id roles.config):

    Stores the UserAdmin Roles, plain Roles are non used for representing Kura identities and permissions. The value must be a JSON array of Role elements.

    • User configuration (id users.config):

    Stores the UserAdmin Users. The value must be a JSON array of User elements.

    • Group configuration (id groups.config):

    Stores the UserAdmin Groups. The value must be a JSON array of Group elements.

    "},{"location":"gateway-configuration/authentication-and-authorization/#json-representation-details","title":"JSON representation details","text":"

    This section describes the JSON format used in org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl configuration.

    "},{"location":"gateway-configuration/authentication-and-authorization/#useradmindictvalue","title":"UserAdminDictValue","text":"

    A value appearing in UserAdmin dictionaries like properties and credentials

    • type : variant Variants:

    • a String property

      • type : string
    • a byte[] property
      • type : array, element description: a byte of the array encoded as an unsigned integer
      • type : number

    Examples:

    \"A string property\"\n
    [1, 2, 3, 4, 5]\n
    "},{"location":"gateway-configuration/authentication-and-authorization/#useradmindict","title":"UserAdminDict","text":"

    A UserAdmin property dictionary, there are no well known property names, property values must be of UserAdminDictValue type

    • type : object

    Example:

    {\n  \"stringProperty\": \"A string property\",\n  \"byteArrayProperty\": [1, 2, 3, 4, 5]\n}\n
    "},{"location":"gateway-configuration/authentication-and-authorization/#role","title":"Role","text":"

    An object describing and UserAdmin Role

    • type : object Properties:

    • name The role name

    • type : string

    • properties

    • optional If the dictionary is empty
      • UserAdminDict
    "},{"location":"gateway-configuration/authentication-and-authorization/#user","title":"User","text":"

    An object describing and UserAdmin User

    • type : object Properties:

    • name The role name

    • type : string

    • properties

    • optional If the dictionary is empty

      • UserAdminDict
    • credentials

    • optional If the dictionary is empty
      • UserAdminDict

    Example:

    {\n  \"name\": \"kura.user.appadmin\",\n  \"credentials\": {\n    \"kura.password\": \"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM=\"\n  }\n}\n
    "},{"location":"gateway-configuration/authentication-and-authorization/#group","title":"Group","text":"

    An object describing and UserAdmin Group

    • type : object Properties:

    • name The role name

    • type : string

    • properties

    • optional If the dictionary is empty

      • UserAdminDict
    • credentials

    • optional If the dictionary is empty

      • UserAdminDict
    • basicMembers

    • optional If the list is empty The list of the group basic members

      • type : array, element description: A role name
      • type : string
    • requiredMembers

    • optional If the list is empty The list of the group required members
      • type : array, element description: A role name
      • type : string

    Example:

    {\n  \"name\": \"kura.permission.kura.wires.admin\",\n  \"basicMembers\": [\"kura.user.appadmin\"]\n}\n
    "},{"location":"gateway-configuration/cellular-configuration/","title":"Cellular Configuration","text":"

    If it is not configured, the cellular interface is presented on the interface list by modem USB address (i.e. 2-1). This 'fake' interface name is completed by 'proper' interface name (e.g., ppp0) when the first modem configuration is submitted.

    The cellular interface should be configured by first enabling it in the IPv4 or IPv6 tab, and then setting the Cellular tab. Note that the cellular interface can only be set as WAN using DHCP, Disabled or Not Managed (only for IPv4 connections). The cellular interface configuration options are described below.

    "},{"location":"gateway-configuration/cellular-configuration/#cellular-configuration_1","title":"Cellular Configuration","text":"

    The Cellular tab contains the following configuration parameters:

    • Model: specifies the modem model.

    • Network Technology: describes the network technology used by this modem.

      • HSDPA
      • EVDO
      • EDGE
    • Connection Type: specifies the type of connection to the modem.

    • Modem Identifier: provides a unique name for this modem.

    • Interface #: provides a unique number for the modem interface (e.g., an interface # of 0 would name the modem interface ppp0).

    • Dial String: instructs how the modem should attempt to connect. Typical dial strings are as follows:

      • HSPA modem: atd*99***1#
      • EVDO/CDMA modem: atd#777
    • APN: defines the modem access point name.

      This is an optional parameter. If left empty, the value is automatically picked up from the Mobile Broadband Provider the modem is registered to. If a value is filled, the APN value is explicitly configured.

      To avoid misconfiguration issues, it is strongly recommended to set it manually.

      Note

      APN value configuration

      A good practice is to set the interface status to Disabled and then Enable For WAN when the APN is explicitly set. NetworkManager, indeed, may fallback to the default value if a wrong APN is specified, causing misleading behaviors. This does not happen if the interface is disabled and re-enabled after APN changes.

    • Auth Type: specifies the authentication type.

      • None
      • Auto
      • CHAP
      • PAP
    • Username: supplies the username; disabled if no authentication method is specified.

    • Password: supplies the password; disabled if no authentication method is specified.

    • Modem Reset Timeout: sets the modem reset timeout in minutes. If set to a non-zero value, the modem is reset after n consecutive minutes of unsuccessful connection attempts. If set to zero, the modem keeps trying to establish a PPP connection without resetting. The default value is 5 minutes.

    • Reopen Connection on Termination: sets the persist option of the PPP daemon that specifies if PPP daemon should exit after connection is terminated. Note that the maxfail option still has an effect on persistent connections.

    • Connection Attempts Retry Delay: Sets the holdoff parameter to instruct the PPP daemon on how many seconds to wait before re-initiating the link after it terminates. This option only has any effect if the persist option (Reopen Connection on Termination) is set to true. The holdoff period is not applied if the link was terminated because it was idle. The default value is 1 second.

    • Connection Attempts: sets the maxfail option of the PPP daemon that limits the number of consecutive failed PPP connection attempts. The default value is 5 connection attempts. A value of zero means no limit. The PPP daemon terminates after the specified number of failed PPP connection attempts and restarts by the ModemMonitor thread.

    • Disconnect if Idle: sets the idle option of the PPP daemon, which terminates the PPP connection if the link is idle for a specified number of seconds. The default value is 95 seconds. To disable this option, set it to zero.

    • Active Filter: sets the active-filter option of the PPP daemon. This option specifies a packet filter (filter-expression) to be applied to data packets in order to determine which packets are regarded as link activity, and thereby, reset the idle timer. The filter-expression syntax is as described for tcpdump(1); however, qualifiers that do not apply to a PPP link, such as ether and arp, are not permitted. The default value is inbound. To disable the active-filter option, leave it blank.

    • LCP Echo Interval: sets the lcp-echo-interval option of the PPP daemon. If set to a positive number, the modem sends LCP echo request to the peer at the specified number of seconds. To disable this option, set it to zero. This option may be used with the lcp-echo-failure option to detect that the peer is no longer connected.

    • LCP Echo Failure: sets the lcp-echo-failure option of the PPP daemon. If set to a positive number, the modem presumes the peer to be dead if a specified number of LCP echo-requests are sent without receiving a valid LCP echo-reply. To disable this option, set it to zero.

    "},{"location":"gateway-configuration/cellular-configuration/#gps","title":"GPS","text":"

    The GPS tab allows the user to enable or disable the GPS module provided by the cellular modem. The available properties are:

    • Enable GPS: enables GPS module for the selected modem.
    • GPS Mode: specifies the GPS mode.
      • UNMANAGED: the GPS device of the modem will be setup but not directly managed, therefore freeing the serial port for other services to use. This can be used in order to perform the setup of the GPS and then have another service (like gpsd) parse the NMEA strings in order to extract the position informations.
      • MANAGED_GPS: the GPS device of the modem will be setup and directly managed (typically by ModemManager) therefore the serial port won't be available for other services to use.

    GPS modes availability

    GPS modes available for the modem are dependent on the modem model, modem firmware version and ModemManager version installed on the system. Some modes may not be selectable if the modem does not support them.

    Therefore, to use the GPS module provided by the cellular modem with Kura's PositionService, the following considerations should be taken into account:

    • The PositionService should be enabled. Serial settings of the PositionService should not be changed; it will be redirected to the modem GPS port automatically.
    • To use the gpsd and serial PositionService providers with the GPS module provided by the cellular modem, the GPS mode should be set to UNMANAGED.
    • To use the modemmanager PositionService provider with the GPS module provided by the cellular modem, the GPS mode should be set to MANAGED_GPS.

    Refer to the Position Service section for more information.

    "},{"location":"gateway-configuration/cloud-connections/","title":"Cloud Connections","text":"

    The Cloud Connections section of the Kura Gateway Administration Console allows to create and manage cloud connections.

    By default, Kura starts with a single cloud connection, as depicted in the following image:

    The cloud services page allows to: - create a new cloud connection; - delete an existing cloud connection; - connect a selected cloud stack to the configured cloud platform; - disconnect the selected cloud stack from the connected cloud platform; - refresh the existing cloud connections.

    When clicking on the New button, a dialog is displayed as depicted in the image below:

    The user can select one of the existing cloud connection factories and give it a name (depending on the implementation, a name format can be suggested or forced).

    Selecting a created Cloud Connection it is possible to associate a new publisher/subscriber by clicking the New Pub/Sub button. As for the connection creation case, the user can select one of the existing publisher/subscriber factories and give it a name.

    "},{"location":"gateway-configuration/cloud-service-configuration/","title":"Cloud Service Configuration","text":"

    The CloudService provides an easy-to-use API layer for the M2M application to communicate with a remote server. It operates as a decorator for the DataService, providing add-on features over the management of the DataTransport layer.

    In addition to simple publish/subscribe, the Cloud Connection API simplifies the implementation of more complex interaction flows like request/response or remote resource management. The Cloud Connection abstracts the developers from the complexity of the transport protocol and payload format used in the communication.

    The Cloud Connection allows a single connection to a remote server to be shared across more than one application in the gateway, providing the necessary topic partitioning. Its functions include:

    • Adds application topic prefixes to allow a single remote server connection to be shared across applications.

    • Defines a payload data model and provides default encoding/decoding serializers.

    • Publishes life-cycle messages when the device and applications start and stop.

    To use this service, select the CloudService option located in the Cloud Services area as shown in the screen capture below.

    The CloudService provides the following configuration parameters:

    • device.display-name: defines the device display name given by the system. (Required field).

    • device.custom-name: defines the custom device display name if the device.display-name parameter is set to \"Custom\".

    • topic.control-prefix: defines the topic prefix used for system and device management messages.

    • encode.gzip: defines if the message payloads are sent compressed.

    • republish.mqtt.birth.cert.on.gps.lock: when set to true, forces a republish of the MQTT Birth Certificate when a GPS correct position lock is received. The device is then registered with its real coordinates. (Required field).

    • enable.default.subscriptions: manages the default subscriptions to the gateway management MQTT topics. When disabled, the gateway will not be remotely manageable.

    • payload.encoding: specifies the encoding for the messages sent by the specific CloudService instance.

      • Kura Protobuf - when this option is selected, the Kura Protobuf encoding will be used
      • Simple JSON - the simple JSON encoding will be used instead. More information is available here. An example below.
    {\n\"sentOn\" : 1491298822,\n\"position\" : {\n    \"latitude\" : 45.234,\n    \"longitude\" : -7.3456,\n    \"altitude\" : 1.0,\n    \"heading\" : 5.4,\n    \"precision\" : 0.1,\n    \"speed\" : 23.5,\n    \"timestamp\" : 1191292288,\n    \"satellites\" : 3,\n    \"status\" : 2\n},\n\"metrics\": {\n    \"code\" : \"A23D44567Q\",\n    \"distance\" : 0.26456E+4,\n    \"temperature\" : 27.5,\n    \"count\" : 12354,\n    \"timestamp\" : 23412334545,\n    \"enable\" : true,\n    \"rawBuffer\" : \"cGlwcG8gcGx1dG8gcGFwZXJpbm8=\"\n},\n\"body\": \"UGlwcG8sIHBsdXRvLCBwYXBlcmlubywgcXVpLCBxdW8gZSBxdWEu\"\n}\n

    The default CloudService implementations publishes the following lifecycle messages:

    1. BIRTH message: sent immediately when device is connected to the cloud platform;
    2. DISCONNECT message: sent immediately before device is disconnected from the cloud platform;
    3. delayed BIRTH message: sent when new cloud application handler becomes available, a DP is installed or removed, or when the GPS position is locked (can be disabled). These messages are cached for 30 seconds before sending. If no other message of such type arrives the message is sent; otherwise the BIRTH is cached and the timeout restarts. This is to avoid sending multiple messages when the framework starts.
    "},{"location":"gateway-configuration/data-service-configuration/","title":"Data Service Configuration","text":"

    The DataService provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. The DataService delegates to the MqttDataTransport service the implementation of the transport protocol that is used to interact with the remote server.

    The DataService also adds the capability of storing published messages in a persistent store function and sending them over the wire at a later time. The purpose of this feature is to relieve service users from implementing their own persistent store. Service users may publish messages independently on the DataService connection status.

    Info

    Starting from Kura 5.3.0, the DataService allows to bind to custom persistent stores. A custom persistent store can be defined by creating an implementation the new org.eclipse.kura.message.store.provider.MessageStoreProvider service interface and registering it as an OSGi service.

    In order to overcome the potential latencies introduced by buffering messages, the DataService allows a priority level to be assigned to\u200b each published message. Depending on the store configuration, there are certain guarantees that stored messages are not lost due to sudden crashes or power outages.

    To use this service, select the DataService option located in the Cloud Connections area as shown in the screen capture below.

    The DataService offers methods and configuration options to manage the connection to the remote server including the following (all required) parameters described below.

    • Connect Auto-on-startup - When set to true, the service tries to auto-connect to the remote server on start-up and restore the connection every time the device is disconnected. These attempts are made at the frequency defined in the Connect Retry-interval parameter until the connection is established. Using the Connect/Disconnect button disables this function.
    • Connect Retry-interval - Frequency in seconds to retry a connection of the Data Publishers after a disconnect.
    • Enable Recovery On Connection Failure - Enables the recovery feature on connection failure. If the device is not able to connect to a remote cloud platform, the service will wait for a specified amount of connection retries. If the recovery fails, the device will be rebooted. Being based on the Watchdog service, it needs to be activated as well.
    • Connection Recovery Max Failure - Number of failures in Data Publishers connection before forcing a reboot.
    • Disconnect Quiesce-timeout - Allows the delivery of in-flight messages to be completed before disconnecting from the broker when a disconnection from the broker is being forced.
    • Message Store Provider Service PID - The Kura service pid of the Message Store instance to be used. The pid of the default instance is org.eclipse.kura.db.H2DbService. The Message Store instance must implement the org.eclipse.kura.message.store.provider.MessageStoreProvider interface.
    • Store Housekeeper-interval - Defines the interval in seconds used to run the Data Store housekeeper task.
    • Store Purge-age - defines the age in seconds of completed messages (either published with QoS = 0 or confirmed with QoS > 0) after which they are deleted (minimum 5).
    • Store Capacity - Defines the maximum number of messages persisted in the Data Store.
    • In-flight-messages parameters - Define the management of messages that have been published and not yet confirmed, including:
    • In-flight-messages Republish-on-new-session - Whether to republish in-flight messages on a new MQTT session.
    • In-flight-messages Max-number - The maximum number of in-flight messages.
    • In-flight-messages Congestion-timeout - Timeouts the in-flight messages congestion condition. The service will force a disconnect attempting to reconnect (0 to disable).
    "},{"location":"gateway-configuration/data-service-configuration/#connection-monitors","title":"Connection Monitors","text":"

    The DataService offers methods and configuration options to monitor the connection to the remote server and, eventually, cause a system reboot to recover from transient network problems.

    This feature, if enabled, leverages the watchdog service and reboots the gateway if the maximum number of configured connection attempts has been made.

    A reboot is not requested if the connection to the remote broker succeeds but an authentication error, an invalid client id or an authorization error is thrown by the remote cloud platform and causes a connection drop.

    The image below shows the parameters that need to be tuned in order to enable this connection monitor feature.

    To configure this functionality, the System Administrator needs to specify the following configuration elements:

    • Enable Recovery On Connection Failure: when enabled, activates the recovery feature on connection failure: if the device is not able to connect to a remote cloud platform, the service will wait for a specified amount of connection retries. If the recovery fails, the device will be rebooted. Being based on the Watchdog service, it needs to be activated as well.

    • Connection Recovery Max Failure: related to the previous parameter. It specifies the number of failures before a reboot is requested. !!! warning To be fully working, this feature needs the enabling of the Watchdog Service.

    "},{"location":"gateway-configuration/data-service-configuration/#message-publishing-backoff-delay","title":"Message Publishing Backoff Delay","text":"

    In order to have a finer control on the data flow, when a device reconnects to a remote cloud platform, Kura integrates into the Data Service a Backoff delay feature that limits the rate of messages sent.

    This feature, enabled by default, integrates the Token Bucket concept to limit the bursts of messages sent to a remote cloud platform.

    In the image below, the parameters that need to be tuned, in the Data Service, to take advantage of this feature:

    • Enable Rate Limit - Enables the token bucket message rate limiting.
    • Rate Limit Average - he average message publish rate in number of messages per unit of time (e.g. 10 messages per MINUTE).

      Danger

      The maximum allowed message rate is 1 message per millisecond, so the following limitations are applied:

      • 86400000 per DAY
      • 3600000 per HOUR
      • 60000 messages per MINUTE
      • 1000 messages per SECOND
      • Rate Limit Time Unit - The time unit for the Rate Limit Average.
      • Rate Limit Burst Size - The token bucket burst size.

    The default setup limits the data flow to 1 message per second with a bucket size of 1 token.

    Warning

    This feature needs to be properly tuned by the System Administrator in order to prevent delays in the remote cloud platform due to messages stacked at the edge.

    If not sure of the number of messages that your gateways will try to push to the remote platform, we suggest to disable this feature.

    "},{"location":"gateway-configuration/data-service-configuration/#connection-schedule","title":"Connection schedule","text":"

    Starting from Kura 5.3.0, the Data Service supports a configurable time based connection schedule. If this functionality is enabled, the Data Service will connect at specific time instants represented by a configurable Cron expression, keep the connection open until it becomes idle and then disconnect until the next instant that matches the expression.

    More in detail, the connection logic is as follows:

    1. The DataService parses the confgiured Cron expression and schedules a connection attempt at the next instant that matches the expression. When the connection instant is reached, the logic continues from step 2.

    2. The Data Service will start the auto connect logic. One or more connection attempts will be performed until the connection is established honoring the connect.retry-interval parameter.

    3. The Data Service starts a timer that ticks after connection.schedule.inactivity.interval.seconds seconds. When the timer ticks the connection will be closed, and the logic resumed from step 1. The timer is reset delaying the disconnection when a message is published or confirmed (for QoS >= 1). The connection will not be closed if there are messages with QoS >= 1 that have not been confirmed yet. If an unexpected connection drop occurs in this phase, the logic will resume from step 2.

    The Data Service will attempt to detect large time shifts in system clock, if a time shift is detected, the logic will switch to step 1, rescheduling the next connection attempt.

    The relevant configuration parameters are the following:

    • Enable Connection Schedule: Enables or disables the connection schedule feature. Please note that in order to enable the connection logic, the Connect Auto-on-startup parameter must be set to true as well.

    • Connection Schedule CRON Expression: A CRON expression that specifies the instants when the gateway should perform a connection attempt. This parameter is only used if Enable Connection Schedule is set to true. The default expression schedules a connection every day at midnight.

    • Allow priority message to overide connection schedule - Allows messages beyond a specified priority to force a connection and be sent regardless of connection schedule.

    • Message schedule priority override threshold - A message with a priority equal to or less than this threshold will cause the framework to automatically re-connect and send regardless of the connection schedule.

    • Connection Schedule Disconnect Inactivity Interval Second: Specifies an inactivity timeout in seconds. If the timeout expires, the cloud connection will be automatically closed. This parameter is only used if Enable Connection Schedule is set to true.

    "},{"location":"gateway-configuration/data-service-configuration/#payload-size-limiting","title":"Payload size limiting","text":"

    Starting from Kura 5.3.0, the DataService allows to limit the maximum payload size for published messages. Attempts to publish a payload larger than the configured threshold will fail with a KuraStoreException. The threshold can be configured with the following parameter:

    • Maximum Payload Size: The maximum allowed size in bytes for the message payload.

    In order to keep backwards compatibility, the default value for the parameter is set to 16777216 bytes, which is the maximum allowed size by the current H2DbService implementation.

    "},{"location":"gateway-configuration/data-transport-service-configuration/","title":"Data Transport Service Configuration","text":"

    The DataTransport service provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. To use this service, select the MqttDataTransport option located in the Cloud Connections area as shown in the screen captures below.

    The MqttDataTransport service provides the following configuration parameters:

    • broker-url: defines the URL of the MQTT broker to connect to. For the Everyware Cloud sandbox, this address is either mqtt://broker-sbx.everyware.io:1883/ or mqtts://broker-sbx.everyware.io:8883/ for an encrypted connection. (Required field).

    • topic.context.account-name: defines the name of the account to which the device belongs.

    • username and password: define the username and password that have been assigned to the device by the account administrator (generally username is account-name_broker). (Required field).

    • client-id: defines the identifier of the MQTT client representing the device when connecting to the MQTT broker. If left empty, it is automatically determined by the client software as the MAC address of the main network interface (in general numbers and uppercase letters without ':'). This identifier has to be unique within your account.

    • keep-alive: defines the \"keep alive\" interval measured in seconds. It specifies the maximum amount of time that should pass without communication between the client and the server. The client will ensure that at least one message travels across the network within each keep alive period. In the absence of a data-related message during this time period, the client will send a very small MQTT \"ping\" message that the server will acknowledge. The keep alive interval enables the client to detect when the server is no longer available without having to wait for the long TCP/IP timeout. (Required field).

      Warning

      The keep-alive interval may \"conflict\" with the TCP idle timeout set at the TCP/IP level. As a best practice the TCP idle timeout should be at least 1,5 times the keep-alive time interval. If the TCP idle timeout is less or equal the keep-alive, the MQTT connection may be dropped due to the TCP idle timeout expiration.

    • timeout: sets the timeout used for all interactions with the MQTT broker. (Required field).

    • clean-session: controls the behavior of both the client and the server at the time of connection and disconnection. When this parameter is set to true, the state information is discarded at connection and disconnection; when set to false, the state information is maintained. (Required field).

    • lwt parameters: define the MQTT \"Last Will and Testament\" (LWT) settings for the client. In the event that the client unexpectedly loses its connection to the server, the server publishes the LWT message (lwt.payload) to the LWT topic on behalf of the client. This allows other clients (subscribed to the LWT topic) to be made aware that the client has disconnected. LWT parameters that may be configured include:

      • lwt.topic
      • lwt.payload
      • lwt.qos
      • lwt.retain
    • in-flight.persistence: defines the storage type where in-flight messages are persisted across reconnections. They may be stored in memory, or in a file on the disk. (Required field).

    • protocol-version: defines the MQTT Protocol version to be used. This value may be 3.1 or 3.1.1.

    • SSL parameters: define the SSL specific settings for the client. SSL parameters that can be configured include:

      • ssl.default.protocol
      • ssl.hostname.verification
      • ssl.default.cipherSuites
      • ssl.certificate.alias
    "},{"location":"gateway-configuration/device-information/","title":"Device Information","text":"

    The Device section provides several information about the gateway where Kura is running on. This section can be accessed by selecting the Device option located in the System area.

    "},{"location":"gateway-configuration/device-information/#profile","title":"Profile","text":"

    The Profile tab shows several information about the gateway, organized under the Device, Hardware, Software and Java Information.

    "},{"location":"gateway-configuration/device-information/#bundles","title":"Bundles","text":"

    This tab lists all the bundles installed on Kura, with details about the name, version, id, state and signature status. The signature value will be true if the corresponding bundle is signed, false otherwise.

    The buttons in the upper part of the tab allows the user to manage the listed bundles:

    • Start Bundle: starts a bundle that is in Resolved or Installed state;
    • Stop Bundle: stops a bundle that is in Active state;
    • Refresh: reloads the bundles states list.

    "},{"location":"gateway-configuration/device-information/#containers","title":"Containers","text":"

    The Containers tab lists the containers and images that are currently managed by the Container Orchestration Service. From this tab, the user can start and stop containers and delete images.

    "},{"location":"gateway-configuration/device-information/#threads","title":"Threads","text":"

    The Threads tab shows a list of the threads that are currently running in the JVM.

    "},{"location":"gateway-configuration/device-information/#system-packages","title":"System Packages","text":"

    The System Packages tab shows the list of all the Linux packages installed on the OS. The package is detailed with the name, version and type (DEB/RPM/APK).

    "},{"location":"gateway-configuration/device-information/#system-properties","title":"System Properties","text":"

    The System Properties tab shows a list of relevant properties including OS and JVM parameters.

    "},{"location":"gateway-configuration/device-information/#command","title":"Command","text":"

    A detailed description of this tab is presented in the Command Service page.

    "},{"location":"gateway-configuration/device-information/#system-logs","title":"System Logs","text":"

    The System Logs tab allows downloading a compressed file containing all the relevant log files from the gateway. The download button creates and downloads a compressed file with the following items:

    • all the files in /var/log or the content of the folder defined by the kura.log.download.sources property;
    • the content of the journal for the Kura process (kura-journal.log);
    • the content of the journal for the whole system (system-journal.log).

    In addition to this feature, the page also allows the real-time displaying of system logs, if the framework has the availability of one or more components that implement the LogProvider API. The UI also provides a useful button to open a new Kura instance in a new browser window. A reference implementation of the LogProvider API is provided in the org.eclipse.kura.log.filesystem.provider bundle. This bundle exposes in the framework a factory that can be used to read filesystem files. By default, Eclipse Kura creates two log providers at startup: one that reads from /var/log/kura.log and the other that reads from /var/log/kura-audit.log.

    The device logs are stored in a server-side cache and are collected from the point in time where the log providers get attached to the UI (usually, from the login or after a refresh of the browser's window). When the section \"System Logs\" is accessed, the new log entries are polled from the server's cache and stored client-side.

    Note

    The default log provider, that is the first log provider shown in the dropdown menu, is filesystem-kura-log. It is used to collect logs from the /var/log/kura.log file. The default value can be changed using the kura.default.log.manager property in the kura.properties file.

    "},{"location":"gateway-configuration/ethernet-configuration/","title":"Ethernet Configuration","text":"

    As described in the Network Configuration section, Ethernet interfaces have four configuration tabs: IPv4, IPv6, DHCPv4 & NAT and Advanced. Each Ethernet interface may be configured either as LAN or WAN; it may also be disabled.

    If the interface is designated as LAN in the IPv4 tab and is manually configured, the DHCPv4 & NAT tab is enabled to allow DHCP server and/or 'many-to-one' NAT setup; otherwise, the DHCPv4 & NAT tab is disabled.

    For more information on IPv4, IPv6 and DHCPv4 & NAT settings, please refer to the Network Configuration section. For the advanced settings, see the Advanced Settings section.

    "},{"location":"gateway-configuration/firewall-configuration/","title":"Firewall Configuration","text":"

    Kura offers easy management of the Linux firewall iptables and ip6tables included in an IoT Gateway. Additionally, Kura provides the ability to manage network access security to an IoT Gateway for both IPv4 and IPv6 through the following:

    • Open Ports (local service rules)
    • Port Forwarding
    • IP Forwarding and Masquerading (NAT service rules)
    • 'Automatic' NAT service rules

    Open Ports, Port Forwarding, and IP Forwarding and Masquerading are configured via respective Firewall configuration tabs. 'Automatic' NAT is enabled for each local (LAN) interface using the DHCP & NAT tab of the respective interface configuration. While the IPv4 Firewall configuration capability is present on all IoT Gateways, the IPv6 Firewall is present only on devices that support IPv6 Networking.

    "},{"location":"gateway-configuration/firewall-configuration/#firewall-linux-configuration","title":"Firewall Linux Configuration","text":"

    This section describes the changes applied by Kura at the Linux networking configuration. Please read the following note before proceeding with manual changes of the Linux networking configuration.

    Warning

    It is NOT recommended performing manual editing of the Linux networking configuration files when the gateway configuration is being managed through Kura. While Linux may correctly accept manual changes, Kura may not be able to interpret the new configuration resulting in an inconsistent state.

    When a new firewall configuration is submitted, Kura immediately applies it using the iptables service provided by the OS. Moreover, the rules are stored in the filesystem and a new Kura snapshot is generated containing the new configuration. At the next startup, the firewall service in the OS will re-apply them and Kura will check the firewall configuration against the one contained in the last snapshot. In this way, the user can update the snapshot with the needed rules and apply them to the system using the webUI or modify the snapshot_0.xml before the first start of Kura.

    In order to allow a better coexistence between Kura and external applications that need to modify firewall rules, Kura writes its rules to a set of custom iptables chains. They are input-kura, output-kura, forward-kura, forward-kura-pf and forward-kura-ipf for the filter table and input-kura, output-kura, prerouting-kura, prerouting-kura-pf, postrouting-kura, postrouting-kura-pf and postrouting-kura-ipf for the nat table. The custom chains are then put in their respective standard iptables chains, as shown in the following:

    iptables -t filter -I INPUT -j input-kura\niptables -t filter -I OUTPUT -j output-kura\niptables -t filter -I FORWARD -j forward-kura\niptables -t filter -I forward-kura -j forward-kura-pf\niptables -t filter -I forward-kura -j forward-kura-ipf\niptables -t nat -I PREROUTING -j prerouting-kura\niptables -t nat -I prerouting-kura -j prerouting-kura-pf\niptables -t nat -I INPUT -j input-kura\niptables -t nat -I OUTPUT -j output-kura\niptables -t nat -I POSTROUTING -j postrouting-kura\niptables -t nat -I postrouting-kura -j postrouting-kura-pf\niptables -t nat -I postrouting-kura -j postrouting-kura-ipf\n

    The same custom chains are used in the IPv6 case.

    Even if many firewall rules can be handled by Kura, it could be that some rules cannot be filled through the Web Console. In this case, custom firewall rules may be added to the /etc/init.d/firewall_cust script manually. These rules are applied/reapplied every time the firewall service starts, that is at the gateway startup. These custom rules should not be applied to the Kura custom chains, but to the standard ones.

    "},{"location":"gateway-configuration/firewall-configuration/#open-ports","title":"Open Ports","text":"

    If Kura is running on a gateway, all TCP/UDP ports are closed by default unless they are listed in the Open Ports IPv4 or Open Ports IPv6 tab of the Firewall section in the Gateway Administration Console, or in the /etc/sysconfig/iptables script. Therefore, if a user needs to connect to a specific port on a gateway, it is insufficient to have an application listening on the desired port; the port also needs to be opened in the firewall.

    To open a port using the Gateway Administration Console, select the Firewall option located in the System area. The Firewall configuration display appears in the main window. With the Open Ports IPv4 or Open Ports IPV6 tab selected, click the New button. The New Open Port Entry form appears.

    The New Open Port Entry form contains the following configuration parameters:

    • Port: specifies the port to be opened. (Required field.)
    • Protocol: defines the protocol (tcp or udp). (Required field.)
    • Permitted Network: only allows packets originated by a host on this network.
    • Permitted Interface Name: only allows packets arrived on this interface.
    • Unpermitted Interface Name: blocks packets arrived on this interface.
    • Permitted MAC Address: only allows packets originated by this host.
    • Source Port Range: only allows packets with source port in the defined range.

    Complete the New Open Port Entry form and click the Submit button when finished. Once the form is submitted, a new port entry will appear. Click the Apply button for the change to take effect.

    The firewall rules related to the open ports section are stored in the input-kura custom chain of the IPv4/IPv6 filter table.

    "},{"location":"gateway-configuration/firewall-configuration/#port-forwarding","title":"Port Forwarding","text":"

    Port forwarding rules are needed to establish connectivity from the WAN side to a specific port on a host that resides on a LAN behind the gateway. In this case, a routing solution may be avoided since the connection is made to a specified external port on a gateway, and packets are forwarded to an internal port on the destination host; therefore, it is not necessary to add the external port to the list of open ports.

    To add a port forwarding rule, select the Port Forwarding IPv4 or Port Forwarding IPv6 tab on the Firewall display and click the New button. The Port Forward Entry form appears.

    The Port Forward Entry form contains the following configuration parameters:

    • Input Interface: specifies the interface through which a packet is going to be received. (Required field).

    • Output Interface: specifies the interface through which a packet is going to be forwarded to its destination. (Required field).

    • LAN Address: supplies the IP address of destination host. (Required field).

    • Protocol: defines the protocol (tcp or udp). (Required field).

    • External Port: provides the external destination port on gateway unit. (Required field).

    • Internal Port: provides the port on a destination host. (Required field).

    • Enable Masquerading: defines whether masquerading is used (yes or no). If enabled, the gateway replaces the IP address of the originating host with the IP address of its own output (LAN) interface. This is needed when the destination host does not have a back route to the originating host (or default gateway route) via the gateway unit. The masquerading option is provided with port forwarding to limit gateway forwarding only to the destination port. (Required field).

    • Permitted Network: only forwards if the packet is originated from a host on this network.

    • Permitted MAC Address: only forwards if the packet is originated by this host.

    • Source Port Range: only forwards if the packet's source port is within the defined range.

    Complete the Port Forward Entry form and click the Apply button for the desired port forwarding rules to take effect.

    The firewall rules related to the port forwarding section are stored in the forward-kura-pf custom chain of the IPv4/IPv6 filter table and in the postrouting-kura-pf and prerouting-kura-pf chains of the IPv4/IPv6 nat table.

    "},{"location":"gateway-configuration/firewall-configuration/#port-forwarding-example","title":"Port Forwarding example","text":"

    This section describes an example of port forwarding rules applied to the IPv4 case. The initial setup is described below.

    • A couple of RaspberryPi that shares the same LAN over Ethernet.

    • The first RaspberryPi running Kura is configured as follows:

      • The eth0 interface static with IP address of 172.16.0.5.
      • There is no default gateway.
    • The second RaspberryPi running Kura is configured as follows:

      • The eth0 interface LAN/static with IP address of 172.16.0.1/24 and no NAT.
      • The wlan0 interface is WAN/DHCP client.
    • A laptop is connected to the same network of the wlan0 of the second RaspberryPi and can ping its wlan0 interface.

    The purpose of the second RaspberryPi configuration is to enable access to the Administration Console running on the first one (port 80) by connecting to the second RaspberryPi's port 8080 over the wlan. This scenario assumes that IP addresses are assigned as follows:

    • Second RaspberryPi wlan0 - 10.200.12.6

    • Laptop wlan0 - 10.200.12.10

    The following port forwarding entries are added to the second RaspberryPi configuration as described above using the Port Forward Entry form:

    • Input Interface - wlan0

    • Output Interface - eth0

    • LAN Address - 172.16.0.5

    • Protocol - tcp

    • External Port - 8080

    • Internal Port - 80

    • Masquerade - yes

    The Permitted Network, Permitted MAC Address, and Source Port Range fields are left blank.

    The following iptables rules are applied and added to the /etc/sysconfig/iptables file:

    iptables -t nat -A prerouting-kura-pf -i wlan0 -p tcp -s 0.0.0.0/0 --dport 8080 -j DNAT --to 172.16.0.5:80\niptables -t nat -A postrouting-kura-pf -o eth0 -p tcp -d 172.16.0.5 -j MASQUERADE\niptables -A forward-kura-pf -i wlan0 -o eth0 -p tcp -s 0.0.0.0/0 --dport 80 -d 172.16.0.5 -j ACCEPT\niptables -A forward-kura-pf -i eth0 -o wlan0 -p tcp -s 172.16.0.5 -m state --state RELATED,ESTABLISHED -j ACCEPT\n

    The following iptables commands may be used to verify that the new rules have been applied:

    sudo iptables -v -n -L\nsudo iptables -v -n -L -t nat\n

    At this point, it is possible to try to connect to http://10.200.12.6 and to http://10.200.12.6:8080 from the laptop. Note that when a connection is made to the device on port 80, it is to the Kura configuration page on the device itself (the second RaspberryPi). When the gateway is connected on port 8080, you are forwarded to the Kura Gateway Administration Console on the first RaspberryPi. The destination host can only be reached by connecting to the gateway on port 8080.

    Another way to connect to the Kura Gateway Administration Console on the first RaspberryPi would be to add an IP Forwarding/Masquerading entry as described in the next section.

    "},{"location":"gateway-configuration/firewall-configuration/#ip-forwardingmasquerading","title":"IP Forwarding/Masquerading","text":"

    The advantage of the Automatic NAT method is its simplicity. However, this approach does not handle reverse NATing, and it cannot be used for interfaces that are not listed in the Gateway Administration Console (such as VPN tun0 interface). To set up generic (one-to-many) NATing, select the IP Forwarding/Masquerading IPv4 or IP Forwarding/Masquerading IPv6 tab on the Firewall display. Press the New button and the IP Forwarding/Masquerading form appears.

    The IP Forwarding/Masquerading form contains the following configuration parameters:

    • Input Interface: specifies the interface through which a packet is going to be received. (Required field).

    • Output Interface: specifies the interface through which a packet is going to be forwarded to its destination. (Required field).

    • Protocol: defines the protocol of the rule to check (all, tcp, or udp). (Required field).

    • Source Network/Host: identifies the source network or host name (CIDR notation). Set to IPv4 0.0.0.0/0 or IPv6 ::/0 if empty.

    • Destination Network/Host: identifies the destination network or host name (CIDR notation). Set to IPv4 0.0.0.0/0 or IPv6 ::/0 if empty.

    • Enable Masquerading: defines whether masquerading is used (yes or no). If set to 'yes', masquerading is enabled. If set to 'no', only FORWARDING rules are be added. (Required field).

    The rules will be added to the forward-kura-ipf chain in the IPv4/IPv6 filter table and in the postrouting-kura-ipf one in the IPv4/IPv6 nat table.

    As a use-case scenario, consider the same setup as in port forwarding, but with cellular interface disabled and eth1 interface configured as WAN/DHCP client. In this case, the interfaces of the gateway are configured as follows:

    • eth0: LAN/Static/No NAT 172.16.0.1/24

    • eth1: WAN/DHCP 10.11.5.4/24

    To reach the gateway sitting on the 172.16.0.5/24 from a specific host on the 10.11.0.0/16 network, set up the following Reverse NAT entry:

    • Input Interface: eth1 (WAN interface)

    • Output Interface: eth0 (LAN interface)

    • Protocol: all

    • Source Network/Host: 10.11.5.21/32

    • Destination Network/Host: 172.16.0.5/32

    • Enable Masquerading: yes

    This case applies the following iptables rules:

    iptables -t nat -A postrouting-kura-ipf -p tcp -s 10.11.5.21/32 -d 172.16.0.5/32 -o eth0 -j MASQUERADE\niptables -A forward-kura-ipf -p tcp -s 172.16.0.5/32 -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A forward-kura-ipf -p tcp -s 10.11.5.21/32 -d 172.16.0.5/32 -i eth1 -o eth0 -m tcp -j ACCEPT\n

    Additionally, a route to the 172.16.0.0/24 network needs to be configured on a connecting laptop as shown below:

    sudo route add -net 172.16.0.0 netmask 255.255.255.0 gw 10.11.5.4\n

    Since masquerading is enabled, there is no need to specify the back route on the destination host. Note that with this setup, the gateway only forwards packets originating on the 10.11.5.21 laptop to the 172.16.0.5 destination.

    If the Source Network/Host and Destination Network/Host fields are empty, iptables rules appear as follows:

    iptables -t nat -A postrouting-kura-ipf -p tcp -s 0.0.0.0/0 -d 0.0.0.0/0 -o eth0 -j MASQUERADE\niptables -A forward-kura-ipf -p tcp -s 0.0.0.0/0 -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A forward-kura-ipf -p tcp -s 0.0.0.0/0 -d 0.0.0.0/0 -i eth1 -o eth0 -j ACCEPT\n

    The gateway forwards packets from any external host (connected to eth1) to any destination on the local network (eth0 interface).

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/","title":"Gateway Administration Console Authentication","text":"

    The Gateway Administration Console supports multiple login identities with associated permissions and HTTPS client side authentication with certificates.

    The identity and permission configuration and credentials is stored externally the UserAdmin service (see Authentication and Authorization for more details).

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#permissions","title":"Permissions","text":"

    The Gateway Administration Console defines the following permissions, that allow to restrict the operations that an identity is allowed to perform:

    • kura.cloud.connection.admin: Allows to manage cloud connections using Cloud Connections tab.
    • kura.packages.admin: Allows to install deployment packages using the Packages tab.
    • kura.device: Allows to interact with the Device and Status tabs.
    • kura.network.admin: Allows to manage network connectivity and firewall configuration using the Network and Firewall tabs.
    • kura.wires.admin: Allows to manage Wire Graph and Driver and Asset configurations using the Wires and Drivers and Assets tabs.
    • kura.admin: This permission implies all other permissions, including the ones defined by external applications.
    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#default-identities","title":"Default identities","text":"

    Kura provides the following identities by default:

    Name Password Permissions admin admin kura.admin appadmin appadmin kura.cloud.connection.admin, kura.packages.admin, kura.wires.admin netadmin netadmin kura.cloud.connection.admin, kura.device, kura.network.admin

    It is possible to modify/remove the default identity configuration.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#login","title":"Login","text":"

    The login screen can be accessed by entering the https://${device.ip} URL in a browser window, where ${device.ip} is the IP address. Replace https with http if HTTPS support has been disabled on the gateway.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#identity-name-and-password","title":"Identity name and password","text":"

    In order to login with identity name and password, select Password as authentication method and enter the credentials.

    Note

    Password authentication method might not be available if it has been disabled by the administrator using the Security -> Web Console section.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#forced-password-change-on-login","title":"Forced password change on login","text":"

    Kura supports forcing an identity to change the password at login, before accessing the Administration Console. This functionality is enabled by default after a fresh installation. At login the following prompt will appear forcing the user to define a new password.

    This functionality can be configured by an admin identity using the Identities section of the Administration Console.

    The forced password change can also be disabled by editing the snapshot_0.xml file removing the kura.need.password.change property for the desired identity in the org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl configuration before Kura first boot. If this functionality is enabled, REST API username and password authentication is will be disabled for the specific identity until the password is updated, REST API certificate authentication will still work.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#certificate-authentication","title":"Certificate authentication","text":"

    In order to perform HTTPS certificate authentication, select the Certificate authentication method and click Login, the browser may prompt to select the certificate to use.

    Note

    Certificate authentication method might not be available if no HTTPS with client authentication ports have been configured or if it has been explicitly disabled by the administrator using the Security -> Web Console section.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#identity-and-permission-management","title":"Identity and Permission management","text":"

    The Gateway Administration Console allows the management of identity and permission configuration in a dedicated view, accessible by navigating to the Identities section:

    The section above allows to:

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#create-new-identities","title":"Create new identities","text":"

    New identities can be created by clicking the New Identity button.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#remove-existing-identities","title":"Remove existing identities","text":"

    Existing identities can be removed by selecting the corresponding entry in the list and pressing the Delete Identity button.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#manage-password-authentication","title":"Manage password authentication","text":"

    Password authentication can be enabled or disabled by changing the Password authentication enabled parameter. Changing this parameter will not modify the existing stored password. Enabling password authentication for a new identity requires to define a new password. The password can be set/modified by clicking the Change password button.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#assign-or-remove-permissions","title":"Assign or remove permissions","text":"

    Permissions can be assigned or removed by ticking the corresponding entries in the Permissions table. No changes will be applied to the gateway until the Apply button is pressed.

    "},{"location":"gateway-configuration/gateway-administration-console-authentication/#certificate-based-authentication","title":"Certificate based authentication","text":"

    The Gateway Administration Console supports HTTPS certificate based client side authentication. The authentication process works as follows:

    • One or more Https client certificate must be added to keystore, this can be done using the Certificate Management section.

    • The user must provide a certificate or certificate chain signed by one of the CAs added as Https client certificate.

    • The common name field of the leaf certificate provided by the user must be the name of the identity that should be used for the session.

    • HTTPS with certificate based authentication must be enabled in the HTTP/HTTPS Configuration section.

    Log in with certificate can be performed by selecting the Certificate authentication method in Gateway Administration Console login screen or by connecting directly to the HTTPS port with client side authentication specified in gateway configuration.

    "},{"location":"gateway-configuration/gateway-administration-console/","title":"Gateway Administration Console","text":""},{"location":"gateway-configuration/gateway-administration-console/#accessing-the-kura-gateway-administration-console","title":"Accessing the Kura Gateway Administration Console","text":"

    Kura provides a web-based, user interface for the administration and management of your IoT gateway. The Kura Gateway Administration Console enables you to monitor the gateway status, manage the network configuration, and manage the installed application and services. Access to the Kura Gateway Administration Console requires that a unit running Eclipse Kura is reachable via its Ethernet primary interface.

    Connections on HTTP port 443 for these interfaces are allowed by default through the built-in firewall. The Kura Gateway Administration Console can be accessed by typing the IP address of the gateway into the browser's URL bar. Once the URL is submitted, the user is required to log in and is then redirected to the Administration Console (e.g., https://192.168.2.8/admin/console) shown in the screen capture below. The default login name and password is admin/admin.

    Warning

    It is recommended to change the default password after initial setup and before deployment, as well as limiting access to the Administration Console to a trusted local network interface using appropriate firewall rules.

    "},{"location":"gateway-configuration/gateway-administration-console/#password-change","title":"Password change","text":"

    Once logged in, the user can modify its password (recommended after the first login). To access the option, click on the button near the username in the header section. A dropdown menu appears with the logout and the password modification options. When clicking on \"Change password\", the following dialog will appear:

    After confirming the changes, the user will be logged out.

    "},{"location":"gateway-configuration/gateway-administration-console/#accessing-the-kura-gateway-administration-console-over-a-cellular-link","title":"Accessing the Kura Gateway Administration Console over a Cellular Link","text":"

    In order to connect to the Gateway Administration Console via a cellular interface, the following requirements must be met:

    • The service plan must allow for a static, public IP address to be assigned to the cellular interface.
    • The used ports must not be blocked by the provider.
    • The user must add Open Port entries for the cellular interface. This may be done either through the Firewall tab.

    If some of the used ports are blocked by the service provider, there is an option to reconfigure the gateway to use another port (i.e., 8080). In order to do so, the following requirements must be met:

    • The HttpService configuration must be changed to use the new ports.
    • The new ports must be open in the firewall for all network interfaces.
    "},{"location":"gateway-configuration/gateway-administration-console/#https-related-warnings","title":"HTTPS related warnings","text":"

    Most browsers will probably warn the user that the connection is not secure when Gateway Administration Console is accessed using HTTPS.

    In order to remove the warning, the browser must be able to verify the identity of the gateway as an HTTPS server. The verification process will fail with default server certificate provided by Kura because it is self-signed and it is not suitable for hostname verification.

    Fixing this might require to configure the browser to trust the certificate provided by the gateway and/or using a server certificate signed by a CA trusted by the browser and assigning a DNS name to the gateway in order to pass hostname verification.

    "},{"location":"gateway-configuration/gateway-administration-console/#system-use-notification-banner","title":"System Use Notification Banner","text":"

    For security reasons, it may be needed to display to the user a banner that describes the intended system use before authenticating. The system use notification message is customisable by authorised personnel in the Security section of the Kura Web UI, in the Web Console tab.

    "},{"location":"gateway-configuration/gateway-status/","title":"Gateway Status","text":"

    The status of the gateway may be viewed from the Status window, which is accessed by selecting the Status option located in the System area. The Status window provides a summary of the key information regarding the status of the gateway including its IoT Cloud connection and network configuration.

    The values reported in the page can be reloaded using the Refresh button. This will read the current values from the system and update the page. Since the update procedure can take time, the update can be performed at most every 30 seconds.

    "},{"location":"gateway-configuration/gateway-status/#cloud-services","title":"Cloud Services","text":"

    This section provides a summary of the IoT Cloud connection status including the following details:

    • Account - defines the name of the account used by the MqttDataTransport service when an MQTT connection is opened.
    • Broker URL - defines the URL of the MQTT broker.
    • Client ID - specifies the client identifier used by the MqttDataTransport service when an MQTT connection is opened.
    • Service Status - provides the status of the DataService and DataTransport connection. Valid values are CONNECTED or DISCONNECTED.
    • Username - supplies the name of the user used by the MqttDataTransport service when an MQTT connection is opened.
    "},{"location":"gateway-configuration/gateway-status/#ethernet-wireless-and-cellular-settings","title":"Ethernet, Wireless, and Cellular Settings","text":"

    This section provides information about the currently configured network interfaces.

    "},{"location":"gateway-configuration/gateway-status/#position-status","title":"Position Status","text":"

    This section provides the GPS status and latest known position (if applicable) including the following details:

    • Longitude - longitude as reported by the PositionService in radians.
    • Latitude - latitude as reported by the PositionService in radians.
    • Altitude - altitude as reported by the PositionService in meters.

    Warning

    The status reported in the page may not be synchronized with the real state of the system. In this case, use the Refresh button to updated the values in the page.

    "},{"location":"gateway-configuration/keys-and-certificates/","title":"Keys and Certificates","text":"

    The framework manages directly different key pairs and trusted certificates from different keystores. To simplify the management of such complex objects, the framework provides a dedicated section of its Administrative Web UI, a set of REST APIs for local management and a request handler (KEYS-V1 and KEYS-V2) for cloud remote interaction.

    "},{"location":"gateway-configuration/keys-and-certificates/#web-ui","title":"Web UI","text":"

    The Certificates List tab in the Security section of the Kura Web UI provides a simple way for the user to get the list of all the managed keys and certificates of the framework:

    The page allows the user to add a new Keypair or trusted certificate or to delete an existing element.

    Every key pair or trusted certificate is listed by its alias, identified by the corresponding type and further identified by the keystore that is managing that element.

    If the user needs to add a new entry to one of the managed KeystoreService instances, can click on the Add button on the top left part of the page. The user will be guided through a process that will allow to identify the type of entry to add:

    It can be either a:

    • Private/Public Key Pair
    • Trusted Certificate

    If the user decides to add a key pair, then the wizard will provide a page like the following:

    Here the user can specify: - Key Store: the KeystoreService instance that will store and maintain the key pair - Storage Alias: the alias that will be used to identify the key pair - Private Key: the private key part of the key pair - Certificate: the public key part of the key pair

    After clicking on the Apply button, the new entry will be stored in the selected Keystore and listed along the other entries managed by the framework.

    The following cryptographic algorithms are supported for Key Pairs: - RSA - DSA

    Instead, if the user wants to load a Trusted Certificate, the Ui will change as follows:

    Here the user can specify: - Key Store: the KeystoreService instance that will store and maintain the trusted certificate - Storage Alias: the alias that will be used to identify the trusted certificate - Certificate: the trusted certificate

    The following cryptographic algorithms are supported for Trusted Certificates: - RSA - DSA - EC

    "},{"location":"gateway-configuration/keys-and-certificates/#rest-apis","title":"REST APIs","text":""},{"location":"gateway-configuration/keys-and-certificates/#keystoresv1","title":"keystores/v1","text":"

    The org.eclipse.kura.core.keystore bundle exposes a REST endpoint under the /services/keystores/v1 path. The Kura REST APIs for Keys and Certificates support the following calls and are allowed to any user with rest.keystores permission.

    Method Path Roles allowed Encoding Request parameters Description GET / keystores JSON None Returns the list of all the KeystoreService instances. GET /entries keystores JSON None Returns the list of all the entries managed by the KeystoreService instances. GET /entries?keystoreServicePid={keystoreServicePid} keystores JSON keystoreServicePid Returns the list of all the entries managed by the specified KeystoreService instance. GET /entries?alias={alias} keystores JSON alias Returns the list of all the entries specified by the defined alias and managed in all the available KeystoreService instances in the framework. GET /entries/entry?keystoreServicePid={keystoreServicePid}&alias={alias} keystores JSON keystoreServicePid and alias Returns the entry identified by the specified keystoreServicePid and alias. POST /entries/csr keystores JSON The reference to the key pair in a specified KeystoreService instance that will be used to generate the CSR. The request has to be associated with additional parameters that identify the algorithm used to compute and sign the CSR and the DN or the corresponding public key that needs to be countersigned. Generates a CSR for the specified key pair in the specified KeystoreService instance, based on the parameters provided in the request. POST /entries/certificate keystores JSON The reference to the KeystoreService instance and the alias that will be used for storage. A type filed identifies the type of key that needs to be managed. This request allows the user to upload a TrustedCertificate. POST /entries/keypair keystores JSON To generate a new KeyPair directly in the device, the request format need to follow the references in the following paragraphs. This request allows the user to generate a new KeyPair into the device. DELETE /entries keystores JSON A JSON identifying the resource to delete. The format of the request is described in in one of the following sections. Deletes the entry in the specified KeystoreService instance."},{"location":"gateway-configuration/keys-and-certificates/#list-all-the-keystoreservices","title":"List All the KeystoreServices","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1

    Response:

    [\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"type\": \"jks\",\n        \"size\": 4\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.crypto.CryptoService\",\n        \"type\": \"jks\",\n        \"size\": 3\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.DMKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    }\n]\n
    "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-managed-entries","title":"Get all the Managed Entries","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries

    Response:

    [\n    {\n        \"subjectDN\": \"OU=Go Daddy Class 2 Certification Authority, O=\\\"The Go Daddy Group, Inc.\\\", C=US\",\n        \"issuer\": \"OU=Go Daddy Class 2 Certification Authority,O=The Go Daddy Group\\\\, Inc.,C=US\",\n        \"startDate\": \"Tue, 29 Jun 2004 17:06:20 GMT\",\n        \"expirationDate\": \"Thu, 29 Jun 2034 17:06:20 GMT\",\n        \"algorithm\": \"SHA1withRSA\",\n        \"size\": 2048,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"alias\": \"ca-godaddyclass2ca\",\n        \"type\": \"TRUSTED_CERTIFICATE\"\n    },\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
    "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-entries-by-keystoreservice","title":"Get All the Entries by KeystoreService","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries?keystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore

    Response:

    [\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"certificateChain\": [\n            \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>\\n-----END CERTIFICATE-----\"\n        ],\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
    "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-entries-by-alias","title":"Get All the Entries by Alias","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries?alias=localhost

    Response:

    [\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"certificateChain\": [\n            \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>\\n-----END CERTIFICATE-----\"\n        ],\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
    "},{"location":"gateway-configuration/keys-and-certificates/#get-specific-entry","title":"Get Specific Entry","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries/entry?keystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore&alias=localhost

    Response:

    {\n    \"algorithm\": \"RSA\",\n    \"size\": 4096,\n    \"certificateChain\": [\n        \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>-----END CERTIFICATE-----\"\n    ],\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\": \"localhost\",\n    \"type\": \"PRIVATE_KEY\"\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#get-the-csr-for-a-keypair","title":"Get the CSR for a KeyPair","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries/csrkeystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore&alias=localhost`

    Request body:

    { \n    \"keystoreServicePid\":\"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\":\"localhost\",\n    \"signatureAlgorithm\" : \"SHA256withRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#store-trusted-certificate","title":"Store Trusted Certificate","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries/certificate

    Request body:

    {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"myCertTest99\",\n    \"certificate\":\"-----BEGIN CERTIFICATE-----\n        <CERTIFICATE>\n        -----END CERTIFICATE-----\"\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#generate-keypair","title":"Generate KeyPair","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries/keypair

    Request body:

    {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"algorithm\" : \"RSA\",\n    \"size\": 1024,\n    \"signatureAlgorithm\" : \"SHA256WithRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#delete-entry","title":"Delete Entry","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v1/entries

    Request body:

    {\n    \"keystoreServicePid\" : \"MyKeystore\",\n    \"alias\" : \"mycerttestec\"\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#keys-v1-request-handler","title":"KEYS-V1 Request Handler","text":"

    Mapping the previously defined REST APIs, the framework exposed to the remote cloud platforms a request handler named KEYS-V1 that allows the remote user to list and manage the keystores, the keys and the certificates in the framework.

    The request handler exposes also the capability to generate on the edge a CSR that can be countersigned remotely by a trusted CA.

    "},{"location":"gateway-configuration/keys-and-certificates/#keystoresv2","title":"keystores/v2","text":"

    Starting from Kura 5.4.0, the keystores/v2 REST API is also available, it supports all of the request endpoints of keystores/v1 plus an additional endpoint that allows to upload and modify private key entries:

    Method Path Roles allowed Encoding Request parameters Description POST /entries/privatekey keystores JSON To upload a new private key entry directly in the device, the request format need to follow the references in the following paragraphs. This request allows the user to upload a new private key entry into the device or to modify the certificate chain of an existing one."},{"location":"gateway-configuration/keys-and-certificates/#uploading-a-private-key-entry","title":"Uploading a Private Key Entry","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v2/entries/privatekey

    Request body:

    The request should include the private key in unencrypted PEM format and the certificate chain in PEM format, the first certificate in the certificateChain list must use the public key associated with the private key supplied as the privateKey parameter.

    The device will overwrite the entry with the provided alias if it already exists.

    WARINING: Please use this endpoint through a secure connection.

    {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"privateKey\":\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
    "},{"location":"gateway-configuration/keys-and-certificates/#updating-a-private-key-entry","title":"Updating a Private Key Entry","text":"

    Request: URL - https://<gateway-ip>/services/keystores/v2/entries/privatekey

    Request body:

    In order to update the certificate chain associated to a specific private key entry it is possible to use the same format as previous request and omit the privateKey parameter.

    In this case the certificate chain of the existing entry will be replaced with the one specified in the request and the existing private key will be retained.

    This request can be useful for example to create a CSR on the device, sign it externally and then updating the corresponding entry with the resulting certificate.

    {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
    "},{"location":"gateway-configuration/keystores-management/","title":"Keystores Management","text":"

    The framework manages different types of cryptographic keys and certificates. In order to simplify the interaction with those objects, Kura provides a KeystoreService API and a specific section in the Kura Web UI that lists all the available KeystoreService instances.

    From the Security section, a user with Security permissions can access the Keystore Configuration section. A list of all the framework managed keystores will be available to the user with the Service PID that will be used by other components to reference the selected keystore. Associated to the Service PID, the UI shows the Factory PID that identifies the specific KeystoreService API implementation that is providing the service to the framework.

    In order to modify the configuration of a specific keystore service instance, the user can select one of the available rows, obtaining the corresponding keystore service configuration.

    The following KeystoreService factories are available:

    "},{"location":"gateway-configuration/keystores-management/#filesystemkeystoreserviceimpl","title":"FilesystemKeystoreServiceImpl","text":"

    The org.eclipse.kura.core.keystore.FilesystemKeystoreServiceImpl factory provides a KeystoreService implementation that stores the private keys and certificates as a file. The user can customise the following options:

    • Keystore Path: identifies the path in the filesystem. If the keystore file does not exists, a new file will be created. The value cannot be empty.
    • Keystore Password: the corresponding keystore password.
    • Randomize Password: a boolean flag that allows the user to specify if the keystore password needs to be randomised at the next framework boot. If set true, the framework will try to access the identified keystore and randomise the password. The new password will be persisted in the framework snapshot. Once successfully randomised, the flag will be automatically set to false by the framework.
    "},{"location":"gateway-configuration/keystores-management/#pkcs11keystoreserviceimpl","title":"PKCS11KeystoreServiceImpl","text":"

    The org.eclipse.kura.core.keystore.PKCS11KeystoreServiceImpl factory provides a KeystoreService implementation that allows to access a PKCS11 token through the SunPKCS11 implementation.

    At the moment this type of KeystoreService provides read only access to the underlying token, operations such as adding or removing entries will fail.

    It is possible to use the entries provided by a PKCS11KeystoreServiceImpl for SSL authentication.

    The available configuration options closely match the parameters provided by the SunPKCS11 implementation, see the official documentation for more details.

    In particular, the official documentation contains a section that explains how the PKCS11 objects are mapped to Java KeyStore entries.

    The only required parameter is the PKCS11 Implementation Library Path parameter. It is usually also necessary to specify the token user pin as the Pin parameter.

    The configuration parameters are mapped to the SunPKCS11 provider parameters in the following way:

    Kura Parameter SunPKCS11 Parameter Notes Slot slot Slot List Index slotListIndex Enabled Mechanisms enabledMechanisms The curly braces must be omitted Disabled Mechanisms disabledMechanisms The curly braces must be omitted. Attributes attributes The value of this field will be appended to the provider configuration."},{"location":"gateway-configuration/network-advanced/","title":"Advanced Network Settings","text":"

    The Advanced configuration tab is designed for users who seek a higher level of control and customization over their network experience. Whether you are an IT professional, network administrator, or enthusiast looking to fine-tune specific aspects of your network, this tab provides access to advanced parameters like Maximum Transmission Unit (MTU) and Promiscuous Mode. Adjusting these settings allows for optimized performance in unique network environments, troubleshooting capabilities, and enhanced security monitoring. Please exercise caution when modifying these settings, ensuring that you have a clear understanding of their implications on your network's behavior.

    Tip

    Some parameters requires a minimum version of NetworkManager, as described below and in Kura UI. The System Component Inventory can be used to check what version is available on your device.

    The Advanced tab allow configuration of:

    • MTU (Maximum Transmission Unit)
      • MTU determines the maximum size of data packets transmitted over your network. While most users can leave this at the default setting, adjusting the MTU may be necessary for optimizing performance in specialized environments or overcoming network constraints.
      • Use Case: Modify MTU if you encounter issues related to packet size, especially in scenarios involving VPNs, specific network equipment, or other unique requirements.
    • Promiscuous Mode
      • Promiscuous mode is a specialized network mode where your device captures and analyzes all network packets, regardless of their destination. This is particularly useful for network monitoring, troubleshooting, and security analysis.
      • Use Case: Enable promiscuous mode when you need to closely inspect network traffic, diagnose connectivity issues, or enhance your device's capabilities for security monitoring.
    "},{"location":"gateway-configuration/network-advanced/#advanced-configuration","title":"Advanced Configuration","text":"

    The Advanced tab contains the following configuration parameters:

    • IPv4 MTU - defines the Maximum Trasmission unit for IPv4 traffic on the selected interface, a value of 0 or empty delegates the MTU definition to the link layer.
    • IPv6 MTU - defines the Maximum Trasmission unit for IPv6 traffic on the selected interface, a value of 0 or empty delegates the MTU definition to the link layer. Requires NetworkManager 1.40 or above.

    Warning

    The MTU value for a VLAN is limited by the configured MTU of its parent interface as an upper bound.

    • Promiscuous Mode - enable promiscuous mode for the selected interface; Requires NetworkManager 1.32 or above.
      • System default - This delegates to the underlying OS setting. In most cases the system default equates to Disabled
      • Enabled
      • Disabled
    "},{"location":"gateway-configuration/network-configuration/","title":"Network Configuration","text":"

    To configure the gateway network interfaces using the Gateway Administration Console, select the Network option located in the System area. With this option selected, the Network display appears with a list of available interfaces. Configuration tabs for the selected interface appear on the right side of the screen. By default, the loopback (lo) interface is selected when the network interfaces are displayed. Choose the desired network interface (e.g., eth0, eth1, wlan0, ppp0) and apply the necessary configuration changes using the tabs on the right. Submit the modified configuration by clicking the Apply button.

    In case of typing errors, the Reset button can be used to reload the prior configuration on the screen. Since the network configuration shown on the screen may not be synchronized with the current state of the system, it can be updated pressing the Refresh button. This can be used also to force the reload of specific parameters like the RSSI or dynamic IP addresses. The refresh procedure reads all the needed parameters from the system and can take several seconds before updating.

    Tip

    It is recommended that the IPv4 or IPv6 tab is configured first since it defines how the interface is going to be used.

    "},{"location":"gateway-configuration/network-configuration/#tcpip-configuration","title":"TCP/IP Configuration","text":"

    The IPv4 and IPv6 tabs contain the following configuration parameters:

    • Status
      • Disabled: disables the selected interface (i.e., administratively down).
      • Enabled for LAN: designates the interface for a local network. It can be set as a DHCP server for hosts on the local network and can serve as a default gateway for those hosts; however, it cannot be set as an actual gateway interface for this device. That is, packets must be routed from this interface to another interface that is configured as WAN. The interface is automatically brought up at boot.
      • Enabled for WAN: designates the interface as a gateway to an external network. The interface is automatically brought up at boot.
      • Not Managed: the interface will be ignored by Kura (available only for IPv4).
      • Layer 2 Only: only the Layer 2 portion of the interface will be configured. The interface is automatically brought up at boot (available only for IPv4).
    • WAN Priority - configure the network failover. See here for more details.
    • Configure
      • Manually: allows manual entry of the IP Address and Netmask fields, if the interface is configured as LAN; allows manual entry of the IP Address, Netmask, Gateway, and DNS Servers fields, if the interface is designated as WAN.
      • Using DHCP/DHCPv6: configures the interface as a DHCP client obtaining the IP address from a network DHCP server.
      • Stateless Address Auto-Configuration (SLAAC): automatically assign an IP address (available only for IPv6).
    • Address Generation Mode - defines the method used to automatically generate the IP address with SLAAC (available only for IPv6)
      • EUI64: use EUI-64 method for address generation
      • Stable-privacy: use RFC7217 method for address generation
    • IP Address - defines the IP address of the interface, if manually configured.
    • Subnet Mask - defines the subnet mask of the interface, if manually configured.
    • Gateway - specifies the default gateway for the unit. (Required field if the interface is designated as WAN and manually configured.)
    • DNS Servers - provides a list of DNS servers, if the interface is designated as WAN and is manually configured.
    • Privacy - configure privacy extension for SLAAC (available only for IPv6).
      • Disabled: disables the privacy extension.
      • Prefer public addresses: use public address for outgoing traffic.
      • Prefer temporary addresses: prefer temporary address for outgoing traffic.

    If the network interface is configured as Enabled for LAN and manually configured (i.e., not a DHCP client) in the IPV4 tab, the DHCPv4 & NAT tab allows the DHCP server to be configured and/or NAT (IP forwarding with masquerading) to be enabled.

    "},{"location":"gateway-configuration/network-configuration/#more-details-about-the-not-managed-interface-status-tbd-not-applicable-in-nm","title":"More details about the Not Managed interface Status - (TBD: not applicable in NM)","text":"

    When a network interface is configured as Not Managed, Kura will ignore it and the configuration will not be touched. The user can configure the interface with the network tools provided by the OS, allowing unusual network setups.

    Regarding DNS, both Kura and the external tools store the DNS addresses in the /etc/resolv.conf file. So, if multiple interfaces are configured to get the DNS information and store it in the same file, the device can be misconfigured. To avoid that, the following table presents who is responsible to update the DNS file depending on the network interfaces configurations.

    Is there at least an interface set as WAN? Is there at least one interface set as Not Managed? Does Kura manage resolv.conf? NO NO YES NO YES NO YES NO YES YES YES YES

    So, the only way to configure the DNS addresses with external tools, is to configure at least one interface as Not Managed and not to set any interface as Enabled For Wan using Kura. If at least one WAN interface is configured by Kura, it will take the control of the /etc/resolv.conf/ file. Finally, if any interface is configured in Enabled For Wan or Not Managed mode, Kura will empty the file.

    To avoid device misconfigurations when Not Managed interfaces are used, don't use the dns-nameservers directive in the /etc/network/interfaces file. Please add the DNS addresses directly to the /etc/resolv.conf file.

    "},{"location":"gateway-configuration/network-configuration/#dhcpv4-nat-configuration","title":"DHCPv4 & NAT Configuration","text":"

    The DHCPv4 & NAT tab contains the following configuration parameters:

    • Router Mode
      • DHCP and NAT: indicates that both DHCP server and NAT are enabled.
      • DHCP Only: indicates that DHCP server is enabled and NAT is disabled.
      • NAT Only: indicates that NAT is enabled and DHCP server is disabled.
      • Off: indicates that both DHCP server and NAT are disabled.
    • DHCP Beginning Address: specifies the first address of DHCP pool (i.e., first available client IP address).
    • DHCP Ending Address: specifies the last address of DHCP pool (i.e., last IP address that can be assigned to a client).
    • DHCP Subnet Mask: defines the subnet mask that is assigned to a client.
    • DHCP Default Lease Time: sets the default time (in minutes) that the client retains the provided IP address. It must be greater than 0.
    • DHCP Max Lease Time: sets the maximum time (in minutes) that the client retains the provided IP address. It must be greater than 0.
    • Pass DNS Servers through DHCP: enables DNS Proxy (i.e., passing DNS servers through DHCP).

    If NAT is enabled and there is another interface designated as WAN (e.g., ppp0), the following iptables rules are added to the custom automatic NAT service rules section of the /etc/init.d/firewall script:

    # custom automatic NAT service rules (if NAT option is enabled for LAN interface)\niptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE\niptables -A FORWARD -i ppp0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A FORWARD -i eth0 -o ppp0 -j ACCEPT\n

    Also, IP forwarding is enabled in the kernel as follows:

    # allow fowarding if any masquerade is defined\necho 1 > /proc/sys/net/ipv4/ip_forward\n

    The rules shown above create an Overloaded (i.e., many-to-one) NAT. This type of network address translation maps multiple IP addresses on the LAN side to a single IP address on the WAN side, allowing internet access from hosts on a local network via a gateway (WAN) interface. Note that for NAT rules to be added, it is insufficient to enable NATing through the DHCPv4 & NAT tab of the LAN interface; there must also be another interface designated as WAN.

    "},{"location":"gateway-configuration/network-configuration/#network-linux-configuration","title":"Network Linux Configuration","text":"

    When applying a new network configuration, Kura changes the configuration files of the Linux networking subsystem. Please read the following note before proceeding with manual changes of the Linux networking configuration.

    Warning

    It is NOT recommended performing manual editing of the Linux networking configuration files when the gateway configuration is being managed through Kura. While Linux may correctly accept manual changes, Kura may not be able to interpret the new configuration resulting in an inconsistent state.

    "},{"location":"gateway-configuration/network-configuration/#network-configuration-properties","title":"Network Configuration properties","text":"

    The Network configuration can be modified using the Kura Gateway Administration Console, as described above, the Configuration Service or appling a proper snapshot.

    The following table describes all the properties related to the Network Configuration. It includes the name of the property, the type, a description and the default value (if applicable). The network configuration pid is org.eclipse.kura.net.admin.NetworkConfigurationService.

    "},{"location":"gateway-configuration/network-configuration/#common-properties","title":"Common properties","text":"Name Type Description Default value net.interfaces String Comma-separated list of the interface names in the device net.interface.<interface>.type String The type of the network interface; possible values are: ETHERNET, WIFI, MODEM, VLAN, LOOPBACK and UNKNOWN UNKNOWN net.interface.<interface>.config.wifi.mode String For wifi interfaces, specify the modality; possible values are INFRA, MASTER and UNKNOWN UNKNOWN net.interface.<interface>.config.nat.enabled Boolean Enable the NAT feature false net.interface.<interface>.config.promisc Integer Enable the Promiscuous Mode; possible values are: -1 (System default), 0 (Disabled), 1 (Enabled) -1"},{"location":"gateway-configuration/network-configuration/#ipv4-properties","title":"IPv4 properties","text":"Name Type Description Default value net.interface.<interface>.config.ip4.status String The status of the interface for the IPv4 configuration; possibile values are: netIPv4StatusDisabled, netIPv4StatusUnmanaged, netIPv4StatusL2Only, netIPv4StatusEnabledLAN, netIPv4StatusEnabledWAN, netIPv4StatusUnknown netIPv4StatusDisabled (see note below) net.interface.<interface>.config.ip4.wan.priority Integer (NetworkManager only) Priority used to determine which interface select as primary WAN. Allowed values range from -1 to 2147483647, inclusive. See Network Failover for further details -1 net.interface.<interface>.config.ip4.address String The IPv4 address assigned to the network interface net.interface.<interface>.config.ip4.prefix Short The IPv4 netmask assigned to the network interface -1 net.interface.<interface>.config.ip4.gateway String The IPv4 address of the default gateway net.interface.<interface>.config.ip4.dnsServers String Comma-separated list of dns servers net.interface.<interface>.config.ip4.mtu Integer The Maximum Transition Unit (MTU) for this interface

    Note

    For physical interfaces the default status is netIPv4StatusDisabled. For virtual ones, instead, the default status is defined by the kura.net.virtual.devices.config property in the kura.properties file.

    "},{"location":"gateway-configuration/network-configuration/#ipv4-dhcp-server-properties","title":"IPv4 DHCP Server properties","text":"Name Type Description Default value net.interface.<interface>.config.dhcpServer4.enabled Boolean Specify if the DHCP server is enabled false net.interface.<interface>.config.dhcpServer4.rangeStart String First IP address available for clients net.interface.<interface>.config.dhcpServer4.rangeEnd String Last IP address available for clients net.interface.<interface>.config.dhcpServer4.defaultLeaseTime Integer The default lease time -1 net.interface.<interface>.config.dhcpServer4.maxLeaseTime Integer The maximum lease time -1 net.interface.<interface>.config.dhcpServer4.prefix Short The netmask for the available IP addresses -1 net.interface.<interface>.config.dhcpServer4.passDns Boolean Specify if the DNS server addresses has to be passed through DHCP false"},{"location":"gateway-configuration/network-configuration/#ipv4-dhcp-client-properties","title":"IPv4 DHCP Client properties","text":"Name Type Description Default value net.interface.<interface>.config.dhcpClient4.enabled Boolean Specify if the DHCP client is enabled false"},{"location":"gateway-configuration/network-configuration/#ipv6-properties","title":"IPv6 properties","text":"Name Type Description Default value net.interface.<interface>.config.ip6.status String The status of the interface for the IPv6 configuration; possibile values are: netIPv6StatusDisabled, netIPv6StatusUnmanaged, netIPv6StatusL2Only, netIPv6StatusEnabledLAN, netIPv6StatusEnabledWAN, netIPv6StatusUnknown netIPv6StatusDisabled (see note below) net.interface.<interface>.config.ip6.wan.priority Integer (NetworkManager only) Priority used to determine which interface select as primary WAN. Allowed values range from -1 to 2147483647, inclusive. See Network Failover for further details -1 net.interface.<interface>.config.ip6.address.method String The IPv6 configuration method; possible values are: AUTO, DHCP, MANUAL. AUTO net.interface.<interface>.config.ip6.address String The IPv6 address assigned to the network interface net.interface.<interface>.config.ip6.prefix Short The IPv6 netmask assigned to the network interface -1 net.interface.<interface>.config.ip6.gateway String The IPv6 address of the default gateway net.interface.<interface>.config.ip6.dnsServers String Comma-separated list of dns servers net.interface.<interface>.config.ip6.addr.gen.mode String The IPv6 address generation mode; possible values are EUI64, STABLE_PRIVACY net.interface.<interface>.config.ip6.privacy String The IPv6 Privacy Extensions for SLAAC; possible values are DISABLED, ENABLED_PUBLIC_ADD, ENABLED_TEMP_ADD net.interface.<interface>.config.ip6.mtu Integer The Maximum Transition Unit (MTU) for Ipv6 traffic on this interface. Requires NetworkManager 1.40 or newer

    Note

    For physical interfaces the default status is netIPv6StatusDisabled. For virtual ones, instead, the default status is defined by the kura.net.virtual.devices.config property in the kura.properties file.

    "},{"location":"gateway-configuration/network-configuration/#wifi-master-access-point-properties","title":"WiFi Master (Access Point) properties","text":"Name Type Description Default value net.interface.<interface>.config.wifi.master.driver String The driver used for the connection net.interface.<interface>.config.wifi.master.passphrase Password The password for the access point net.interface.<interface>.config.wifi.master.ssid String The SSID of the access point net.interface.<interface>.config.wifi.master.securityType String The security protocol for the wireless network; possible values are SECURITY_NONE, SECURITY_WEP, SECURITY_WPA, SECURITY_WPA2, SECURITY_WPA_WPA2 SECURITY_NONE net.interface.<interface>.config.wifi.master.mode String The mode of the wireless connection; for the access point mode set it to MASTER MASTER net.interface.<interface>.config.wifi.master.channel String The channel to be used for the access point 1 net.interface.<interface>.config.wifi.master.radioMode String Specify the 802.11 radio mode; possible values are RADIO_MODE_80211a, RADIO_MODE_80211b, RADIO_MODE_80211g, RADIO_MODE_80211nHT20, RADIO_MODE_80211_AC RADIO_MODE_80211b net.interface.<interface>.config.wifi.master.ignoreSSID Boolean Specify if the SSID broadcast is ignored false net.interface.<interface>.config.wifi.master.groupCiphers String Group ciphers i.e. group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP net.interface.<interface>.config.wifi.master.pairwiseCiphers String Pairwise ciphers i.e. pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP"},{"location":"gateway-configuration/network-configuration/#wifi-infra-station-mode-properties","title":"WiFi Infra (Station Mode) properties","text":"Name Type Description Default value net.interface.<interface>.config.wifi.infra.ssid String The SSID of the wireless network to connect to net.interface.<interface>.config.wifi.infra.channel String The channel of the wireless network to connect to 1 net.interface.<interface>.config.wifi.infra.bgscan String Set the background scans; possible values have the form <mode>:<shortInterval>:<rssiThreshold>:<longInterval> where mode (String) is one of NONE, SIMPLE, or LEARN, shortInterval (Integer) sets the Bgscan short interval (secs), rssiThreshold (Integer) sets the Bgscan Signal strength threshold (dBm), and longInterval (Integer) sets the Bgscan long interval (secs) net.interface.<interface>.config.wifi.infra.passphrase Password The password for the wireless network net.interface.<interface>.config.wifi.infra.ignoreSSID Boolean Specify if a scan for SSID is required before attempting to associate false net.interface.<interface>.config.wifi.infra.mode String The mode of the wireless connection; for station mode set to INFRA INFRA net.interface.<interface>.config.wifi.infra.pingAccessPoint Boolean Enable pinging the access point after connection is established false net.interface.<interface>.config.wifi.infra.driver String The driver used for the connection net.interface.<interface>.config.wifi.infra.securityType String The security protocol for the wireless network; possible values are SECURITY_NONE, SECURITY_WEP, SECURITY_WPA, SECURITY_WPA2, SECURITY_WPA_WPA2 SECURITY_NONE net.interface.<interface>.config.wifi.infra.groupCiphers String Group ciphers i.e. group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP net.interface.<interface>.config.wifi.infra.pairwiseCiphers String Pairwise ciphers i.e. pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP"},{"location":"gateway-configuration/network-configuration/#cellular-modem-properties","title":"Cellular Modem properties","text":"Name Type Description Default value net.interface.<interface>.config.enabled Boolean Enable the interface false net.interface.<interface>.config.idle Integer The idle option of the PPP daemon 95 net.interface.<interface>.config.username String The username used for the connection net.interface.<interface>.config.password Password The password used for the connection net.interface.<interface>.config.pdpType String The PdP type; possible values are IP, PPP and IPv6 IP net.interface.<interface>.config.maxFail Integer The maxfail option of the PPP daemon 5 net.interface.<interface>.config.authType String The authentication type; possible values are NONE, AUTO, CHAP and PAP NONE net.interface.<interface>.config.lpcEchoInterval Integer The lcp-echo-interval option of the PPP daemon 0 net.interface.<interface>.config.activeFilter String The active-filter option of the PPP daemon inbound net.interface.<interface>.config.lpcEchoFailure Integer The lcp-echo-failure option of the PPP daemon 0 net.interface.<interface>.config.diversityEnabled Boolean Enable the LTE diversity antenna false net.interface.<interface>.config.resetTimeout Integer The modem reset timeout in minutes 5 net.interface.<interface>.config.gpsEnabled Boolean Enable the GPS device in the modem if available false net.interface.<interface>.config.gpsMode String Select the GPS mode to activate for the modem if available kuraModemGpsModeUnmanaged net.interface.<interface>.config.persist Boolean The persist option of the PPP daemon true net.interface.<interface>.config.apn String The modem Access Point Name net.interface.<interface>.config.dialString String The dial string used for connecting to the APN net.interface.<interface>.config.holdoff Integer The holdoff option of the PPP daemon (in seconds) 1 net.interface.<interface>.config.pppNum Integer Assigned ppp interface number 0"},{"location":"gateway-configuration/network-configuration/#gps-mode","title":"GPS Mode","text":"

    The GPS mode can be set to one of the following values:

    • kuraModemGpsModeUnmanaged: the GPS device of the modem will be setup but not directly managed, therefore freeing the serial port for other services to use. This can be used in order to perform the setup of the GPS and then have another service (like gpsd) parse the NMEA strings in order to extract the position informations. This is the default value.
    • kuraModemGpsModeManagedGps: the GPS device of the modem will be setup and directly managed (typically by ModemManager) therefore the serial port won't be available for other services to use.

    For older versions compatibility, if the net.interface.<interface>.config.gpsMode property is not set, the GPS mode will be automatically set to the kuraModemGpsModeUnmanaged equivalent.

    "},{"location":"gateway-configuration/network-configuration/#vlan-properties","title":"VLAN properties","text":"Name Type Description Default value net.interface.<interface>.config.vlan.parent String Physical interface Vlan is bound to net.interface.<interface>.config.vlan.id Integer Vlan tag identifier, between 0 and 4094 net.interface.<interface>.config.vlan.ingress String Incoming traffic priorities in the format from:to, as comma separated pairs of unsigned integers (Optional) net.interface.<interface>.config.vlan.egress String Outgoing traffic priorities in the format from:to, as comma separated pairs of unsigned integer (Optional) net.interface.<interface>.config.vlan.flags String Configuration flags, between 0 and 15 (Optional) 1"},{"location":"gateway-configuration/network-configuration/#8021x-properties","title":"802.1x properties","text":"Name Type Description net.interface.<interface>.config.802-1x.eap String The EAP method to be used when authenticating to the network with 802.1x. Supported methods: \"Kura8021xEapTls\", \"Kura8021xEapPeap\", \"Kura8021xEapTtls\". net.interface.<interface>.config.802-1x.innerAuth String Specifies the \"phase 2\" inner authentication method when an EAP method that uses an inner TLS tunnel is specified in the \"eap\" property. Supported methods: \"Kura8021xInnerAuthNone\", \"Kura8021xInnerAuthMschapv2\". net.interface.<interface>.config.802-1x.identity String Identity string for EAP authentication methods. Typically the user's user or login name. net.interface.<interface>.config.802-1x.password String Password used for EAP authentication methods. net.interface.<interface>.config.802-1x.client-cert-name String Name referring to the corresponding Kura keystore entry containing the client certificate if used by the EAP method specified in the \"eap\" property. Typically is set to the same name as the net.interface.<interface>.config.802-1x.private-key-name when using EAP-TLS. net.interface.<interface>.config.802-1x.private-key-name String Name referring to the corresponding Kura keystore entry containing the private key when the \"eap\" property is set to \"Kura8021xEapTls\". Typically is set to the same name as the net.interface.<interface>.config.802-1x.client-cert-name. net.interface.<interface>.config.802-1x.ca-cert-name String Name referring to the corresponding Kura keystore entry containing the CA certificate if used by the EAP method specified in the \"eap\" property. This parameter is optional but this allows man-in-the-middle attacks and is NOT recommended. net.interface.<interface>.config.802-1x.anonymous-identity String Anonymous identity string for EAP authentication methods. Used as the unencrypted identity with EAP types that support different tunneled identity like EAP-TTLS (Optional)"},{"location":"gateway-configuration/network-configuration/#network-configuration-recipes","title":"Network Configuration recipes","text":"

    This section presents some snapshot examples to perform basic operations on networking. The snippets can be modified adapting them to the required configuration (i.e. changing the interface name in the property to be applied).

    Warning

    Be aware that an inconsitent or wrong configuration can compromise the network functionality of the gateway. Try the new configuration on a test device before appling it in a production environment!

    Moreover, if a property is not present in the new snapshot, the old value is used for the configuration. So, the best practice is to set all the needed properties in the snapshot.

    "},{"location":"gateway-configuration/network-configuration/#disable-a-network-interface","title":"Disable a network interface","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusDisabled</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#configure-an-ethernet-interface-for-wan-with-dhcp-client-enabled-and-custom-dns-server","title":"Configure an ethernet interface for WAN with DHCP client enabled and custom DNS server","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value>1.2.3.4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#configure-an-ethernet-interface-for-lan-with-dhcp-server-enabled-and-nat-disabled","title":"Configure an ethernet interface for LAN with DHCP server enabled and NAT disabled","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.rangeEnd\" type=\"String\">\n                <esf:value>192.168.4.110</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.defaultLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.passDns\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.rangeStart\" type=\"String\">\n                <esf:value>192.168.4.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.maxLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.4.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.gateway\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#configure-a-wireless-interface-as-access-point-with-dhcp-server-and-nat-enabled","title":"Configure a wireless interface as access point with DHCP server and NAT enabled","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.type\" type=\"String\">\n                <esf:value>WIFI</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.gateway\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.address\" type=\"String\">\n                <esf:value>172.16.1.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.rangeStart\" type=\"String\">\n                <esf:value>172.16.1.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.maxLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.defaultLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.rangeEnd\" type=\"String\">\n                <esf:value>172.16.1.110</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.passDns\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.mode\" type=\"String\">\n                <esf:value>MASTER</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.driver\" type=\"String\">\n                <esf:value>nl80211</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.wlp1s0.config.wifi.master.passphrase\" type=\"Password\">\n                <esf:value>ZW5hYmxlbWVwbGVhc2U=</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.ssid\" type=\"String\">\n                <esf:value>kura_gateway_19</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.securityType\" type=\"String\">\n                <esf:value>SECURITY_WPA2</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.mode\" type=\"String\">\n                <esf:value>MASTER</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.channel\" type=\"String\">\n                <esf:value>11</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.radioMode\" type=\"String\">\n                <esf:value>RADIO_MODE_80211g</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.ignoreSSID\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.pairwiseCiphers\" type=\"String\">\n                <esf:value>CCMP</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#configure-a-wireless-interface-as-station-mode-with-dhcp-client-enabled","title":"Configure a wireless interface as station mode with DHCP client enabled","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.type\" type=\"String\">\n                <esf:value>WIFI</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.mode\" type=\"String\">\n                <esf:value>INFRA</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.ssid\" type=\"String\">\n                <esf:value>MyWirelessNetwork</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.bgscan\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.wlp1s0.config.wifi.infra.passphrase\" type=\"Password\">\n                <esf:value>MyPasswordBase64</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.ignoreSSID\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.mode\" type=\"String\">\n                <esf:value>INFRA</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.pingAccessPoint\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.driver\" type=\"String\">\n                <esf:value>nl80211</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.securityType\" type=\"String\">\n                <esf:value>SECURITY_WPA2</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#enable-a-cellular-interface","title":"Enable a cellular interface","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>  \n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.type\" type=\"String\">\n                <esf:value>MODEM</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.idle\" type=\"Integer\">\n                <esf:value>95</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.1-1.config.password\" type=\"Password\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.pdpType\" type=\"String\">\n                <esf:value>IP</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ipAddress\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.maxFail\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.authType\" type=\"String\">\n                <esf:value>NONE</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.lcpEchoInterval\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.activeFilter\" type=\"String\">\n                <esf:value>inbound</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.lcpEchoFailure\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.diversityEnabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.resetTimeout\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.gpsEnabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.persist\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dialString\" type=\"String\">\n                <esf:value>atd*99***2#</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.apn\" type=\"String\">\n                <esf:value>web.omnitel.it</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-configuration/#create-a-vlan","title":"Create a VLAN","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?><esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>wlan0,lo,ens33,vlanFull,ens34</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.id\" type=\"Integer\">\n                <esf:value>41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.flags\" type=\"Integer\">\n                <esf:value>1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.ingress\" type=\"String\">\n                <esf:value>1:2,3:4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.egress\" type=\"String\">\n                <esf:value>5:6</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.41.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/network-failover/","title":"Network Failover","text":"

    For devices configured to use NetworkManager, it is possible to configure multiple WAN interfaces and a basic network failover functionality.

    As in the picture below, the Kura UI allows for multiple WAN interfaces to be defined. Each WAN interface can be configured with a WAN Priority. WAN Priority is used to determine which interface will be selected for primary WAN. In the case where the primary WAN interface loses connection, then the next highest priority interface is assigned.

    Kura uses NetworkManager's implementation to achieve network failover (see NetworkManager). Lower values correspond to higher priority. Allowed values range from -1 to 2147483647. Value -1 means that the metric is chosen automatically based on the device type (see NetworkManager DBUS properties).

    To observe changes to the applied configuration, use the following command on your device's shell:

    route -n\n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n0.0.0.0         192.168.2.1     0.0.0.0         UG    100    0        0 eth0\n172.16.1.0      0.0.0.0         255.255.255.0   U     600    0        0 wlan0\n172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0\n192.168.2.0     0.0.0.0         255.255.255.0   U     100    0        0 eth0\n

    The metric flag will correspond to the set WAN Priority. NetworkManager will always prioritize lower metric routes.

    "},{"location":"gateway-configuration/network-failover/#operating-modes","title":"Operating modes","text":"

    The NetworkManager failover mechanism can work at two different levels:

    • by detecting disruptions at physical level (i.e. an ethernet cable that disconnects);
    • by performing a connectivity check to an upstream URI.

    NetworkManager brings a network interface down when it detects the loss of its physical link. In such case, the next highest priority interface is selected as the main one.

    When the connectivity check fails, NetworkManager penalizes the metric of that interface in the routing table. NetworkManager continues to perform connectivity checks over all the other interfaces. As soon as the connectivity is restored over a previously failing interface, the metric is also restored to the original value and the routing table goes back to the original state.

    "},{"location":"gateway-configuration/network-failover/#configuring-the-connectivity-check","title":"Configuring the connectivity check","text":"

    The connectivity check is enabled by default in Kura and is configured to probe the connection to http://network-test.debian.org/nm every 60 seconds. To set a specific URI and a different interval edit /etc/NetworkManager/conf.d/99kura-nm.conf (reference NetworkManager):

    [connectivity]\nuri=http://network-test.debian.org/nm\ninterval=60\nresponse=\"NetworkManager is online\"\n

    The minimun interval is 60 seconds, if no interval is specified it defaults to 300 seconds.

    The response should match what the URI is returning when probed. Some examples of web pages with NetworkManager responses:

    URI Response http://network-test.debian.org/nm NetworkManager is online https://fedoraproject.org/static/hotspot.txt OK http://nmcheck.gnome.org/check_network_status.txt NetworkManager is online https://www.pkgbuild.com/check_network_status.txt NetworkManager is online

    To disable the connectivity check feature:

    • remove the [connectivity] section from the configuration file; or
    • set interval=0; or
    • remove uri; or
    • set an empty URI, like uri=
    "},{"location":"gateway-configuration/network-hardware/","title":"Hardware Tab","text":"

    Each interface in the Network page contains inside its specific box the tab Hardware: this collects the most significant information about the current state of the interface.

    In this way the user can have a quick and complete view of what are the settings and physical specifications of a network interface.

    These information are respectively:

    • Status: the current state of the network interface, from the framework point of view. Some examples of status are UNMANAGED, ACTIVATED, FAILED, UNKNOWN.
    • Name: name of the interface
    • Type: physical interface type like WIFI or ETHERNET
    • Hardware Address: MAC address of the interface
    • Serial #: serial number of the hardware interface
    • Driver: Linux driver used for the interface
    • Version: Linux driver version
    • Firmware: firmware version of the interface module
    • MTU: Maximum Transmission Unit, is the measurement in bytes of the largest data packets that the interface can accept
    • USB Device: path of the usb device
    • Received Signal Strength (dBm): power of the received radio signal from a wireless interface
    • Current WLAN Channel: actual channel used by the wifi connection

    These entries are filled only if the associated data is available. This means, for example, that no information will be shown under Current WLAN Channel if the interface is of Ethernet type. Conversely, if the interface is Wifi type and no information is shown, it is possible that there has been some problem with the configuration or connection of the interface.

    So this tab, could be also an intuitive way to check if the interface is working properly or not.

    "},{"location":"gateway-configuration/network-threat-manager/","title":"Network Threat Manager","text":"

    Eclipse Kura provides a set of features to detect and prevent network attacks. The Security section in the Gateway Administration Console shows the Network Threat Manager tab where is it possible to activate these functions.

    Warning

    The Network Threat Manager tab is not available for the No Network version of Eclipse Kura.

    "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection","title":"Flooding protection","text":"

    The flooding protection function is used to prevent DDos (Distributed Denial-of-Service) attacks using the firewall. When enabled, the feature adds a set of firewall rules to the mangle table.

    "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection-for-ipv4","title":"Flooding protection for IPv4","text":"

    The following rules are added to the mangle table and they are implemented to block invalid or malicious network packets:

    iptables -A prerouting-kura -m conntrack --ctstate INVALID -j DROP\niptables -A prerouting-kura -p tcp ! --syn -m conntrack --ctstate NEW -j DROP\niptables -A prerouting-kura -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags SYN,RST SYN,RST -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,RST FIN,RST -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,ACK FIN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,URG URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,FIN FIN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,PSH PSH -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL ALL -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL NONE -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP\niptables -A prerouting-kura -p icmp -j DROP\niptables -A prerouting-kura -f -j DROP\n

    To further filter the incoming TCP fragmented packets, specific system configuration files are configured. The flooding.protection.enabled property is used to enable the feature.

    "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection-for-ipv6","title":"Flooding protection for IPv6","text":"

    The same rules applied to the IPv4 are used for preventing attack on IPv6. In addition, some rules are implemented to drop specific IPv6 headers and limit the incoming ICMPv6 packets. Moreover, the incoming TCP fragmented packets are dropped configuring specific system files.

    The following rules are applied to the mangle table:

    ip6tables -A prerouting-kura -m conntrack --ctstate INVALID -j DROP\nip6tables -A prerouting-kura -p tcp ! --syn -m conntrack --ctstate NEW -j DROP\nip6tables -A prerouting-kura -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags SYN,RST SYN,RST -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,RST FIN,RST -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,ACK FIN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,URG URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,FIN FIN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,PSH PSH -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL ALL -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL NONE -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP\nip6tables -A prerouting-kura -p ipv6-icmp -m ipv6-icmp --icmpv6-type 128 -j DROP\nip6tables -A prerouting-kura -p ipv6-icmp -m ipv6-icmp --icmpv6-type 129 -j DROP\nip6tables -A prerouting-kura -m ipv6header --header dst --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header hop --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header route --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header frag --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header auth --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header esp --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header none --soft -j DROP\nip6tables -A prerouting-kura -m rt --rt-type 0 -j DROP\nip6tables -A output-kura -m rt --rt-type 0 -j DROP\n

    Also in this case, to enable the feature and add the rules to the firewall, the flooding.protection.enabled.ipv6 property has to be set to true. If the device doesn't support IPv6, this property is ignored.

    Warning

    To recover the device state when the IPv6 flooding protection feature is disabled, a reboot is required. So, to disable the feature, set the flooding.protection.enabled.ipv6 property to false tha reboot the device.

    "},{"location":"gateway-configuration/ssl-configuration/","title":"SSL Configuration","text":"

    A SSL Service instance manages the configuration of the SSL connections. It uses the associated KeystoreService to access the trust certificates, private keys pairs needed to setup a SSL connection. It also enforces best practices that are not enabled by default in the Java VM, such as, enabling hostname verification, disabling the legacy SSL-2.0-compatible Client Hello, and disabling the Nagle algorithm.

    The list of all available SSL Service instances is available in the SSL Configuration tab of the Security section, accessible only by the users with the corresponding permission.

    By default, the framework creates a SSLManagerService instance with the org.eclipse.kura.ssl.SslManagerService PID. This instance is generally used by all the core services of the framework.

    A new SSL Service instance can be created using the New Button, by specifying the desired factory and the Service PID that will be associated with this new instance.

    An instance of the default org.eclipse.kura.ssl.SslManagerService factory has the following configuration parameters:

    • KeystoreService Target Filter - specifies, as an OSGi target filter, the pid of the KeystoreService used to manage the SSL key store (Required field).

    • Truststore Target Filter - specifies, as an OSGi target filter, the pid of the KeystoreService used to manage the SSL trust store. If the target service cannot be found, the service configured with the Keystore Target Filter parameter will be used as truststore.

    • ssl.default.protocol - defines the allowed SSL protocol.

    • ssl.hostname.verification - indicates whether hostname verification is enabled or disabled.

    • ssl.default.cipherSuites - defines the allowed cipher suites.

    By selecting the Select available targets button, the user can associate the SSLManagerService instance with the corresponding KeystoreService instances available in the framework runtime.

    "},{"location":"gateway-configuration/ssl-configuration/#server-ssl-certificate","title":"Server SSL Certificate","text":"

    The device requires a public key in its trust store in order to authenticate the broker and be able to setup an SSL connection. Kura is distributed with a pre-initialized SSL keystore that contains only some of the major Certification Authorities (CA) public keys.

    If the broker uses a certificate signed by a different CA, or uses an auto-signed certificate, the system administrator must setup Kura with the correct certificates used to trust the remote cloud broker.

    The inclusion of public certificates is accomplished with the Server SSL Certificate feature. To do so, the SSL Certificates form must be completed by providing a certificate or a certificates chain to be trusted and defining the alias used to register this new data in the device's trust store.

    With this feature, when the device tries to instantiate an SSL connection with the broker, it receives the broker's public key chain. The SSL connection is secured only if the received chain is trusted. This connection can only happen if one of the certificates that compose the broker chain are available in the device's trust store.

    When instantiating the device's trust store, the user decides whether to add a single certificate (leaf or CA certificate) or the full chain.

    In the latter case, the chain should be provided by specifying the leaf certificate, followed by the CA certificate that is signing it, and so on, until the root CA is reached. An example of this scenario is depicted in the following image:

    "},{"location":"gateway-configuration/ssl-configuration/#device-ssl-certificate-mutual-authentication","title":"Device SSL Certificate & Mutual Authentication","text":"

    Mutual authentication is a technique that allows authentication of the device that is connecting to the broker. The form available in Certificates List may be used to specify the keys needed to enable mutual authentication.

    This authentication may be accomplished by specifying a couple of certificates (private and public keys) to be used by the client device to authenticate itself to the broker. This authentication is possible because the broker has the root CA certificate that has been used to sign the couple held by the device. In this way, the authenticity of the couple of certificates held by the device may be verified, and therefore, enable the two communicating parts (the broker and the device) to trust each other.

    To enable mutual authentication, the user must complete the form with a well-formed key pair (public and private), and with an alias value that corresponds with the account name used to connect to the broker.

    "},{"location":"gateway-configuration/ssl-configuration/#key-pair-generation","title":"Key Pair Generation","text":"

    The keys may be generated using specific software, such as OpenSSL or Keytool. This section describes how to use OpenSSL to generate a couple of private and public keys.

    The private key may be created using the following command:

    openssl genrsa -out certsDirectory/certs/certificate.key 1024\n

    This command creates a new, 1024-bit private key in the specified path. This key is used to generate a Certificate Signing Request (CSR) file, which is used by a CA to authenticate the certificate's creator. A CSR file is created with OpenSSL using the following command:

    openssl req -new -key certsDirectory/certs/certificate.key -out certsDirectory/crl/certificate.csr\n

    If the user is creating their own certificate chain, the CSR file may be signed using a personal CA. This process may be accomplished using OpenSSL with the following command:

    openssl ca -config certsDirectory/openssl.cnf -days 3650 -keyfile certsDirectory/ca/ca.key -cert certsDirectory/ca/ca.pem -out certsDirectory/certs/certificate.pem -infiles certsDirectory/crl/certificate.csr\n

    The parameters are defined as follows:

    • -config: specifies the OpenSSL configuration file that must be used to sign the certificate.

    • -days: specifies how long the certificate is valid.

    • -keyfile and -cert: allow the specification of the CA that will sign the CSR file.

    • -out: identifies the location and the name of the signed certificate that will be created.

    • -infiles: identifies the location of the CSR file that has to be signed.

    Tip

    The private key may not be placed into the Kura Gateway Administration Console without a format conversion.

    OpenSSL offers the following command to convert the input private key to a non-encrypted PKCS#8 format that may be processed by the Kura code:

    openssl pkcs8 -topk8 -inform PEM -outform PEM -in inPrivateKey.key -out outKey.pem -nocrypt\n
    "},{"location":"gateway-configuration/vlan-configuration/","title":"VLAN Configuration","text":"

    A VLAN, or Virtual Local Area Network, is a network segmentation technology that allows a single physical network to be logically divided into multiple isolated networks. These virtual networks operate as if they are independent, even though they share the same physical infrastructure. This is achieved via a VLAN ID, or VLAN tag, a numerical label added to network frames to identify the specific Virtual Local Area Network (VLAN) to which they belong. It's a critical component in VLAN technology, allowing network switches and routers to differentiate and route traffic within a VLAN. VLAN tags are added to the Ethernet frame's header, indicating which virtual network a data packet should be directed to when it traverses the physical network infrastructure. Therefore, VLANs must also be supported and configured on the network equipment a device is connected to.

    A VLAN can be named freely, as long as it's 15 or less characters. A typical VLAN naming format is physicalInterfaceName.vlanId (eg. a vlan with id 100 on the interface eth0 would be named eth0.100).

    This is achieved by NetworkManager by creating a virtual device bound to the underlying physical interface when Kura sets up a new VLAN connection.

    "},{"location":"gateway-configuration/vlan-configuration/#vlan-configuration-via-kura-snapshot-upload","title":"VLAN Configuration via Kura Snapshot upload","text":"

    Currently, VLAN configuration is supported via uploading snapshot.xml fragments.

    Warning

    When creating a new VLAN be sure to include the net.interfaces parameter, containing both the previously existing network interfaces, either virtual or physical, and the name of the new VLAN to be created.

    "},{"location":"gateway-configuration/vlan-configuration/#basic-vlan-configuration-example","title":"Basic VLAN configuration example","text":"

    The following example creates a VLAN with ID 40 over the ethernet interface ens33, naming it ens33.40, using a predefined IP address, enabled for LAN.

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>lo,ens33,ens34,ens33.40</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.vlan.id\" type=\"Integer\">\n                <esf:value>40</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.address\" type=\"String\">\n                <esf:value>10.0.55.37</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/vlan-configuration/#complete-vlan-configuration-example","title":"Complete VLAN configuration example","text":"

    The following example creates a VLAN with ID 41 over the ethernet interface ens33, naming it ens33.41, using a predefined IP address, enabled for WAN. This example also sets the 'flags' and 'traffic priority' optional parameters as described in Network Manager API documentation.

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>lo,ens33,ens34,ens33.41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.gateway\" type=\"String\">\n                <esf:value>192.168.41.254</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.dnsServers\" type=\"String\">\n                <esf:value>8.8.8.8</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.id\" type=\"Integer\">\n                <esf:value>41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.41.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.flags\" type=\"Integer\">\n                <esf:value>1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.ingress\" type=\"String\">\n                <esf:value>1:2,3:4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.egress\" type=\"String\">\n                <esf:value>5:6</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/vlan-configuration/#vlan-management","title":"VLAN Management","text":"

    Once a VLAN is created it can be managed via the Kura UI just like any other Ethernet interface.

    Warning

    Setting a VLAN status to \"Disabled\" deletes its configuration in NetworkManager and the related virtual interface from the system. Although it will is no longer be visible on the UI, all the configurations are left in Kura. Therefore the VLAN can be restored by setting the net.interface.<interface>.config.ip4.status to netIPv4StatusEnabledLAN or netIPv4StatusEnabledWAN via snapshot upload, then resume configuration via UI.

    As an example, the configuration to reactivate a disabled VLAN named ens33.40 would be as follows:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
    "},{"location":"gateway-configuration/web-console-configuration/","title":"Web Console Configuration","text":"

    The Web Console exposes a set of configuration parameters that can be used to increase the overall UI security. The Web Console configuration can be accessed in the Security section.

    "},{"location":"gateway-configuration/web-console-configuration/#web-server-entry-point","title":"Web Server Entry Point","text":"

    This parameter allows to configure the relative path that the user will be redirected to when accessing http(s)://gateway-ip/. Note: this parameter does not change the Kura Web UI relative path, that is always /admin/console. The default value set is /admin/console

    "},{"location":"gateway-configuration/web-console-configuration/#session-max-inactivity-interval","title":"Session max inactivity interval","text":"

    The session max inactivity interval in minutes. If no interaction with the Web UI is performed for the value of this parameter in minutes, a new login will be requested. The default value set is 15 minutes

    "},{"location":"gateway-configuration/web-console-configuration/#access-banner-enabled","title":"Access Banner Enabled","text":"

    For security reasons, it may be needed to display to the user a banner that describes the intended system use before authenticating.

    Once enabled and configured, the Kura Web UI will display a banner before every access attempt, as depicted in the image below.

    "},{"location":"gateway-configuration/web-console-configuration/#password-management","title":"Password Management","text":"

    This section is related to the definition of required parameters that must be respected when defining a new password, for example when a user changes its password at first access.

    "},{"location":"gateway-configuration/web-console-configuration/#minimum-password-length","title":"Minimum password length","text":"

    The minimum length to be enforced for new passwords. Set to 0 to disable. The default value set is 8 characters

    "},{"location":"gateway-configuration/web-console-configuration/#require-digits-in-new-password","title":"Require digits in new password","text":"

    If set to true, new passwords will be accepted only if containing at least one digit. The default value is false

    "},{"location":"gateway-configuration/web-console-configuration/#require-special-characters-in-new-password","title":"Require special characters in new password","text":"

    If set to true, new passwords will be accepted only if containing at least one non alphanumeric character The default value is false

    "},{"location":"gateway-configuration/web-console-configuration/#require-uppercase-and-lowercase-characters-in-new-passwords","title":"Require uppercase and lowercase characters in new passwords","text":"

    If set to true, new passwords will be accepted only if containing both uppercase and lowercase alphanumeric characters. The default value is false

    "},{"location":"gateway-configuration/web-console-configuration/#allowed-ports","title":"Allowed ports","text":"

    If set to a non empty list, Web Console access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. It is needed for the end user to make sure that the allowed ports are open in HttpService and Firewall configuration.

    "},{"location":"gateway-configuration/web-console-configuration/#authentication-method-password-enabled","title":"Authentication Method \"Password\" Enabled","text":"

    Defines whether the \"Password\" authentication method is enabled or not. The default value is true

    "},{"location":"gateway-configuration/web-console-configuration/#authentication-method-certificate-enabled","title":"Authentication Method \"Certificate\" Enabled","text":"

    Defines whether the \"Certificate\" authentication method is enabled or not The default value is true

    "},{"location":"gateway-configuration/web-console-configuration/#sslmanagerservice-target-filter","title":"SslManagerService Target Filter","text":"

    It is possible to specify the SslManagerService containing the certificates truststore required to establish a new https connection, this is needed, for example, for fetching package descriptions from Eclipse Marketplace. The default target is the org.eclipse.kura.ssl.SslManagerService

    "},{"location":"gateway-configuration/wifi-configuration-8021x/","title":"Wi-Fi 802.1x Configuration","text":"

    To enable the Enterprise Wi-Fi, the Wireless Security property in the Wireless tab has to be set to WPA2/WPA3-Enterprise. This feature is available only if the Wireless Mode is Station Mode. The following is a list of currently supported 802.1x authentication methods.

    • TTLS-MSCHAPv2
    • PEAP-MSCHAPv2
    • EAP-TLS
    "},{"location":"gateway-configuration/wifi-configuration-8021x/#ttls-mschapv2","title":"TTLS-MSCHAPv2","text":"
    1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
    2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise
    3. select the 802.1x tab
    4. Set Enteprise EAP -> TTLS
    5. Set Inner Authentication -> MSCHAPV2
    6. Set Identity (Username)
    7. Set Password
    8. Press 'Apply'

    The configuration should look like the following:

    "},{"location":"gateway-configuration/wifi-configuration-8021x/#peap-mschapv2","title":"PEAP-MSCHAPv2","text":"
    1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
    2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise
    3. select the 802.1x tab
    4. Set Enteprise EAP -> PEAP
    5. Set Inner Authentication -> MSCHAPV2
    6. Set Identity (Username)
    7. Set Password
    8. Press 'Apply'

    The configuration should look like the following:

    "},{"location":"gateway-configuration/wifi-configuration-8021x/#eap-tls","title":"EAP-TLS","text":"

    To connect via EAP-TLS you will need the following items in unencrypted PEM format:

    • Certificate Authority (CA) Certificate
    • Client Certificate + Private Key (PKCS8)
    "},{"location":"gateway-configuration/wifi-configuration-8021x/#enrolling-secrets-in-the-keystore-service","title":"Enrolling secrets in the Keystore service.","text":"
    1. Navigate to Security under the System tab.
    2. Under the Keystore Configuration add a new keystore, and keep note of the name.
    3. After the Keystore is created, be sure to change the path to a persistent directory.
    4. Navigate to the Certificate List and create a new Certificate. Insert the PEM and Apply, keep note of the name.
    5. Now press add and create a new Private Key. Insert both the certificates in the PEM in the dialogue and press apply. keep note of the name.
    "},{"location":"gateway-configuration/wifi-configuration-8021x/#wifi-setup","title":"Wifi Setup","text":"
    1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
    2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise.
    3. Select the 802.1x tab.
    4. Set Enteprise EAP -> TLS.
    5. Set Identity (Username).
    6. Set Keystore Pid to the name of the keystore created above.
    7. Set Certificate Authority Certificate (CA-Cert) to the name of the certificate created above.
    8. Set the Client Private Key to the name of the Private Key created above.

    When completed the Wi-Fi configuration should look like the following:

    "},{"location":"gateway-configuration/wifi-configuration/","title":"Wi-Fi Configuration","text":"

    From a configuration standpoint, the Wi-Fi interface (e.g., wlan0) may be viewed as an extension of Ethernet. In addition to the IPv4, IPv6 and DHCPv4 & NAT configuration tabs, it has the Wireless tab that allows for the configuration of wireless settings. These configuration options are described below.

    Warning

    Before using wifi make sure that you have correctly set the Regulatory Domain on the gateway. You can check the current configuration using the iw reg get command. To set the Regulatory Domain please refer to the specific section in the Gateway Configurations.

    "},{"location":"gateway-configuration/wifi-configuration/#wireless-configuration","title":"Wireless Configuration","text":"

    The Wireless tab contains the following configuration parameters:

    • Wireless Mode: defines the mode of operation.

      • Access Point: creates a wireless access point.
      • Station Mode: connects to a wireless access point.
    • Network Name: specifies the Service Set Identifier (SSID).

      • In Access Point mode, this is the SSID that identifies this wireless network.
      • In Station mode, this is the SSID of a wireless network to connect to.
    • Band: defines the frequency band to use

      • 2.4GHz: use 2.4GHz band
      • 5GHz: use 5GHz band
      • 2.4GHz/5GHz: use either in 2.4Ghz or 5Ghz depending on the choosen channel
    • Wireless Security: sets the security protocol for the wireless network.

      • None: No Wi-Fi security
      • WEP: Wired Equivalent Privacy
      • WPA: Wi-Fi Protected Access
      • WPA2: Wi-Fi Protected Access II
      • WPA2/WPA3-Enterprise: Wi-Fi Protected Access II & III with 802.1x. Please see here for further details. This fearture is available only in station mode
    • Wireless Password: sets the password for the wireless network.

      • WEP: 64-bit or 128-bit encryption key
      • WPA/WPA2: pre-shared key
    • Verify Password: sets the password verification field.

      • In Access Point mode, allows the wireless password to be retyped for verification.
      • In Station mode, this field is disabled.
    • Pairwise Ciphers: lists accepted pairwise (unicast) ciphers for WPA/WPA2.

      • In Access Point mode, this option is disabled.
      • In Station mode,
        • CCMP (AES-based encryption mode with strong security)
        • TKIP (Temporal Key Integrity Protocol)
        • CCMP and TKIP
    • Group Ciphers: lists accepted group (broadcast/multicast) ciphers for WPA/WPA2.

      • In Access Point mode, this option is disabled.
      • In Station mode,
        • CCMP (AES-based encryption mode with strong security)
        • TKIP (Temporal Key Integrity Protocol)
        • CCMP and TKIP
    • Bgscan Module: requests background scans for the purpose of roaming within an ESS (i.e., within a single network block with all the APs using the same SSID).

      • None: background scan is disabled
      • Simple: periodic background scans based on signal strength
      • Learn: learn channels used by the network and try to avoid bgscans on other channels
    • Bgscan Signal Strength Threshold: defines a threshold (in dBm) that determines which one of the following two parameters (i.e., Short Interval or Long Interval) will be effective.

    • Bgscan Short Interval: defines the interval between background scans (in seconds) if the actual signal level of the currently connected access point is worse than signal_strength.

    • Bgscan Long Interval: defines the interval between background scans (in seconds) if the actual signal level of the currently connected access point is better than signal_strength.

    • Ping Access Point & renew DHCP lease if not reachable: enables pinging the access point after connection is established.

      • In Access Point mode, this option is disabled.
      • In Station mode, if set to true, the unit will ping the access point and attempt to renew the DHCP lease if the access point is not reachable.
    • Ignore Broadcast SSID: operates as follows if set to true:

      • In Access Point mode, sends an empty SSID in beacons and ignores probe request frames that do not specify full SSID.
      • In Station mode, does not scan for the SSID before attempting to associate.
    • Channels list: allows the selection of desired channel frequencies. The availability of the desired frequency is subject to the Regdom set on the device. For a list of limitations in different countries you can consult the following page: List of WLAN channels. Channels marked as No Irradiation and Radar Detection can be used only if DFS (Dynamic Frequency Selection) is supported by the Wi-Fi chip.

      • In Access Point mode, only one channel may be selected.
      • In Station mode, the list of available channels depends on the selected Radio Mode. The selected radio mode also affects the ability to select a network in the scan window (if the channel associated with the network is not enabled in the regulatory domain an error message will be shown).

    "},{"location":"gateway-configuration/wifi-configuration/#wi-fi-station-mode-configuration","title":"Wi-Fi Station Mode Configuration","text":"

    In addition to the options described above, the Wireless configuration display the Access Point Scan button that help to configure Wi-Fi in the Station mode.

    • Access Point Scan: clicking this button triggers access point scan operations. Upon a successful scan, a table containing access points within range is presented. This table contains the following information:

      • SSID
      • MAC Address
      • Signal Strength (in dBm)
      • Channel
      • Frequency
      • Security

      If you select one of these access points, respective wireless controls (i.e., Network Name, Wireless Security, and Channel) are filled with information obtained during the scan operation. The Force Wireless Network Scan button triggers a manual scan for available access points.

    "},{"location":"getting-started/docker-quick-start/","title":"Docker Quick Start","text":""},{"location":"getting-started/docker-quick-start/#installation","title":"Installation","text":"

    Eclipse Kura is also available as a Docker container available in Docker Hub.

    To download and run, use the following command:

    docker run -d -p 443:443 -t eclipse/kura\n

    This command will start Kura in background and the Kura Web Ui will be available through port 443.

    Once the image is started you can navigate your browser to https://localhost and log in using the credentials admin : admin.

    "},{"location":"getting-started/docker-quick-start/#command-toolbox","title":"Command Toolbox","text":"

    Following, a set of useful Docker command that can be used to list and manage Docker containers. For more details on Docker commands, please reference the official Docker documentation

    "},{"location":"getting-started/docker-quick-start/#list-docker-images","title":"List Docker Images","text":"

    To list all the installed Docker images run:

    docker images\n
    "},{"location":"getting-started/docker-quick-start/#list-running-docker-containers","title":"List Running Docker Containers","text":"

    To list all the available instances (both running and powered off) run:

    docker ps -a\n
    "},{"location":"getting-started/docker-quick-start/#startstop-a-docker-container","title":"Start/Stop a Docker Container","text":"
    docker stop <container id>\ndocker start <container id>\n

    where <container id> is the instance identification number.

    "},{"location":"getting-started/install-kura/","title":"Install Kura","text":"

    Eclipse Kura\u2122 is provided using a Debian Linux package. Visit the Kura download page to find the correct installation file for the target system.

    "},{"location":"getting-started/install-kura/#installer-types","title":"Installer types","text":"

    Several installers can be found on such page, and they fall into one of the following categories:

    1. standard installers, like kura-6.0.0_generic-arm32_installer.deb; and
    2. installers with suffix nn, like kura-6.0.0_generic-arm32-nn_installer.deb

    Profiles of types (1) ship a Kura version with networking functionalities. In particular, they can be installed on targets with NetworkManager, a commonly available tool for managing Linux networking. Kura leverages this tool for networking functionalities. Refer to the Kura installers section for further information.

    Installers of type (2) with the suffix nn are No Networking profiles that do not bundle the Kura Network Manager: all the network configurations need to be done outside of Kura. Functionalities missing in NN profiles compared to the full Kura profiles:

    • Networking interfaces management
    • Firewall configuration management
    • Network Threat management
    "},{"location":"getting-started/install-kura/#kura-installers","title":"Kura installers","text":"

    A user can deploy Kura on a target system using the installer tailored for the device architecture. The installer file looks like:

    kura-<kura-version>_generic-<arch>_installer.deb/rpm\n

    where <arch> is one of the supported architectures: x86_64, arm32, and arm64. Kura can work on systems that have available the dependencies listed in the Kura dependencies section, and that have at least one physical ethernet interface.

    "},{"location":"getting-started/install-kura/#java-heap-memory-assignment","title":"Java Heap Memory Assignment","text":"

    The Eclipse Kura\u2122's installer incorporates an adaptive Heap Memory allocation system during installation. The allocation follows a formula based on your gateway's available memory. If your gateway has less than 1024MB of RAM, Kura will set -Xms and -Xmx to 256MB. For gateways with more than 1024MB, one quarter of the total RAM will be assigned to -Xms and -Xmx.

    "},{"location":"getting-started/install-kura/#initial-network-configuration","title":"Initial network configuration","text":"

    During the installation of Eclipse Kura with network management support, the initial network configuration will be generated dynamically. The existing wired and wireless network interface names are detected and sorted in ascending lexicographic order at the installation.

    If only one ethernet interface is detected (e.g. eth0), it will be configured as follows:

    Interface Configuration Ethernet interface (e.g. eth0) - Status: Enabled for WAN- Configure: Using DHCP

    If multiple ethernet interfaces are detected, instead, the first two interfaces will be configured as presented in the table below. The other ethernet interfaces will be disabled.

    Interface Configuration First Ethernet interface (e.g. eth0) - Status: Enabled for LAN- Configure: Manually- IP address: 172.16.0.1- Router Mode: DHCP and NAT Second Ethernet interface (e.g. eth1) - Status: Enabled for WAN- Configure: Using DHCP

    Finally, if a wireless interface (e.g. wlan0) is detected, it will configured as shown below. The other wireless interfaces will be disabled.

    Interface Configuration Wireless interface (e.g. wlan0) - Status: Enabled for LAN- Configure: Manually- IP address: 172.16.1.1- Passphrase: testKEYS- Router Mode: DHCP and NAT

    For example, if the system contains the following interfaces: wlp2s0, wlp3s0, enp3s0, eno1, ens2; then eno1 will be enabled for LAN with a DHCP server, enp3s0 will be enabled for WAN in DHCP client mode, wlp2s0 will be configured as an AP, and all other network interfaces will be disabled.

    Warning

    On systems that do not use systemd's predictable interface naming scheme (see Freedesktop reference) the primary network interface name might change whenever a re-enumeration is triggered (for example, after a reboot or after plugging in an external network adapter).

    The advice is to install Kura on systems that use a reliable naming convention for network interfaces.

    Systemd consistent network interface naming assigns the name prefix based on the physical location of the device, see Understanding the Predictable Network Interface Device Names for further reference.

    "},{"location":"getting-started/install-kura/#initial-firewall-configuration","title":"Initial firewall configuration","text":"

    Similarly to the initial network configuration, the initial firewall setup is adapted based on the network interface detected on the system. In case of multiple ethernet and wireless interfaces, the configuration will be as shown in the screenshot below.

    If the wireless interface is not present, the firewall entries for the wlan0 are dropped.

    Please note that installing Eclipse Kura with network configuration support will replace the current network and firewall configuration with the one shown above.

    "},{"location":"getting-started/install-kura/#other-kura-services","title":"Other Kura services","text":"

    Eclipse Kura\u2122 do not contain gateway specific customizations, this implies that the values of some configuration parameters may be incorrect and/or missing and must be manually filled after installation. For example the user might want to:

    • Configure the other network interfaces, if any.
    • Setup additional firewall rules.
    • Edit the /opt/eclipse/kura/framework/jdk.dio.properties with the correct GPIO mappings. By default this file is empty.
    "},{"location":"getting-started/install-kura/#kura-dependencies","title":"Kura dependencies","text":"

    To have all the Kura features working, the following dependencies are required:

    • General: setserial, zip, gzip, unzip, procps, usbutils, socat, gawk, sed, inetutils-telnet.
    • Security: polkit or policykit-1, ssh or openssh, openssl, busybox, openvpn.
    • Bluetooth: bluez or bluez5, bluez-hcidump or bluez5-noinst-tools.
    • Time: ntpdate, chrony, chronyc, cron or cronie.
    • Networking: network-manager or networkmanager, bind9 or bind, dnsmasq or isc-dhcp-server or (dhcp-server and dhcp-client), iw, iptables, modemmanager, hostapd, wpa-supplicant, ppp, iproute2.
    • Logs: logrotate.
    • Gps: gpsd.
    • Python: python3.
    • Java: openjdk-17-jre-headless or temurin-17-jdk or openjdk-8-jre-headless or temurin-8-jdk.
    • Others: dos2unix
    "},{"location":"getting-started/install-kura/#reference-devices","title":"Reference devices","text":"

    Eclipse Kura\u2122 has been tested on the following devices and provides full configuration of all the available interfaces and GPIO mappings.

    Device Architecture OS Raspberry Pi 3/4 arm32 Raspberry Pi OS \"Bookworm\" Raspberry Pi 3/4 arm64 Raspberry Pi OS \"Bookworm\" Raspberry Pi 3/4 arm64 Ubuntu 20.04 ZimaBoard/Blade x86_64 TBD NVIDIA Jetson AGX Orin\u2122 arm64 TBD

    Check out the quick start guides for the detailed installation steps and set-up procedures:

    • Raspberry Pi - Raspberry Pi OS Quick Start
    • Raspberry Pi - Ubuntu 20 Quick Start
    • Docker Quick Start
    • Nvidia Jetson AGX Orin Quick Start
    • ZimaBoard/Blade Quick Start
    "},{"location":"getting-started/nvidia-jetson-orin-quick-start/","title":"NVIDIA Jetson AGX Orin\u2122 - Quick Start","text":""},{"location":"getting-started/nvidia-jetson-orin-quick-start/#overview","title":"Overview","text":""},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/","title":"Raspberry Pi - Raspberry Pi OS Quick Start","text":""},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#overview","title":"Overview","text":"

    This section provides Eclipse Kura\u2122 quick installation procedures for the Raspberry Pi and the Kura development environment.

    Warning

    This quickstart will install the version of Kura with the administrative web UI and network configuration support but not CAN bus support. For more information on this please visit the Eclipse Kura download page

    This quickstart has been tested using the latest Raspberry Pi OS 32 and 64 bits images which are available for download through the official Raspberry Pi foundation site and the Raspberry Pi Imager.

    Warning

    Recent versions of Raspberry Pi OS 32 bit on Raspberry PI 4 will use by default a 64 bit kernel with a 32 bit userspace. This can cause issues to applications that use the result of uname -m to decide which native libraries to load (see https://github.com/raspberrypi/firmware/issues/1795). This currently affects for example the Kura SQLite database connector. It should be possible to solve by switching to the 32 bit kernel setting arm_64bit=0 in /boot/config.txt and restarting the device.

    For additional details on OS compatibility refer to the Kura\u2122 release notes.

    "},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#enable-ssh-access","title":"Enable SSH Access","text":"

    The ssh server is disabled by default on Raspbian images released after November 2016, in order to enable it follow the instructions available here.

    If you're using the Raspberry Pi Imager you can directly enable SSH before writing the operating system into the SD card by clicking on the \"setting\" icon.

    "},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#eclipse-kuratm-installation","title":"Eclipse Kura\u2122 Installation","text":"

    To install Eclipse Kura with its dependencies on the Raspberry Pi, perform the following steps:

    1. Boot the Raspberry Pi with the latest\u00a0Raspberry Pi OS image.

    2. Make sure your device is connected to the Internet. The best installation experience can be obtained when the device is cabled to the local network and the Internet. By default, the Raspberry Pi OS configures the ethernet interface eth0 in DHCP mode.

    3. Upgrade the system:

      sudo apt update\n
      sudo apt upgrade\n

      Tip

      Optional: Since version 5.3.0 Kura also supports Eclipse Temurin\u2122 as an alternative JVM. To install it you need to perform these additional steps:

      sudo apt-get install -y wget apt-transport-https gnupg\n
      sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
      sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
      sudo apt-get update\n
      sudo apt-get install temurin-17-jdk\n

    4. Download the Kura package with:

      wget http://download.eclipse.org/kura/releases/<version>/kura-<kura-version>_generic-<arch>_installer.deb\n

      Note: replace <version> in the URL above with the version number of the latest release (e.g. 5.5.0) and <arch> with your device architecture

    5. Install Kura with:\u00a0

      sudo apt-get install ./kura-<kura-version>_generic-<arch>_installer.deb\n
    6. For a correct configuration of the Wlan interface, it is necessary to set the Locale and the WLAN Country through the raspi-config command:

      sudo raspi-config\n

      From the raspi-config main menu select Localisation Options:

      Then modify the Locale and WLAN Country with with the proper settings for your location. For example, an user located in Italy could set the values as the ones in the table:

      Setting Value L1 Locale it_IT.UTF-8 UTF-8 L4 WLAN Country IT Italy
    7. (Optional) To correctly use the GPIO pins, the user is asked to update the jdk.dio.properties file with the proper configuration, based on its own device.

      This is required since the sysfs interface has been deprecated, and some OS distribution may have already suppressed it. Moreover, the kernel complains if a static base number is assigned to a GPIO controller: indeed, when it assigns the numbers automatically, it usually starts from 511. More information can be found here.

      In order to set the correct configuration the user can perform the following steps:

      • Execute on the device the command cat /sys/kernel/debug/gpio, looking for entries similar to gpio-ABC (GPIxx): from this information it is possible to retrieve which number the GPIO controller was assigned to by the OS (in this case the GPIO controller number xx is assigned with the number ABC). The image below represent an example of this file

      • Modify the /opt/eclipse/kura/framework/jdk.dio.properties with the number and controllers found in the previous step:
      573 = deviceType: gpio.GPIOPin, pinNumber:573, name:GPI02\n574 = deviceType: gpio.GPIOPin, pinNumber:574, name:GPIO3\n575 = deviceType: gpio.GPIOPin, pinNumber:575, name:GPIO4\n576 = deviceType: gpio.GPIOPin, pinNumber:576, name:GPIO5\n577 = deviceType: gpio.GPIOPin, pinNumber:577, name:GPIO6\n578 = deviceType: gpio.GPIOPin, pinNumber:578, name:GPIO7\n579 = deviceType: gpio.GPIOPin, pinNumber:579, name:GPIO8\n580 = deviceType: gpio.GPIOPin, pinNumber:580, name:GPIO9\n581 = deviceType: gpio.GPIOPin, pinNumber:581, name:GPIO10\n582 = deviceType: gpio.GPIOPin, pinNumber:582, name:GPIO11\n583 = deviceType: gpio.GPIOPin, pinNumber:583, name:GPIO12\n584 = deviceType: gpio.GPIOPin, pinNumber:584, name:GPIO13\n585 = deviceType: gpio.GPIOPin, pinNumber:585, name:GPIO14\n586 = deviceType: gpio.GPIOPin, pinNumber:586, name:GPIO15\n587 = deviceType: gpio.GPIOPin, pinNumber:587, name:GPIO16\n588 = deviceType: gpio.GPIOPin, pinNumber:588, name:GPIO17\n589 = deviceType: gpio.GPIOPin, pinNumber:589, name:GPIO18\n590 = deviceType: gpio.GPIOPin, pinNumber:590, name:GPIO19\n591 = deviceType: gpio.GPIOPin, pinNumber:591, name:GPIO20\n592 = deviceType: gpio.GPIOPin, pinNumber:592, name:GPIO21\n593 = deviceType: gpio.GPIOPin, pinNumber:593, name:GPIO22\n594 = deviceType: gpio.GPIOPin, pinNumber:594, name:GPIO23\n595 = deviceType: gpio.GPIOPin, pinNumber:595, name:GPIO24\n596 = deviceType: gpio.GPIOPin, pinNumber:596, name:GPIO25\n597 = deviceType: gpio.GPIOPin, pinNumber:597, name:GPIO26\n598 = deviceType: gpio.GPIOPin, pinNumber:598, name:GPIO27\n\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\nuart.UART = baudRate:19200, parity:0, dataBits:8, stopBits:1, flowControl:0\n

      You can also check your GPIO device configuration executing the command pinout

    8. Reboot the Raspberry Pi with:

      sudo reboot\n

      Kura starts on the target platform after reboot.

    9. Kura setups a local web ui that is available using a browser via:

      https://<device-ip>\n

      The browser will prompt the user to accept the connection to an endpoint with an untrusted certificate:

      Once trusted the source, the user will be redirected to a login page where the following credentials: username: admin password: admin

    "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/","title":"Raspberry Pi - Ubuntu 20 Quick Start","text":""},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#overview","title":"Overview","text":"

    This section provides Eclipse Kura\u2122 quick installation procedures for the Raspberry Pi.

    Warning

    This quickstart will install the version of Kura with the administrative web UI and network configuration support but not CAN bus support. For more information on this please visit the Eclipse Kura download page

    This quickstart has been tested using the latest Ubuntu 20.04.3 LTS Live Server for arm64 architecture flashed on the SD card through Raspberry Pi Imager.

    The official images can be also found on the Project Page. Further information on the Ubuntu installation for Raspberry Pi can be found here.

    Warning

    Please note that, at the time of this writing, only 64 bit OS image is supported.

    "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#enable-ssh-access","title":"Enable SSH Access","text":"

    On Ubuntu 20.04.3 the ssh access is enabled only for the standard ubuntu user. If you desire to remote login as root user, edit the file /etc/ssh/sshd_config (using the root permission) adding the line PermitRootLogin yes

    "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#eclipse-kuratm-installation","title":"Eclipse Kura\u2122 Installation","text":"

    To install Eclipse Kura with its dependencies on the Raspberry Pi, perform the following steps:

    1. Boot the Raspberry Pi with the latest\u00a0Ubuntu 20.04.3 LTS Server image.

    2. Make sure your device is connected to internet. By default, eth0 lan network interface is configured in DHCP mode.

    3. Upgrade the system:

      sudo apt update\n
      sudo apt upgrade\n

      Tip

      Optional: Since version 5.3.0 Kura also supports Eclipse Temurin\u2122 as an alternative JVM. To install it you need to perform these additional steps:

      sudo apt-get install -y wget apt-transport-https gnupg\n
      sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
      sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
      sudo apt-get update\n
      sudo apt-get install temurin-17-jdk\n

    4. Download the Kura package with:

      wget http://download.eclipse.org/kura/releases/<version>/kura-<kura-version>_generic-<arch>_installer.deb\n

      Note: replace <version> in the URL above with the version number of the latest release (e.g. 5.5.0) and <arch> with your device architecture

    5. Install Kura with:

      sudo apt install ./kura-<kura-version>_generic-<arch>_installer.deb\n

      All the required dependencies will be downloaded and installed.

    6. Set the right Wi-Fi regulatory domain based on your current world region editing the /etc/default/crda and adding the ISO 3166-1 alpha-2 code of your region.

    7. (Optional) Configure the GPIO replacing the content of the file /opt/eclise/kura/framework/jdk.dio.properties with the following text:

      0 = deviceType: gpio.GPIOPin, pinNumber:0, name:GPIO0\n1 = deviceType: gpio.GPIOPin, pinNumber:1, name:GPIO1\n2 = deviceType: gpio.GPIOPin, pinNumber:2, name:GPI02\n3 = deviceType: gpio.GPIOPin, pinNumber:3, name:GPIO3\n4 = deviceType: gpio.GPIOPin, pinNumber:4, name:GPIO4\n5 = deviceType: gpio.GPIOPin, pinNumber:5, name:GPIO5\n6 = deviceType: gpio.GPIOPin, pinNumber:6, name:GPIO6\n7 = deviceType: gpio.GPIOPin, pinNumber:7, name:GPIO7\n8 = deviceType: gpio.GPIOPin, pinNumber:8, name:GPIO8\n9 = deviceType: gpio.GPIOPin, pinNumber:9, name:GPIO9\n10 = deviceType: gpio.GPIOPin, pinNumber:10, name:GPIO10\n11 = deviceType: gpio.GPIOPin, pinNumber:11, name:GPIO11\n12 = deviceType: gpio.GPIOPin, pinNumber:12, name:GPIO12\n13 = deviceType: gpio.GPIOPin, pinNumber:13, name:GPIO13\n14 = deviceType: gpio.GPIOPin, pinNumber:14, name:GPIO14\n15 = deviceType: gpio.GPIOPin, pinNumber:14, name:GPIO15\n16 = deviceType: gpio.GPIOPin, pinNumber:16, name:GPIO16\n17 = deviceType: gpio.GPIOPin, pinNumber:17, name:GPIO17\n18 = deviceType: gpio.GPIOPin, pinNumber:18, name:GPIO18\n19 = deviceType: gpio.GPIOPin, pinNumber:19, name:GPIO19\n20 = deviceType: gpio.GPIOPin, pinNumber:20, name:GPIO20\n21 = deviceType: gpio.GPIOPin, pinNumber:21, name:GPIO21\n22 = deviceType: gpio.GPIOPin, pinNumber:22, name:GPIO22\n23 = deviceType: gpio.GPIOPin, pinNumber:23, name:GPIO23\n24 = deviceType: gpio.GPIOPin, pinNumber:24, name:GPIO24\n25 = deviceType: gpio.GPIOPin, pinNumber:25, name:GPIO25\n26 = deviceType: gpio.GPIOPin, pinNumber:26, name:GPIO26\n27 = deviceType: gpio.GPIOPin, pinNumber:27, name:GPIO27\n\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\nuart.UART = baudRate:19200, parity:0, dataBits:8, stopBits:1, flowControl:0\n
    8. Reboot the Raspberry Pi with:

      sudo reboot\n

      Kura starts on the target platform after reboot.

    9. Kura setups a local web ui that is available using a browser via:

      https://<device-ip>\n

      The browser will prompt the user to accept the connection to an endpoint with a self signed certificate, select Accept the risk and continue:

      Once trusted the source, the user will be redirected to a login page where the following credentianls: username: admin password: admin

    "},{"location":"getting-started/zima-board-quick-start/","title":"ZimaBoard/Blade Quick Start","text":""},{"location":"getting-started/zima-board-quick-start/#overview","title":"Overview","text":""},{"location":"java-application-development/configurable-application/","title":"Configurable Application","text":""},{"location":"java-application-development/configurable-application/#overview","title":"Overview","text":"

    This section provides a simple example of how to create an OSGi bundle that implements the ConfigurableComponent interface in Kura. This bundle will interact with the Kura ConfigurationService via the ConfigurableComponent interface. It also uses the MQTT services in Kura to connect to the Cloud, which allows for a local configuration mechanism using a Web user-interface (UI). In this example, you will learn how to perform the following functions:

    • Create a plugin project

    • Implement the ConfigurableComponent interface

    • Use the Kura web UI to modify the bundle\u2019s configuration

    • Export a single OSGi bundle (plug-in)

    "},{"location":"java-application-development/configurable-application/#prerequisites","title":"Prerequisites","text":"
    • Requires Kura development environment set-up (Setting up Kura Development Environment)

    • Implements the use of Kura web user-interface (UI)

    "},{"location":"java-application-development/configurable-application/#configurable-component-example","title":"Configurable Component Example","text":""},{"location":"java-application-development/configurable-application/#create-plug-in","title":"Create Plug-in","text":"

    In Eclipse, create a new Plug-in project by selecting File | New | Project. Select Plug-in Development | Plug-in Project and click Next.

    Your screen should display the New Plug-in Project dialog box as shown in the following screen capture. Enter your project a name, such as \u201corg.eclipse.kura.example.configurable\u201d. Under Target Platform, ensure that the an OSGi framework option button is selected and set to standard as shown below. You can also (optionally) add projects to a working set. To continue, click Next.

    In the next New Plug-in Project menu (shown below), change the Name field to something more descriptive, such as \u201cConfigurable Component Example.\u201d Make sure that the Execution Environment list is set to match the JVM version running on the target device (JavaSE-1.6 or JavaSE-1.7). To determine the JVM version running on the target device, log in to its administrative console and enter the command

    java \u2013version

    Also, uncheck the Generate an activator, a Java class that controls the plug-in\u2019s life cycle option button. For the purposes of this example, a single class will be used. An Activator class will not be created; instead, OSGi Declarative Services will be used to start and stop the bundle.

    Finally, click Finish.

    You should see the new project in the Package Explorer (or Project Explorer) in Eclipse. Also, you will see the MANIFEST.MF was automatically opened in the Manifest Editor. An OSGi bundle is a regular Java .jar file that contains Java code and resources and a custom Manifest and an Activator. The manifest will be modified in the next section.

    "},{"location":"java-application-development/configurable-application/#add-dependencies-to-manifest","title":"Add Dependencies to Manifest","text":"

    First, you will use the Manifest Editor in Eclipse to add some dependencies. Click the Dependencies tab at the bottom of the editor screen and then click the Automated Management of Dependencies heading to expand it.

    Under Automated Management of Dependencies, click Add. In the Select a Plug-in field, enter org.eclipse.osgi.services. Select the plug-in name and click OK.

    Note that this operation is very much like adding standalone jars to the buildpath by including the \u2018-cp\u2019 argument to javac. However, in this case you are telling Eclipse where to find these dependencies the \u201cOSGi way\u201d, so it is aware of them at compile time.

    Click Add again and use the same procedure to add the following dependencies:

    • slf4j.api
    • org.eclipse.kura.api

    You should now see the list of dependencies. Save changes to the Manifest.

    "},{"location":"java-application-development/configurable-application/#create-java-class","title":"Create Java Class","text":"

    Now you are ready to start writing a simple Java class. Right-click the org.eclipse.kura.example.configurable project. Select New | Class. Set the Package field to org.eclipse.kura.example.configurable, set the Name field to ConfigurableExample, and then click Finish.

    Write the following code for the new class. You can copy and paste the code provided below into your newly created Java class file.

    package org.eclipse.kura.example.configurable;\n\npublic class ConfigurableExample implements ConfigurableComponent {\n    private static final Logger s_logger = LoggerFactory.getLogger(ConfigurableExample.class);\n    private static final String APP_ID = \"org.eclipse.kura.example.configurable.ConfigurableExample\";\n    private Map<String, Object> properties;\n\n    protected void activate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n    }\n\n    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started with config!\");\n        updated(properties);\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n    }\n\n    public void updated(Map<String, Object> properties) {\n        this.properties = properties;\n        if(properties != null && !properties.isEmpty()) {\n            Iterator<Entry<String, Object>> it = properties.entrySet().iterator();\n            while (it.hasNext()) {\n                Entry<String, Object> entry = it.next();\n                s_logger.info(\"New property - \" + entry.getKey() + \" = \" +\n                entry.getValue() + \" of type \" + entry.getValue().getClass().toString());\n            }\n        }\n    }\n}\n

    The activate() method is the entry point when the bundle is started. Note this class has two forms of the activate() method. The second method (with the \u201cMap properties\u201d parameter) enables a default configuration to be specified at bundle start time. The deactivate() method is the entry point when the bundle is stopped. You have also specified an updated() method. These methods define how the bundle receives a new configuration from the Kura configuration manager. Kura handles robust configuration management routines automatically once you implement the ConfigurableComponent interface and the updated() method."},{"location":"java-application-development/configurable-application/#resolve-dependencies","title":"Resolve Dependencies","text":"

    At this point, there will be errors in your code because of unresolved imports.

    Select the menu Source | Organize Imports to resolve these errors. Because you added dependencies to your dependency list in the Manifest, you will be prompted to choose one of the following two potential sources for importing a few classes.

    For the \u201cEntry\u201d class, select java.util.Map.Entry as shown below and click Next.

    For the \u201cLogger\u201d class, select org.slf4j.Logger as shown below and click Finish.

    Resolving the imports should clear the errors in the class as shown in the screen capture that follows. Save the changes to the ConfigurableExample class.

    The complete set of code (with import statements) is shown below.

    package org.eclipse.kura.example.configurable;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.osgi.service.component.ComponentContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.eclipse.kura.configuration.ConfigurableComponent;\n\npublic class ConfigurableExample implements ConfigurableComponent {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(ConfigurableExample.class);\n    private static final String APP_ID* = \"org.eclipse.kura.configurable.ConfigurableExample\";\n    private Map<String, Object> properties;\n\n    protected void activate(ComponentContext componentContext) {\n      s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n    }\n\n    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started with config!\");\n        updated(properties);\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n    }\n\n    public void updated(Map<String, Object> properties) {\n        this.properties = properties;\n        if(properties != null && !properties.isEmpty()) {\n            Iterator<Entry<String, Object>> it = properties.entrySet().iterator();\n            while(it.hasNext()) {\n                Entry<String, Object> entry = it.next();\n                s_logger.info(\"New property - \" + entry.getKey() + \" = \" +\n                entry.getValue() + \" of type \" + entry.getValue().getClass().toString());\n            }\n        }\n    }\n}\n

    Switch back to the Manifest Editor. Under Automated Management of Dependencies, ensure the Import-Package option button is selected. Click the add dependencies link to automatically add packages to the dependencies list (under Imported Packages) based on the \u201cimport\u201d statements in your example code. Save changes to the Manifest again.

    "},{"location":"java-application-development/configurable-application/#create-component-class","title":"Create Component Class","text":"

    Right-click the example project and select New | Folder. Create a new folder named \u201cOSGI-INF\u201d.

    Now, right-click the example project\u2019s \u201cOSGI-INF\u201d folder and select New | Other. From the wizard, select Plug-in Development | Component Definition and click Next.

    Next to the Class field, click Browse and type the name of your newly created class in the Select entries field. In this case, type the word \u201cConfigurable\u201d, and you will see matching items. Select the ConfigurableExample class and click OK.

    In the Enter or select the parent folder field, make sure \u201c/OSGI-INF\u201d is at the end of the existing entry (e.g., org.eclipse.kura.example.configurable/OSGI-INF). Set the Name field equal to the Class field as shown below:

    Click Finish.

    After the Component class has been created, it will open in the Workspace. On the Services tab, click the Add button under Provided Services. Enter \u201cconfigurable\u201d and select the interface \u201corg.eclipse.kura.example.configurable\u201d. This is required for components that are configurable through the Kura ConfigurationService, so that they expose a Service.

    In the Overview tab, the Name and Class fields should already point to your Java class. Make the following settings:

    • Set the Activate field to activate and set the Deactivate field to deactivate. This tells the component where these OSGi activation methods are located.

    • Set the Configuration Policy to require.

    • Set the Modified field to updated. This tells the component which method to call when the configuration is updated.

    • Uncheck the box This component is enabled when started, then check both boxes This component is enabled when started and This component is immediately activated.

    Click the Add Property button. Enter a property with the name \u201cservice.pid\u201d and value \u201corg.eclipse.kura.example.configurable.ConfigurableExample\u201d as shown in the screen capture below.

    Verify that the completed Overview tab looks like the screen shot shown below and save the Component class definition file:

    Check the Source tab of the component.xml file and carefully verify that each of the property values and tags match what is shown below:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    activate=\"activate\"\n    configuration-policy=\"require\"\n    deactivate=\"deactivate\"\n    enabled=\"true\"\n    immediate=\"true\"\n    modified=\"updated\"\n    name=\"org.eclipse.kura.example.configurable.ConfigurableExample\">\n\n    <implementation class=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    <service>\n        <provide interface=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    </service>\n    <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n</scr:component>\n

    If Kura 3.0 or newer versions are used and the \"org.eclipse.kura.core.configuration.legacyServiceTracking\" system property is set to false or not set, proceed as follows.

    "},{"location":"java-application-development/configurable-application/#create-the-default-configuration","title":"Create the Default Configuration","text":"

    With the component definition file created, you also need to specify the configurable parameters of this bundle. This is done using the \u201cmetatype\u201d definition. Right-click the OSGI-INF directory for the example project in the Package Explorer window and select New | Folder. Name the folder \u201cmetatype\u201d. Next, right-click the metatype folder and select New | File.

    Name the file \u201corg.eclipse.kura.example.configurable.ConfigurableExample.xml\u201d as shown in the following screen capture:

    At this point, you have to write the \u2018metatype\u2019 file that defines the parameters, default values, types, etc. Click on the Source button and paste the following XML text into ConfigurableExample.xml for this example. Save changes to ConfigurableExample.xml.

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n    <OCD id=\"org.eclipse.kura.example.configurable.ConfigurableExample\"\n        name=\"ConfigurableExample\"\n        description=\"This is a sample metatype file for a simple configurable component\">\n\n        <AD id=\"param1.string\"\n            name=\"param1.string\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"Some Text\"\n            description=\"String configuration parameter\"/>\n\n        <AD id=\"param2.float\"\n            name=\"param2.float\"\n            type=\"Float\"\n            cardinality=\"0\"\n            required=\"false\"\n            default=\"20.5\"\n            min=\"5.0\"\n            max=\"40.0\"\n            description=\"Float configuration parameter\"/>\n\n        <AD id=\"param3.integer\"\n            name=\"param3.integer\"\n            type=\"Integer\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"2\"\n            min=\"1\"\n            description=\"Integer configuration parameter\"/>\n    </OCD>\n\n    <Designate pid=\"org.eclipse.kura.example.configurable.ConfigurableExample\">\n        <Object ocdref=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    </Designate>\n</MetaData>\n

    In the MANIFEST.MF of this bundle, you must also make sure the XML file gets packaged into the bundle when you export the plug-in. Click the Build tab, and in the Binary Build section of the Manifest editor verify that all the checkboxes for items under META-INF and OSGI-INF are checked. Save the Manifest after making this change.

    "},{"location":"java-application-development/configurable-application/#run-the-bundle","title":"Run the Bundle","text":"

    At this point, you can run the bundle using the emulator in Eclipse (Linux or OS X only). To do so, expand the org.eclipse.kura.emulator project in the package explorer and browse to src/main/resources. As appropriate for you platform type, right-click Kura_Emulator_[OS].launch (where \u201c[OS]\u201d specifies your operating system) and select Run as | KURA_EMULATOR_[OS].launch. Doing so will start the Kura emulator and your new bundle in the console window of Eclipse.

    "},{"location":"java-application-development/configurable-application/#view-the-bundle-configuration-in-the-local-web-ui","title":"View the Bundle Configuration in the Local Web UI","text":"

    With the bundle running, open a browser window on the same computer as the Eclipse development environment and browse to the Kura web UI at http://127.0.0.1:8080. Once connected to the Kura web UI, a log in window appears prompting you to enter the Name and Password as shown below:

    Enter the appropriate name and password (default is admin/admin) and click Log in. The Kura Admin web UI appears with the ConfigurableExample in the Services area on the left side of the browser window as shown below:

    From the Kura Admin web UI, you can change the parameters that are used by the Kura configuration manager and in turn call the updated() method of the newly created bundle. To do so, click ConfigurableExample and the configurable component parameters will be displayed as shown below:

    Make any necessary changes and click the Apply button near the top left of the configuration pane for the modifications to take affect. Every time a change is made to the configuration, a new snapshot is generated along with an ID.

    "},{"location":"java-application-development/connected-application/","title":"Connected Application","text":""},{"location":"java-application-development/connected-application/#overview","title":"Overview","text":"

    This section describes the prepackaged heater demo bundle that comes with the Kura development environment and demonstrates how to perform the following functions:

    • Run the Kura Emulator

    • Connect to the Cloud

    • Gain an understanding of ConfigurableComponents in Kura

    • Modify configurations of custom bundles

    "},{"location":"java-application-development/connected-application/#prerequisites","title":"Prerequisites","text":"
    • Setting up Kura Development Environment

    • Using the Kura web UI

    "},{"location":"java-application-development/connected-application/#heater-demo-introduction","title":"Heater Demo Introduction","text":"

    The org.eclipse.kura.demo.heater bundle is a simple OSGi bundle that represents a thermostat and heater combination. The application utilizes the Kura ConfigurableComponent interface to be able to receive configuration updates through the local Kura web UI. In addition, this bundle utilizes OSGi declarative services and the Kura CloudClientListener. This tutorial demonstrates how to modify configurations of custom bundles and shows how those configuration changes can dynamically impact the behavior of the bundle through the Kura web UI.

    "},{"location":"java-application-development/connected-application/#code-walkthrough","title":"Code Walkthrough","text":"

    The following sections will highlight three important API layers when creating an application that will publish to the cloud. These layers are:

    • DataTransportService
    • Available for standard MQTT messaging. Allows consumers of the service to connect to brokers, publish messages, and receive messages on subscribed topics
    • DataService
    • Delegates data transport to the DataTransportService
    • Provides extended features for managing broker connections, buffering of published messages, and priority based delivery of messages
    • CloudService
    • Further extends the functionality of DataService
    • Provides means for more complex flows (i.e. request/response)
    • Manages single broker connection across multiple applications
    • Provides payload data model with encoding/decoding serializers
    • Publishes life cycle manages for devices and applications
    "},{"location":"java-application-development/connected-application/#acquiring-cloudclient","title":"Acquiring CloudClient","text":"

    The CloudService can manage multiple applications over a shared MQTT connection by treating each application as a client. The example code uses the \"setCloudService\" and \"unsetCloudService\" methods for referencing and releasing the CloudService. In the bundles activate method, the service reference in conjunction with a unique application ID can then be used to obtain a CloudClient. The relevant code is shown below (ommitted sections are denoted by ==OMMITTED==):

    ==OMMITTED==\n// Cloud Application identifier\nprivate static final String APP_ID = \"heater\";\n\n==OMMITTED==\n\npublic void setCloudService(CloudService cloudService) {\n    m_cloudService = cloudService;\n}\n\npublic void unsetCloudService(CloudService cloudService) {\n    m_cloudService = null;\n}\n\n==OMMITTED==\n\n// Acquire a Cloud Application Client for this Application\ns_logger.info(\"Getting CloudClient for {}...\", APP_ID);\nm_cloudClient = m_cloudService.newCloudClient(APP_ID);\n
    "},{"location":"java-application-development/connected-application/#publishingsubscribing","title":"Publishing/Subscribing","text":"

    The private \"doPublish\" method is used to publish messages at a fixed rate. The method demonstrates how to use the CloudClient and KuraPayload to publish MQTT messages.

    ==OMMITTED==\n\n// Allocate a new payload\nKuraPayload payload = new KuraPayload();\n\n// Timestamp the message\npayload.setTimestamp(new Date());\n\n// Add the temperature as a metric to the payload\npayload.addMetric(\"temperatureInternal\", m_temperature);\npayload.addMetric(\"temperatureExternal\", 5.0F);\npayload.addMetric(\"temperatureExhaust\",  30.0F);\n\nint code = m_random.nextInt();\nif ((m_random.nextInt() % 5) == 0) {\n    payload.addMetric(\"errorCode\", code);\n}\nelse {\n    payload.addMetric(\"errorCode\", 0);\n}\n\n// Publish the message\ntry {\n    m_cloudClient.publish(topic, payload, qos, retain);\n    s_logger.info(\"Published to {} message: {}\", topic, payload);\n}\ncatch (Exception e) {\n    s_logger.error(\"Cannot publish topic: \"+topic, e);\n}\n

    Similarly, the CloudClient can be used to subscribe to MQTT topics. Although not shown in the example code, the following snippet could be added to subscribe to all published messages:

    m_cloudClient.subscribe(topic, qos);\n
    "},{"location":"java-application-development/connected-application/#callback-methods","title":"Callback Methods","text":"

    The example class implements CloudClientListener, which provides methods for several common callback methods. The below snippet shows the relevant code for creating the listeners for the demo application.

    ==OMMITTED==\n\npublic class Heater implements ConfigurableComponent, CloudClientListener\n\n==OMMITTED==\n\nm_cloudClient.addCloudClientListener(this);\n

    The available methods for implementation are:

    • onControlMessageArrived: Method called when a control message is received from the broker.
    • onMessageArrived: Method called when a data message is received from the broker.
    • onConnectionLost: Method called when the client has lost connection with the broker.
    • onConnectionEstablished: Method called when the client establishes a connection with the broker.
    • onMessageConfirmed: Method called when a published message has been fully acknowledged by the broker (not applicable for qos 0 messages).
    • onMessagePublished: Method called when a message has been transfered from the publishing queue to the DataTransportService.

    For more information on the various Kura APIs, please review the Kura APIs

    "},{"location":"java-application-development/connected-application/#run-the-bundle","title":"Run the Bundle","text":"

    By default, the heater demo bundle does not run automatically. To run the bundle and Kura in the Emulator, locate the org.eclipse.kura.emulator project. Expand it to show the src/main/resources folder.

    Right-click the correct Kura_Emulator_[OS].launch file, depending on which operating system you are running. In the context menu, select the Run As option, and click on Run Configurations.

    Under OSGi Framework (Run Configurations window shown below), click on the Kura_Emulator_[OS] entry. In the Bundles tab under Workspace, enable the org.eclipse.kura.demo.heater checkbox to enable it as shown below:

    Click the Apply and Run buttons to start the Kura Emulator. Once this setting has been made, you only need to right-click on the launch file and select Run As and the Kura_Emulator_[OS] option to run with the same settings.

    This will start Kura running locally and will display a Console window in Eclipse. The Console window will show the OSGi diagnostics as various bundles start and execute.

    "},{"location":"java-application-development/connected-application/#configure-the-mqtt-client","title":"Configure the MQTT Client","text":"

    With the heater demo bundle running, open a browser window on the same computer as the Eclipse development environment and browse to the Kura web UI at http://127.0.0.1:8080. Once connected to the Kura web UI, a log in window appears prompting you to enter the Name and Password as shown below:

    Enter the appropriate name and password (default is admin/admin) and click Log in. The Kura Admin web UI appears as shown below:

    From the Kura web UI, click on MqttDataTransport in the Services pane on the lower left of the browser window. You will see a menu similar to the one shown in the following screen capture:

    Fill in the following fields then click the Apply button:

    Field Value broker-url: The url for the MQTT broker (this example shows the MQTT broker-url mqtt://iot.eclipse.org:1883/ hosted by the Eclipse Foundation) topic.context.account-name: Your [account_name] username: Typically [account_name]_broker password: The password for your user client-id The client identifier to be used when connecting to the MQTT broker (optional)

    Now that the account credentials are set in the MqttDataTransport service, the DataService needs to be configured to connect by default. To do so, click DataService in the Services area on the left of the browser window. For the \u2018connect.auto-on-startup\u2019 parameter, select true as shown below:

    "},{"location":"java-application-development/connected-application/#modify-bundle-configuration-in-local-web-ui","title":"Modify Bundle Configuration in Local Web UI","text":"

    Bundles changes may be made directly in the emulator web UI. Since you are running an emulated device in Eclipse, you can do this by browsing to http://127.0.0.1:8080 (same URL where the MQTT client was configured in the previous section of this tutorial). If the bundle was running on a real device and you had network access to it, you would browse to http://[ip_address_of_device].

    From the Kura web UI, select the Heater bundle from the configurable services on the left and modify the parameters as needed (shown in the screen capture below). By default, the heater demo is configured according to the following characteristics and assumptions about its operational environment:

    • Start operation is at 6:00am (06:00).

    • End operation is at 10:00pm (22:00).

    • It is colder outside than inside the heated chamber (hard-coded to 5 degrees in the application).

    • Output of the heater is constant at 30 degrees (hard-coded).

    • When in operational mode, the temperature will drop inside if the heater is off.

    • The heater turns off when it is about to exceed the setPoint defined in the configuration.

    • After the temperature drops to four times the increment point (a made-up value to show dropping temperature, hard-coded in the application), the heater turns back on, and the temperature starts increment at the rate of the \u2018temperature.increment\u2019 rate.

    Click Apply for changes to take affect. The updated() method is called after settings are applied for the new configuration.

    After completing this tutorial, it is highly recommended that you review the heater demo source code in Eclipse to see how it is put together. Kura automatically generates the user configuration interface through implementation of the ConfigurableComponent interface and some small additions to the component.xml file (called heater.xml). This powerful feature provides both a local and remote configuration user interface with no additional development requirements.

    "},{"location":"java-application-development/contributing/","title":"Contributing","text":"

    Contributing to Eclipse Kura project is very easy.

    The steps required to submit code to the project can be found on Github Contributing Page.

    If you face any issues, or just want to get involved with the kura community feel free to join us on:

    • Github Issues: for bug reporting.
    • Github Discussions: for receiving feedback, making new proposals and generally talking about the project.
    "},{"location":"java-application-development/contributing/#kura-dev-meeting","title":"Kura Dev Meeting","text":"

    If you want to get involved in the development process you can join us at the Kura Dev Meeting. The meeting is held every 2 weeks on Wednesday, at 5 PM CEST on Microsoft Teams.

    You can join by using this link.

    The scheduled dates for the meeting can be found here. Unzip the calendar file and double-click on it to add the scheduled dates to your calendar.

    "},{"location":"java-application-development/deploy-and-debug-applications/","title":"Deploy and Debug Applications","text":""},{"location":"java-application-development/deploy-and-debug-applications/#overview","title":"Overview","text":"

    This section provides a simple example of how to test and deploy OSGi bundles and deployment packages in a Kura environment. These instructions use the \u201cHello World\u201d OSGi project created in the previous section. In this example, you will learn how to perform the following functions:

    • Use local OSGi emulation mode in Eclipse

    • Deploy a bundle to a remote target running the OSGi Framework

    • Install a Deployment Package to a remote target running the OSGi Framework

    • Manage OSGi bundles on a target device

    • Set bundle Logger levels in Kura

    "},{"location":"java-application-development/deploy-and-debug-applications/#prerequisites","title":"Prerequisites","text":"
    • Setting up Kura Development Environment

    • Hello World Using the Kura Logger

    "},{"location":"java-application-development/deploy-and-debug-applications/#testing-the-osgi-plug-in","title":"Testing the OSGi Plug-in","text":"

    Once you have created an OSGi plug-in, you can test it in Local Emulation Mode and/or deploy it to a Remote Target Device.

    "},{"location":"java-application-development/deploy-and-debug-applications/#local-emulation-mode","title":"Local Emulation Mode","text":"

    The Kura user workspace can be used in Eclipse in local emulation mode (Linux/OS X only; this feature is not currently supported under Windows). To deploy the code to a running system, see the section Remote Target Device.

    "},{"location":"java-application-development/deploy-and-debug-applications/#run-kura-in-emulator-mode","title":"Run Kura in Emulator Mode","text":"

    In the Eclipse workspace, locate the org.eclipse.kura.emulator project. Expand it to show the src/main/resources folder.

    Right-click the Kura_Emulator.launch file. In the context menu, select the Run as option, and select the Kura_Emulator*. This will start Kura running locally and will display a Console window in the bottom pane in Eclipse. The Console window will show the OSGi diagnostics as various bundles start and execute.

    Because the org.eclipse.kura.example.hello_osgi bundle is in the workspace with a valid activate() method, it is automatically started with the Kura OSGi framework. Note the INFO message highlighted below that shows the bundle\u2019s activate() method was run.

    "},{"location":"java-application-development/deploy-and-debug-applications/#list-osgi-bundles-in-local-mode","title":"List OSGi Bundles in Local Mode","text":"

    With the OSGi framework running in the Eclipse console (refer to the previous section), click in the Console window. Press Enter/Return and then type the \u2018ss\u2019 command to show a list of installed bundles. Note the bundle ID number for the org.eclipse.kura.example.hello_osgi bundle.

    "},{"location":"java-application-development/deploy-and-debug-applications/#startstop-bundle-in-local-mode","title":"Start/Stop Bundle in Local Mode","text":"

    In the OSGi Console window in Eclipse, run the

    start ## or stop ##

    commands to start or stop a bundle, where the \u201c##\u201d is either the bundle ID number or the bundle name (such as \u201cstart org.eclipse.kura.example.hello_osgi\u201d). Note that the INFO messages for both the activate() and deactivate() messages appear in the Console window when the bundle is started or stopped.

    "},{"location":"java-application-development/deploy-and-debug-applications/#installuninstall-bundle-in-local-mode","title":"Install/Uninstall Bundle in Local Mode","text":"

    In the OSGi Console window in Eclipse, bundles can be installed or uninstalled. To uninstall the example bundle, issue the command \u2018uninstall ##\u2019, where \u201c##\u201d is either the bundle ID number or the bundle name, such as:

    uninstall 47 or uninstall org.eclipse.kura.example.hello_osgi

    A message will appear indicating that the bundle has been stopped.

    Once the bundle has been uninstalled from the local OSGi console, it cannot be started or installed by number or name. Instead, it must be installed by using the plug-in JAR file created earlier. Issue the \u2018install\u2019 command to install a bundle into the Emulation environment:

    install file:/[*path_to_bundle*]/[*bundle_name*].jar

    where \u201c[path_to_bundle]/[bundle_name].jar\u201d should be replaced with the name of the bundle exported earlier (the section Hello World Using the Kura Logger), as shown in the example below:

    install file:/Users/Nina/Documents/myPlugins/plugins/plugins/plugins/plugins/plugins/org.eclipse.kura.example.hello_osgi_ 1.0.0.201409101740.jar

    Then the bundle can be started or stopped, as described in the previous section, Start/Stop Bundle in Local Mode. Optionally, you can add the flag \u2018-start\u2019 to the \u2018install\u2019 command to automatically start the bundle after installation.

    "},{"location":"java-application-development/deploy-and-debug-applications/#remote-target-device","title":"Remote Target Device","text":"

    One or more OSGi bundles can be deployed to a remote device running Kura, either by installing separate bundle files or deployment packages using Eclipse.

    Warning

    These steps require Kura to be running on the target device.

    This method of deployment is temporary on the remote target device and is not persistent after a restart. To make the deployment permanent, see Making Deployment Permanent.

    "},{"location":"java-application-development/deploy-and-debug-applications/#connect-to-remote-osgi-framework","title":"Connect to Remote OSGi Framework","text":"

    To deploy a bundle to the remote target device, you will need to connect Eclipse to the OSGi framework running on the device. This is done using mToolkit. See Kura Setup for instructions on installing mToolkit into the Eclipse development environment.

    • Select the Eclipse menu Window | Show View | Other.

    • Select mToolkit -> Frameworks entry to open the mToolkit Frameworks view.

    • Enter a name for the framework definition and the IP address of the target device.

    Close the dialog by clicking the OK button.

    Warning

    The remote target device must have port 1450 open in its firewall, in order to allow mToolkit o make a connection to its OSGi framework. If this port is not opened, refer to the section Open Port for OSGi Remote Connection.

    Right-click the framework icon name and select Connect Framework. The list of installed bundles and deployment packages should be retrieved shortly. (Use the Disconnect Framework option to disconnect from the remote target framework when finished.)

    "},{"location":"java-application-development/deploy-and-debug-applications/#open-port-for-osgi-remote-connection","title":"Open Port for OSGi Remote Connection","text":"

    In order to allow mToolkit to make a remote connection to the OSGi framework on the target device, the device must allow the incoming port in its firewall. To set this option, open a Web browser and log into Kura using its current IP address, such as:

    http://10.11.5.4

    Click the Firewall icon and then click the Open Ports tab. If port 1450 is not shown in the list of allowed ports, click the New button under Open Ports. Enter the port 1450 and select protocol TCP. Then click Submit.

    Now, click Apply to apply changes to the remote device.

    "},{"location":"java-application-development/deploy-and-debug-applications/#install-single-bundle-to-target-device","title":"Install Single Bundle to Target Device","text":"

    With the Eclipse environment connected to the remote OSGi target framework, a single bundle can be installed on the remote device.

    In the mToolkit Frameworks view, right-click the Framework name and select Install Bundle. (This requires that you have exported the bundle as a deployable plug-in JAR file. See the section Hello World Using the Kura Logger.)

    Use the Browse button to select the JAR file and click OK to install it to the target device.

    The newly installed bundle should be shown in the Frameworks view under Bundles.

    To control operation of the bundle through the OSGi Frameworks view, right-click the bundle name. The following actions can be performed:

    • Start \u2013 start the bundle

    • Stop \u2013 stop the bundle

    • Update \u2013 reinstall the bundle

    • Install Bundle \u2013 install a different bundle

    • Uninstall Bundle \u2013 remove this bundle from the target device

    • Show Bundle IDs / Show Bundle Versions \u2013 show additional information about bundles

    You can also verify operation of the bundle on the target device itself. See the section Manage Bundles on Target Device.

    "},{"location":"java-application-development/deploy-and-debug-applications/#install-deployment-package-to-target-device","title":"Install Deployment Package to Target Device","text":"

    With the Eclipse environment connected to the remote OSGi target framework, a deployment package can be installed on the remote device.

    NOTE: If you have just installed the individual bundle in the previous section, you should uninstall it before proceeding. Doing so will avoid any confusion in having the same bundle installed twice.

    In the mToolkit Frameworks view, right-click the Framework name and select Install Deployment Package. (This step requires that you have exported the bundle as a deployable plug-in JAR file. See the section Hello World Using the Kura Logger for instructions on exporting the OSGi bundle.)

    Open the resources/dp folder in the Workspace filesystem directory, select the .dp file (not the \u201c.dpp\u201d file), and click OK.

    The deployment package will be installed on the target device and shown in the Frameworks view under Deployment Packages. (The deployment package can also be uninstalled from the Framework view.)

    The bundle included in the deployment package can also be viewed under Bundles and can be controlled remotely (start/stop) as with other bundles. The operation of the bundle can also be verified on the target device itself. See the section Manage Bundles on Target Device.

    "},{"location":"java-application-development/deploy-and-debug-applications/#connect-to-osgi-on-target-device","title":"Connect to OSGi on Target Device","text":"

    You can manage the OSGi framework on a target device by logging into a console on the device using a connected keyboard and VGA monitor or over a network connection using SSH (PuTTY in Windows or \u2018ssh\u2019 from Linux or Mac).

    At the command prompt, display the Kura log file with:

    tail -f /var/log/kura.log

    Connect to the OSGi framework by typing the following commands:

    telnet localhost 5002

    There are many commands available in the OSGi console for managing bundles. Following are just a few useful commands:

    Command Description ss Lists names and ID of bundles help Displays the help menu of OSGi commands lb Lists all installed bundles and IDs h [bundle IDs] Displays bundle headers (i.e., Bundle Manifest Version, Name, Required Execution Environment, Symbolic Name, Version, Import Package, Manifest Version, and Service Component) exit Exits the OSGi console and stops Kura disconnect Exits the OSGi console, but leaves Kura running"},{"location":"java-application-development/deploy-and-debug-applications/#manage-bundles-on-target-device","title":"Manage Bundles on Target Device","text":"

    From the OSGi command line, you can display a list of bundles with the \u2018ss\u2019 command as shown in the example below:

    ss

    In this example, the org.eclipse.kura.example.hello_osgi bundle ID is 64.

    You can run the \u2018start ##\u2019 or \u2018stop ##\u2019 commands to start or stop a bundle, where the \u201c##\u201d is either the bundle ID number or the bundle name (such as \u201cstart org.eclipse.kura.example.hello_osgi\u201d). To verify that the bundled is stopped, you can issue the \u2018ss\u2019 command. If the bundled is stopped, the activity will show RESOLVED (as shown below). If the bundle is started, the activity will show ACTIVE (as shown above).

    "},{"location":"java-application-development/deploy-and-debug-applications/#set-kura-logger-levels","title":"Set Kura Logger Levels","text":"

    Kura logger levels are defined in a configuration file. The messages that appear require a log statement in the application and that the log level of the statement matches the log level of the application (such as logger.info or logger.debug).

    To set or change logger levels, the Kura logger configuration file may be modified using the vi editor. From the Linux command prompt, enter the following command:

    vi /opt/eclipse/kura/kura/log4j.properties

    At the bottom of the \u201clog4j.properties\u201d file, there will be one or more \u201clog4j\u201d logger property entries, which determine the logger level used by the bundles at startup.

    In the example screen capture shown below, the \u201clog4j.logger.org.eclipse.kura\u201d property has been set to \u201cINFO\u201d, which applies to all bundles that start with \u201corg.eclipse.kura.\u201d Additional, more specific, properties may be defined as required for your particular logging needs. The property entries will take on the defined logger level at startup. The logger levels are hierarchical, so that those in a deeper level of the hierarchy will apply; otherwise, the more general logger level will override them.

    Once you have made the necessary changes, save and close the file using the \u2018:wq\u2019 command. Restart Kura, and check the log levels in the OSGi console again to make sure that the desired levels have taken effect.

    "},{"location":"java-application-development/deploy-and-debug-applications/#making-deployment-permanent","title":"Making Deployment Permanent","text":"

    The mToolkit deployment of a package is a temporary installation and does not make the package permanent. Once a set of bundles has been tested on the remote target device and is ready for permanent deployment, the software can be installed on a device with deployment packages from the command line of a target device using the instructions below:

    1. Copy the deployment package file (*.dp) to the target device, into the folder:

    /opt/eclipse/kura/kura/packages

    1. Edit the dpa.properties file through the vi editor by entering the following command:

    vi /opt/eclipse/kura/kura/dpa.properties

    1. Add an entry in the dpa.properties file to include the new package name, such as:

    package_name=file\\:/opt/eclipse/kura/kura/packages/package_filename.dp

    where, \u201cpackage_name\u201d and \u201cpackage_filename\u201d should be replaced with the actual name of the deployment package.

    1. Save and close the file using the \u2018:wq\u2019 command.

    2. Then restart Kura, and the new package should be installed in addition to the default Kura package.

    In conclusion, this section described how to test a bundle in an Emulation environment within the Eclipse IDE and how to install bundles and Deployment Packages to a remote target system running Kura.

    "},{"location":"java-application-development/development-environment-setup/","title":"Development Environment Setup","text":"

    In this document we'll cover the required steps to setup the Development Environment for contributing to the Eclipse Kura project. If, instead, you want to develop applications or bundles running on Eclipse Kura refer to the Eclipse Kura Workspace setup guide.

    The Eclipse Kura development environment may be installed on Windows, Linux, or Mac OS. The setup instructions will be the same across each OS though each system may have unique characteristics.

    Info

    The local emulation of Eclipse Kura code is only supported in Linux and Mac, not in Windows.

    This document will cover the use of Eclipse Oomph installer which is the easiest way to install and configure the Eclipse IDE to start contributing to Eclipse Kura.

    The setup requires three basic steps:

    1. Requirements installation
    2. Eclipse Oomph setup
    3. Eclipse Kura maven build
    "},{"location":"java-application-development/development-environment-setup/#requirements","title":"Requirements","text":"

    Before building Eclipse Kura, you need to have the following programs installed in your system:

    • JDK 1.8 (or JDK 17)
    • Maven 3.5.x (or greater)

    Recommended additional software:

    • Git
    "},{"location":"java-application-development/development-environment-setup/#installing-prerequisites-in-mac-os","title":"Installing Prerequisites in Mac OS","text":"

    To install Java 8, download the JDK tar archive from the Adoptium Project Repository.

    Once downloaded, copy the tar archive in /Library/Java/JavaVirtualMachines/ and cd into it. Unpack the archive with the following command:

    sudo tar -xzf <archive-name>.tar.gz\n
    The tar archive can be deleted afterwards.

    Depending on which terminal you are using, edit the profiles (.zshrc, .profile, .bash_profile) to contain:

    # Adoptium JDK 8\nexport JAVA_8_HOME=/Library/Java/JavaVirtualMachines/<archive-name>/Contents/Home\nalias java8='export JAVA_HOME=$JAVA_8_HOME'\njava8 \n
    Reload the terminal and run java -version to make sure it is installed correctly.

    Using Brew you can easily install Maven from the command line:

    brew install maven@3.5\n
    Run mvn -version to ensure that Maven has been added to the PATH. If Maven cannot be found, try running brew link maven@3.5 --force or manually add it to your path with:
    export PATH=\"/usr/local/opt/maven@3.5/bin:$PATH\"\n

    "},{"location":"java-application-development/development-environment-setup/#installing-prerequisites-in-linux","title":"Installing Prerequisites in Linux","text":"

    For Java

    sudo apt install openjdk-8-jdk\n

    For Maven

    You can follow the tutorial from the official Maven site. Remember that you need to install 3.5.x version or greater.

    "},{"location":"java-application-development/development-environment-setup/#eclipse-oomph-setup","title":"Eclipse Oomph setup","text":"

    Download the latest Eclipse Installer appropriate for your platform from the Eclipse Downloads page and start it.

    Switch to \"Advanced Mode\" (top right hamburger menu) and select \"Eclipse IDE for Eclipse Committers\" and configure the \"Product Version\" to be the version 2023-03 or newer.

    Select the Eclipse Kura installer from the list. If this is not available, add a new installer from https://raw.githubusercontent.com/eclipse/kura/develop/kura/setups/kura.setup, then check and press the \"Next\" button.

    Variables setup

    • Select the \"Developer Type\":
      • \"User\": if you want to develop applications or bundles running on Eclipse Kura, select this option. It will install only the APIs and the examples.
      • \"Developer\": if you are a framework developer, select this option. It will download and configure the Eclipse Kura framework (for the purpose of this document we'll use this option)
    • Set the JRE 1.8 location value to the installed local jdk-8 VM
    • Update Eclipse Kura Git repository username (prefer the anonymous HTTPS option, link to your fork) and customize further settings if you like (e.g. Root install folder, Installation folder name). To show these options, make sure that the \"Show all variables\" checkbox is enabled.

    If you plan to contribute to Eclipse Kura you might want to create a fork, see our contributing guide for further informations. For the purpose of this tutorial we'll work with a fictional fork for the username user. To clone the repo use the link appropriate for your fork, in our case it will be: https://github.com/user/kura.git

    Keep in mind that the \"Root install folder\" is where the Eclipse executable will be installed and the Eclipse Kura sources will be downloaded (in the git subfolder).

    Press Next, leave all Bootstrap Tasks selected and press the Finish button

    Accept all the licenses and wait for the installation to finish.

    At first startup Eclipse IDE will checkout the code, perform a full build and configure a few Working Sets

    When the tasks are completed, go to into the Package Explorer and Target Platform > Target-Definition > Kura Target Platform Equinox 3.16.0, and press \"Set as Target Platform\" located at the top right of the window:

    "},{"location":"java-application-development/development-environment-setup/#eclipse-kura-maven-build","title":"Eclipse Kura maven build","text":"

    Navigate to the git folder created within the Eclipse workspace (~/iot-kura-workspace in the example above) and build the target platform:

    mvn -f target-platform/pom.xml clean install\n

    Then build the core components:

    mvn -f kura/pom.xml clean install\n

    Build the examples (optional):

    mvn -f kura/examples/pom.xml clean install\n

    Build the target profiles:

    mvn -f kura/distrib/pom.xml clean install -DbuildAll\n

    Note

    You can skip tests by adding -Dmaven.test.skip=true in the commands above and you can compile a specific target by specifying the profile (e.g. -Praspberry-pi-armhf).

    "},{"location":"java-application-development/development-environment-setup/#build-scripts","title":"Build scripts","text":"

    Alternatively you can use the build scripts available in the root directory.

    ./build-all.sh\n

    or

    ./build-menu.sh\n

    and select the profiles you want to build.

    "},{"location":"java-application-development/hello-world-application/","title":"Hello World Application","text":""},{"location":"java-application-development/hello-world-application/#overview","title":"Overview","text":"

    This section provides a simple example of how to create a Kura \u201cHello World\u201d OSGi project using Eclipse. With this example, you will learn how to perform the following functions:

    • Create a plugin project

    • Consume the Kura Logger service

    • Write an OSGi Activator

    • Export a single OSGi bundle (plug-in)

    • Create a Deployment Package

    "},{"location":"java-application-development/hello-world-application/#prerequisites","title":"Prerequisites","text":"

    Setting up the Kura Development Environment

    "},{"location":"java-application-development/hello-world-application/#hello-world-using-the-kura-logger","title":"Hello World Using the Kura Logger","text":""},{"location":"java-application-development/hello-world-application/#create-hello-world-plug-in","title":"Create Hello World Plug-in","text":"

    In Eclipse, create a new Plug-in project by selecting File | New | Project. Select Plug-in Development | Plug-in Project and click Next.

    Your screen should display the New Plug-in Project dialog box as shown in the following screen capture. Enter your project a name, such as \u201corg.eclipse.kura.example.hello_osgi\u201d, in the appropriate field. Under Target Platform, ensure that the an OSGi framework option button is selected and set the variable to standard as shown below. You can also (optionally) add projects to a working set. To continue, click Next.

    In the next New Plug-in Project menu (shown below), change the Name field to a descriptive name, such as \u201cHello World Example with Logger\u201d.

    Also, verify that the Execution Environment list is set to match the Java JVM version running on the target device (JavaSE-1.8 or JavaSE-11). To determine the JVM version running on the target device, log in to its administrative console and enter the command

    java \u2013version

    Finally, uncheck the Generate an activator, a Java class that controls the plug-in\u2019s life cycle option button. For the purposes of this example, a single class will be used. An Activator class will not be created; instead, OSGi Declarative Services will be used to start and stop the bundle.

    Click Finish.

    If the Open Associated Perspective pop-up window (shown below) appears for adding Plug-ins and Error Log views, select Yes or No depending on your development requirements.

    You should see the new project in the My Projects working set in the Package Explorer (or Project Explorer). Also, you will see the MANIFEST.MF was automatically opened in the Manifest Editor. An OSGi bundle is a regular Java .jar file that contains Java code and resources and a custom Manifest and an Activator.

    "},{"location":"java-application-development/hello-world-application/#add-dependencies-to-manifest","title":"Add Dependencies to Manifest","text":"

    First, you will use the Manifest Editor in Eclipse to add some dependencies. Click the Dependencies tab at the bottom of the editor screen and then click the Automated Management of Dependencies heading to expand it.

    Under Automated Management of Dependencies, click Add. In the Select a Plug-in field, enter org.eclipse.osgi.services. Select the plug-in name and click OK.

    Note that this operation is very much like adding standalone jars to the buildpath by including the \u2018-cp\u2019 argument to javac. However, in this case you are telling Eclipse where to find these dependencies the \u201cOSGi way\u201d, so it is aware of them at compile time. Click Add again and use the same procedure to add the following dependency:

    • slf4j.api

    You should now see the list of dependencies. Save changes to the Manifest.

    "},{"location":"java-application-development/hello-world-application/#create-java-class","title":"Create Java Class","text":"

    Now you are ready to start writing a simple Java class. Right-click the org.eclipse.kura.example.hello_osgi project. Select New | Class. The New Java Class window appears as shown below. Set the Source folder to org.eclipse.kura.example.hello_osgi/src. Set the Package field to org.eclipse.kura.example.hello_osgi, set the Name field to HelloOsgi, and then click Finish.

    Write the following code for the new class. You can copy and paste the code provided below into your newly created Java class file.

    package org.eclipse.kura.example.hello_osgi;\n\npublic class HelloOsgi {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(HelloOsgi.class);\n\n    private static final String APP_ID = \"org.eclipse.kura.example.hello_osgi\";\n\n    protected void activate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n\n        s_logger.debug(APP_ID + \": This is a debug message.\");\n\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n\n    }\n\n}\n

    The activate() method is the entry point when the bundle in started. The deactivate() method is the entry point when the bundle is stopped.

    Notice the use of the private LoggerFactory.getLogger() method. If the LoggerFactory method is present (running) in the OSGi framework and your hello_osgi bundle is started, your activate method is called, and you can simply access the service by calling the getLogger() method.

    One convenient feature of Eclipse, auto-completion, is worth mentioning here. If you type \u2018s_logger.\u2019 (instance name of the \u201cLoggerFactory.getLogger\u201d method) and stop after the period, it will show you a list of methods implemented in that class. The examples above show two different methods used for logging messages. Logger methods include: \u201cerror\u201d, \u201cwarn\u201d, \u201cinfo\u201d, \u201cdebug\u201d, and \u201ctrace\u201d, which represent increasingly lower (more detailed) levels of log information. Logger levels should generally be used to represent the following conditions:

    • ERROR - A serious problem has occurred that requires attention from the system administrator.

    • WARNING - An action occurred or a condition was discovered that should be reviewed and may require action before an error occurs. It may also be used for transient issues.

    • INFO - A report of a normal action or event. This could be a user operation, such as \"login completed\", or an automatic operation, such as a log file rotation.

    • DEBUG - A debug message used for troubleshooting or performance monitoring. It typically contains detailed event data including things an application developer would need to know.

    • TRACE - A fairly detailed output of diagnostic logging, such as actual bytes of a particular message being examined.

    "},{"location":"java-application-development/hello-world-application/#resolve-dependencies","title":"Resolve Dependencies","text":"

    At this point, there will be errors in your code because of unresolved imports.

    Select the menu Source | Organize Imports to resolve these errors. Because you added the \u201corg.slf4j\u201d to your dependency list, you will be prompted to choose one of two potential sources for importing the \u201cLogger\u201d class. Select org.slf4j.Logger and click Finish.

    Now the errors in the class should have been resolved. Save the HelloOsgi class.

    The complete set of code (with import statements) is shown below.

    package org.eclipse.kura.example.hello_osgi;\n\nimport org.osgi.service.component.ComponentContext;\n\nimport org.slf4j.Logger;\n\nimport org.slf4j.LoggerFactory;\n\npublic class HelloOsgi {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(HelloOsgi.class);\n\n    private static final String APP_ID = \"org.eclipse.kura.example.hello_osgi\";\n\n    protected void activate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n\n        s_logger.debug(APP_ID + \": This is a debug message.\");\n\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n\n    }\n\n}\n

    For more information on using the Simple Logging Facade for Java (slf4j), see the Logger API.

    Switch back to the Manifest Editor. Under Automated Management of Dependencies, ensure the Import-Package option button is selected. Click the add dependencies link to automatically add packages to the dependencies list based on the \u201cimport\u201d statements in your example code. Save changes to the Manifest again.

    "},{"location":"java-application-development/hello-world-application/#create-component-class","title":"Create Component Class","text":"

    Right-click the example project and select New | Other. From the wizard, select Plug-in Development | Component Definition and click Next.

    Warning

    This option is available only if the Plug-in Development Environment (PDE) is installed in Eclipse (plugins can be installed into Eclipse IDE by searching the name in the Eclipse Marketplace under the Help menu).

    In the Class field of the New Component Definition window shown below, click Browse.

    Enter the name of your newly created class in the Select entries field. In this case, type the word \u201chello\u201d, and you will see a list of matching items. Select the HelloOsgi class and click OK.

    In the Enter or select the parent folder field of the New Component Definition window, add \"/OSGI-INF\" to the existing entry (e.g., org.eclipse.kura.example.hello_osgi/OSGI-INF). Then click Finish.

    After the Component class has been created, it will open in the Workspace. In the Overview tab, the Name and Class point to our Java class. Set the Activate field to activate and set the Deactivate field to deactivate. Doing so tells the component where these OSGi activation methods are located. Then save the Component class definition file.

    "},{"location":"java-application-development/hello-world-application/#deploying-the-plug-in","title":"Deploying the Plug-in","text":"

    The next few sections describe how to create a stand-alone JAR file as a deployable OSGI plug-in and how to create an installable Deployment Package.

    An OSGi bundle is a Java archive file containing Java code, resources, and a Manifest.

    A Deployment Package is a set of resources grouped into a single package file that may be deployed in the OSGi framework through the Deployment Admin service and may contain one or more bundles, configuration objects, etc.

    "},{"location":"java-application-development/hello-world-application/#export-the-osgi-bundle","title":"Export the OSGi Bundle","text":"

    Your bundle can be built as a stand-alone OSGi plug-in.

    To do so, right-click the project and select the Export menu. This is equivalent to running javac on your project. From the wizard, select Plug-in Development | Deployable plug-ins and fragments and click Next.

    Under Available Plug-ins and Fragments of the Export window, ensure the newly created plug-in is selected. Under Destination, select the Directory option button and use the Browse button to select an appropriate place to save the JAR file on the local file system.

    NOTE: During the deployment process that is described in the following section, you will need to remember the location where this JAR file is saved.

    Click Finish.

    This will create a JAR file in the selected directory (e.g., /home/joe/myPlugins/plugins/org.eclipse.kura.example.hello_osgi_1.0.0.jar).

    "},{"location":"java-application-development/hello-world-application/#create-a-deployment-package","title":"Create a Deployment Package","text":"

    Rather than creating a stand-alone plug-in, you can also create a Deployment Package that contains multiple bundles, configuration elements, etc. that can be deployed into an OSGi framework. In this example, you will simply create a Deployment Package containing the \u201chello_osgi\u201d bundle. This step requires mToolkit to be installed. (See Kura Setup for instructions on setting up the Eclipse development environment.)

    Right-click the project and select New | Folder. Select the org.eclipse.kura.example.hello_osgi project and enter a folder named \u201cresources\u201d.

    Then repeat this step to create a folder named \u201cdp\u201d under the resources folder. The resources/dp folder will be used to store the Deployment Package.

    Select File | New | Other. Select OSGi | Deployment Package Definition and click Next.

    Ensure that the Target folder field of the New dpp file window is set to the /[project_name]/resources/dp folder. In the File name field, enter the name for the new Deployment Package file to create, such as \u201chello_osgi\u201d. A version number can also be entered in the Version field. Then click Finish.

    Under the resources/dp folder in your project, verify that the [filename].dpp file was created. This is a Deployment Package Project that provides information needed to create the Deployment Package, such as its output directory, ant build file, etc.

    Select the Bundles tab and then click New. In the Bundle Path column, select the browse icon. Browse to the bundle\u2019s JAR file created earlier. Select the file and click Open. Doing so should populate the remaining columns as needed.

    Save changes to the deployment package file.

    In the resources/dp folder, right-click the .dpp file. Select Quick Build. A new [filename].dp file will be created in the same directory. This is the final Deployment Package that can be installed on a remote target system.

    In conclusion, you were able to create a new bundle from scratch, write the Java code and Activator, modify the Manifest, and build the plug-in and/or Deployment Package that can be used in a Kura environment.

    The next steps will be to test your code in an Emulation mode and/or to deploy your code to a target system running Kura. See Testing and Deploying Bundles to continue with those steps.

    "},{"location":"java-application-development/how-to-manage-network-settings/","title":"How to manage Network Settings","text":"

    This section provides an example of how to create a Kura bundle that can be used to configure the network interfaces of your device. In this example, you will learn how to perform the following functions:

    • Create a plugin that configures the network interfaces

    • Connect to a wireless access point

    • Create a wireless access point

    As written, the example code configures the device with a static Wi-Fi configuration. Typically, the device settings would be defined through the Kura Gateway Administration Console instead of through Java code.

    A more practical application of this example is for IP network interfaces that need to be dynamically modified based on some external trigger or condition, such as geo-fencing. The Kura framework allows the device to be programmatically changed via its APIs based on application-specific logic.

    "},{"location":"java-application-development/how-to-manage-network-settings/#prerequisites","title":"Prerequisites","text":"

    Setting up the Eclipse Kura Development Environment

    "},{"location":"java-application-development/how-to-manage-network-settings/#network-configuration-with-kura","title":"Network Configuration with Kura","text":""},{"location":"java-application-development/how-to-manage-network-settings/#hardware-setup","title":"Hardware Setup","text":"

    This example requires an embedded device running Kura with at least one Ethernet port and Wi-Fi support.

    Additionally, the Connect to an Access Point section requires a wireless access point and the following information about the access point:

    • SSID (Network Name)

    • Security Type (WEP, WPA, or WPA2), if any

    • Password/Passphrase, if any

    Lastly, the Create an Access Point section requires:

    • A wireless device, such as a laptop, to test the access point.

    • Optionally, you may connect the Kura device\u2019s Ethernet port to another network and use Kura as a gateway to that network.

    "},{"location":"java-application-development/how-to-manage-network-settings/#determine-your-network-interfaces","title":"Determine Your Network Interfaces","text":"

    In order to determine your network interfaces, run one of the following commands at a terminal on the embedded gateway:

    ifconfig -a or ip link show

    Typical network interfaces will appear as follows:

    • lo - loopback interface

    • eth0 - first Ethernet network interface

    • wlan0 - first wireless network interface

    • ppp0 - first point-to-point protocol network interface, which could be a dial-up modem, PPTP VPN connection, cellular modem, etc.

    Make note of your wireless interface. For this tutorial, we will assume the wireless interface name is \u2018wlan0\u2019.

    "},{"location":"java-application-development/how-to-manage-network-settings/#kura-networking-api","title":"Kura Networking API","text":"

    The networking API consists of two basic services: org.eclipse.kura.net.NetworkService and org.eclipse.kura.net.NetworkAdminService.

    The NetworkService is used to get the current state of the network. For example, the getNetworkInterfaces() method will return a List of NetInterface objects (such as EthernetInterface or WifiInterface) for each interface. This provides a detailed representation of the current state of that interface, such as its type, whether it is currently up, and its current address, which is returned by the getNetInterfaceAddresses() method as a List of NetInterfaceAddress objects. The NetworkService can also be used to get a list of all the network interface names available on the system, or a list of all the Wi-Fi access points that are currently detected by the system.

    The NetworkAdminService is used to get and set the configuration for each interface. Similar to the NetworkService, it has a getNetworkInterfaceConfigs() that returns a List of NetInterfaceConfig objects (such as EthernetInterfaceConfig and WifiInterfaceConfig) for each interface. These have the same methods as a NetInterface object but represent the current configuration for that interface. For a NetInterfaceConfig object, the getNetInterfaceAddress() method will return a List of NetInterfaceAddressConfig objects. These NetInterfaceAddressConfig instances, in turn, contain a List of NetConfig objects that define the configuration for that interface.

    There are many types of NetConfig objects, including:

    • NetConfigIP4 - contains the IPv4 address configuration

    • WifiConfig - contains the Wi-Fi configuration. Note that a WifiInterfaceAddressConfig may contain multiple WifiConfigs, since a configuration might exist for one or more Wi-Fi modes. The currently active WifiConfig is the one with a WifiMode that matches the WifiInterfaceAddressConfig WifiMode.

    • DhcpServerConfigIP4 - contains the IPv4-based DHCP server configuration

    • DnsServerConfigIP4 - contains the IPv4-based DNS server configuration

    • FirewallNatConfig - contains the firewall NAT configuration

    These NetConfigs can also be used to configure an interface by providing them as a list to the updateEthernetInterfaceConfig(), updateWifiInterfaceConfig(), or updateModemInterfaceConfig() methods in the NetworkAdminService.

    "},{"location":"java-application-development/how-to-manage-network-settings/#connect-to-an-access-point","title":"Connect to an Access Point","text":"

    In this section, you will develop a Kura network configuration bundle that sets up the Wi-Fi interface as a client to a wireless access point.

    "},{"location":"java-application-development/how-to-manage-network-settings/#implement-the-bundle","title":"Implement the Bundle","text":"

    To implement the network configuration bundle, perform the following steps:

    Note

    For more detailed information about bundle development (i.e., the plug-in project, classes, and MANIFEST file configuration), please refer to the Hello World Application

    • Create a Plug-in Project named org.eclipse.kura.example.network; set the an OSGi framework option to standard; uncheck the Generate an activator option; and set the Execution Environment variable to match the JVM on your target device.

    • Include the following bundles in the MANIFEST.MF:

    • org.eclipse.kura
    • org.eclipse.kura.net
    • org.eclipse.kura.net.dhcp
    • org.eclipse.kura.net.firewall
    • org.eclipse.kura.net.wifi
    • org.osgi.service.component
    • org.slf4j

    • Create a class named NetworkConfigExample in the org.eclipse.kura.example.network project.

    • Create an OSGI-INF folder in the org.eclipse.kura.example.network project. Add a Component Class with the parent folder org.eclipse.kura.example.network/OSGI-INF, Component Name org.eclipse.kura.example.network, and Class org.eclipse.kura.example.network.NetworkConfigExample.

    • Select the Services tab in the component.xml file. Under Referenced Services, add org.eclipse.kura.net.NetworkAdminService. Edit the properties of this service, and configure the Bind property to setNetworkAdminService and Unbind to unsetNetworkAdminService as shown in the following screen capture. These settings are required because of the dependency on NetworkAdminService.

    The following source code will also need to be implemented:

    • META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and its dependencies.

    • OSGI-INF/component.xml - declarative services definition describing what services are exposed by and consumed by this bundle.

    • org.eclipse.kura.example.network.NetworkConfigExample.java - main implementation class.

    "},{"location":"java-application-development/how-to-manage-network-settings/#meta-infmanifestmf-file","title":"META-INF/MANIFEST.MF File","text":"

    The META-INF/MANIFEST.MF file should look as follows when complete:

    Warning

    Whitespace is significant in this file; make sure yours matches this file exactly.

    Manifest-Version: 1.0\nBundle-ManifestVersion: 2\nBundle-Name: Network\nBundle-SymbolicName: org.eclipse.kura.example.network\nBundle-Version: 1.0.0.qualifier\nBundle-Vendor: ECLIPSE\nBundle-RequiredExecutionEnvironment: JavaSE-1.7\nImport-Package: org.eclipse.kura,\n org.eclipse.kura.net,\n org.eclipse.kura.net.dhcp,\n org.eclipse.kura.net.firewall,\n org.eclipse.kura.net.wifi,\n org.osgi.service.component;version=\"1.2.0\",\n org.slf4j;version=\"1.6.4\"\nService-Component: OSGI-INF/component.xml\n
    "},{"location":"java-application-development/how-to-manage-network-settings/#osgi-infcomponentxml-file","title":"OSGI-INF/component.xml File","text":"
    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    name=\"org.eclipse.kura.example.network.NetworkConfigExample\"\n    activate=\"activate\"\n    deactivate=\"deactivate\"\n    enabled=\"true\"\n    immediate=\"true\"\n    configuration-policy=\"require\"\n    modified=\"updated\">\n   <implementation class=\"org.eclipse.kura.example.network.NetworkConfigExample\"/>\n   <service>\n        <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n   <reference name=\"NetworkAdminService\"\n        interface=\"org.eclipse.kura.net.NetworkAdminService\"\n        policy=\"static\"\n        cardinality=\"1..1\"\n        bind=\"setNetworkAdminService\"\n        unbind=\"unsetNetworkAdminService\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.network.NetworkConfigExample\"/>\n</scr:component>\n
    "},{"location":"java-application-development/how-to-manage-network-settings/#orgeclipsekuraexamplenetworknetworkconfigexamplejava","title":"org.eclipse.kura.example.network.NetworkConfigExample.java","text":"
    package org.eclipse.kura.example.network;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.osgi.service.component.ComponentContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.eclipse.kura.KuraException;\nimport org.eclipse.kura.net.IP4Address;\nimport org.eclipse.kura.net.IPAddress;\nimport org.eclipse.kura.net.NetConfig;\nimport org.eclipse.kura.net.NetConfigIP4;\nimport org.eclipse.kura.net.NetInterfaceStatus;\nimport org.eclipse.kura.net.NetworkAdminService;\nimport org.eclipse.kura.net.dhcp.DhcpServerConfigIP4;\nimport org.eclipse.kura.net.firewall.FirewallNatConfig;\nimport org.eclipse.kura.net.wifi.WifiCiphers;\nimport org.eclipse.kura.net.wifi.WifiConfig;\nimport org.eclipse.kura.net.wifi.WifiMode;\nimport org.eclipse.kura.net.wifi.WifiRadioMode;\nimport org.eclipse.kura.net.wifi.WifiSecurity;\n\npublic class NetworkConfigExample {\n\n  private static final Logger s_logger = LoggerFactory.getLogger(NetworkConfigExample.class);\n\n  private NetworkAdminService m_netAdminService;\n\n  // ----------------------------------------------------------------\n  //\n  //   Dependencies\n  //\n  // ----------------------------------------------------------------\n\n  public void setNetworkAdminService(NetworkAdminService netAdminService) {\n    this.m_netAdminService = netAdminService;\n  }\n\n  public void unsetNetworkAdminService(NetworkAdminService netAdminService) {\n    this.m_netAdminService = null;\n  }\n\n\n  // ----------------------------------------------------------------\n  //\n  //   Activation APIs\n  //\n  // ----------------------------------------------------------------\n\n  protected void activate(ComponentContext componentContext) {\n    s_logger.info(\"Activating NetworkConfigExample...\");\n\n    connectToWirelessAccessPoint();\n//      createWirelessAccessPoint();\n\n    s_logger.info(\"Activating NetworkConfigExample... Done.\");\n  }\n\n  protected void deactivate(ComponentContext componentContext) {\n    s_logger.info(\"Deactivating NetworkConfigExample...\");\n\n    s_logger.info(\"Deactivating NetworkConfigExample... Done.\");\n  }\n\n\n  // ----------------------------------------------------------------\n  //\n  //   Private Methods\n  //\n  // ----------------------------------------------------------------\n\n  /**\n   * Connect to a wireless access point using the hard-coded parameters below\n   */\n  private void connectToWirelessAccessPoint() {\n    String interfaceName = \"wlan0\";\n\n    // Create a NetConfigIP4 - configure as a WAN (gateway) interface, and a DHCP client\n    NetInterfaceStatus netInterfaceStatus = NetInterfaceStatus.netIPv4StatusEnabledWAN;\n    boolean dhcpClient = true;\n    boolean autoConnect = true;\n    NetConfigIP4 netConfigIP4 = new NetConfigIP4(netInterfaceStatus, autoConnect, dhcpClient);\n\n    // Create a WifiConfig managed mode client\n    String driver = \"nl80211\";\n    String ssid = \"access_point_ssid\";\n    String password = \"password\";\n    WifiSecurity security = WifiSecurity.SECURITY_WPA2;\n    WifiCiphers ciphers = WifiCiphers.CCMP_TKIP;\n    int[] channels = {1,2,3,4,5,6,7,8,9,10,11};\n\n    WifiConfig wifiConfig = new WifiConfig();\n    wifiConfig.setMode(WifiMode.INFRA);\n    wifiConfig.setDriver(driver);\n    wifiConfig.setSSID(ssid);\n    wifiConfig.setPasskey(password);\n    wifiConfig.setSecurity(security);\n    wifiConfig.setChannels(channels);\n    wifiConfig.setGroupCiphers(ciphers);\n    wifiConfig.setPairwiseCiphers(ciphers);\n\n    // Create a NetConfig List\n    List<NetConfig> netConfigs = new ArrayList<NetConfig>();\n    netConfigs.add(netConfigIP4);\n    netConfigs.add(wifiConfig);\n\n    // Configure the interface\n    try{\n      s_logger.info(\"Reconfiguring \" + interfaceName + \" to connect to \" + ssid);\n      m_netAdminService.disableInterface(interfaceName);\n      m_netAdminService.updateWifiInterfaceConfig(interfaceName, autoConnect, null, netConfigs);\n\n      s_logger.info(\"Enable \" + interfaceName);\n      m_netAdminService.enableInterface(interfaceName, dhcpClient);\n    } catch(KuraException e) {\n      s_logger.error(\"Error connecting to wireless access point\", e);\n    }\n  }\n\n  /**\n   * Create a wireless access point\n   */\n  private void createWirelessAccessPoint() {\n    try{\n      String interfaceName = \"wlan0\";\n\n      // Create a NetConfigIP4 - configure as a LAN interface with a manual IP address\n      NetInterfaceStatus netInterfaceStatus = NetInterfaceStatus.netIPv4StatusEnabledLAN;\n      boolean dhcpClient = false;\n      boolean autoConnect = true;\n      IP4Address ipAddress = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.1\");\n      IP4Address subnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n\n      NetConfigIP4 netConfigIP4 = new NetConfigIP4(netInterfaceStatus, autoConnect);\n      netConfigIP4.setAddress(ipAddress);\n      netConfigIP4.setSubnetMask(subnetMask);\n\n      // Create a WifiConfig access point\n      String driver = \"nl80211\";\n      String ssid = \"NetworkConfigExample\";\n      String password = \"password\";\n      WifiSecurity security = WifiSecurity.SECURITY_WPA2;\n      WifiCiphers ciphers = WifiCiphers.CCMP_TKIP;\n      int[] channels = {1};\n      WifiRadioMode radioMode = WifiRadioMode.RADIO_MODE_80211g;\n      String hwMode = \"g\";\n\n      WifiConfig wifiConfig = new WifiConfig();\n      wifiConfig.setMode(WifiMode.MASTER);\n      wifiConfig.setDriver(driver);\n      wifiConfig.setSSID(ssid);\n      wifiConfig.setPasskey(password);\n      wifiConfig.setSecurity(security);\n      wifiConfig.setChannels(channels);\n      wifiConfig.setGroupCiphers(ciphers);\n      wifiConfig.setPairwiseCiphers(ciphers);\n      wifiConfig.setRadioMode(radioMode);\n      wifiConfig.setHardwareMode(hwMode);\n\n\n      // Create a DhcpServerConfig to enable DHCP server functionality\n      int defaultLeaseTime = 7200;\n      int maximumLeaseTime = 7200;\n      IP4Address routerAddress = ipAddress;\n      IP4Address rangeStart = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.100\");\n      IP4Address rangeEnd = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.200\");\n      IP4Address dhcpSubnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n      IP4Address subnet = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.0\");\n      short prefix = 24;\n      boolean passDns = true;\n\n      List<IP4Address> dnsServers = new ArrayList<IP4Address>();\n      dnsServers.add(ipAddress);                // Use our IP as the DNS server\n\n      DhcpServerConfigIP4 dhcpServerConfigIP4 = new DhcpServerConfigIP4(\n          interfaceName, true, subnet, routerAddress, dhcpSubnetMask, defaultLeaseTime,\n          maximumLeaseTime, prefix, rangeStart, rangeEnd, passDns, dnsServers);\n\n      // Create a FirewallNatConfig to enable NAT (network address translation)\n      // note that the destination interface is determined dynamically\n      FirewallNatConfig natConfig = new FirewallNatConfig(interfaceName, \"tbd\", true);\n\n\n      // Create a NetConfig List\n      List<NetConfig> netConfigs = new ArrayList<NetConfig>();\n      netConfigs.add(netConfigIP4);\n      netConfigs.add(wifiConfig);\n      netConfigs.add(dhcpServerConfigIP4);\n      netConfigs.add(natConfig);\n\n      // Configure the interface\n      s_logger.info(\"Reconfiguring \" + interfaceName + \" as an access point with SSID: \" + ssid);\n      m_netAdminService.disableInterface(interfaceName);\n      m_netAdminService.updateWifiInterfaceConfig(interfaceName, autoConnect, null, netConfigs);\n\n      s_logger.info(\"Enable \" + interfaceName);\n      m_netAdminService.enableInterface(interfaceName, dhcpClient);\n    } catch(Exception e) {\n      s_logger.error(\"Error configuring as an access point\", e);\n    }\n  }\n}\n

    Modify the parameters in the connectToWirelessAccessPoint() method with the specific values for the access point you want to connect to, including the variables for SSID, password, and security settings:

    • String ssid = \"access_point_ssid\";

    • String password = \"password\";

    • WifiSecurity security = WifiSecurity.SECURITY_WPA2;

    At this point, the bundle implementation is complete. Make sure to save all files before proceeding.

    Export the OSGi bundle as a stand-alone plug-in, following the instructions in Hello World Using the Kura Logger.

    "},{"location":"java-application-development/how-to-manage-network-settings/#deploy-the-bundle","title":"Deploy the Bundle","text":"

    In order to proceed, you need to know the IP address of your embedded gateway that is running Kura. Follow the mToolkit instructions for installing a single bundle to the remote target device located here. Once the bundle has finished deploying, it will set the device\u2019s network configuration and attempt to connect to a Wi-Fi access point using the configured parameters in the connectToWirelessAccessPoint() method.

    "},{"location":"java-application-development/how-to-manage-network-settings/#test-the-connection-to-the-access-point","title":"Test the Connection to the Access Point","text":"

    To verify that the interface (wlan0) has acquired an IP address, run the ifconfig command at a terminal on the embedded gateway.

    To show the current connection status to the access point, run the following commands:

    wpa_cli -i wlan0 status\niw dev wlan0 link\niw dev wlan0 station dump\n
    "},{"location":"java-application-development/how-to-manage-network-settings/#create-an-access-point","title":"Create an Access Point","text":"

    This example code can be modified slightly to make the gateway function as an access point instead of connecting to an access point.

    To do this, modify the activate() method in the NetworkConfigExample.java file to comment out connectToWirelessAccessPoint() and uncomment createWirelessAccessPoint().

    protected void activate(ComponentContext componentContext) {\n  s_logger.info(\"Activating NetworkConfigExample...\");\n\n//  connectToWirelessAccessPoint();\n  createWirelessAccessPoint();\n\n  s_logger.info(\"Activating NetworkConfigExample... Done.\");\n}\n

    Modify the access point configuration variables under createWirelessAccessPoint() for your needs, if necessary, such as the variables:

    IP4Address ipAddress = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.1\");\nIP4Address subnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n\n// Create a WifiConfig access point\nString driver = \"nl80211\";\nString ssid = \"NetworkConfigExample\";\nString password = \"password\";\n

    Export the bundle again as a stand-alone OSGi plug-in and redeploy it to the target device. It should now reconfigure itself to create an access point with an active DHCP server, DNS proxy forwarding, and NAT enabled.

    "},{"location":"java-application-development/how-to-manage-network-settings/#test-the-access-point","title":"Test the Access Point","text":"

    To verify that the interface (wlan0) has a fixed IP address, run the ifconfig command at a terminal on the embedded gateway.

    To view information on the Wi-Fi access point, including interface name, wireless channel, and MAC address, enter:

    iw dev wlan0 info

    To monitor connect and disconnect events from the access point, enter:

    iw event \u2013f

    Use another wireless client, such as a laptop, to verify that you can connect to the access point, that it receives an IP address, and that it can ping the network. When the client connects to the access point, the console should show a new station connection event.

    To view station statistic information, including signal strength and bitrate, enter:

    iw dev wlan0 station dump

    Optionally, if the gateway has another interface configured for WAN with connection to the Internet, then the wireless client should be able to reach the Internet using this access point as its gateway. The setup for the other interface (not covered in this example) would need to be configured in the device using the Kura Gateway Administration Console.

    "},{"location":"java-application-development/how-to-serial-ports/","title":"How to Use Serial Ports","text":""},{"location":"java-application-development/how-to-serial-ports/#overview","title":"Overview","text":"

    This section provides an example of how to create a Kura bundle that will communicate with a serial device. In this example, you will communicate with a simple terminal emulator to demonstrate both transmitting and receiving data. You will learn how to perform the following functions:

    • Create a plugin that communicates to serial devices

    • Export the bundle

    • Install the bundle on the remote device

    • Test the communication with minicom where, minicom is acting as an attached serial device such as an NFC reader, GPS device, or some other ASCII-based communication device

    "},{"location":"java-application-development/how-to-serial-ports/#prerequisites","title":"Prerequisites","text":"
    • Setting up Kura Development Environment

    • Hello World Using the Kura Logger

    • Hardware

      • Use an embedded device running Kura with two available serial ports. (If the device does not have a serial port, USB to serial adapters can be used.)

      • Ensure minicom is installed on the embedded device.

    "},{"location":"java-application-development/how-to-serial-ports/#serial-communication-with-kura","title":"Serial Communication with Kura","text":"

    This section of the tutorial covers setting up the hardware, determining serial port device nodes, implementing the basic serial communication bundle, deploying the bundle, and validating its functionality. After completing this section, you should be able to communicate with any ASCII-based serial device attached to a Kura-enabled embedded gateway. In this example, we are using ASCII for clarity, but these same techniques can be used to communicate with serial devices that communicate using binary protocols.

    "},{"location":"java-application-development/how-to-serial-ports/#hardware-setup","title":"Hardware Setup","text":"

    Your setup requirements will depend on your hardware platform. At a minimum, you will need two serial ports with a null modem serial, crossover cable connecting them.

    • If your platform has integrated serial ports, you only need to connect them using a null modem serial cable.

    • If you do not have integrated serial ports on your platform, you will need to purchase USB-to-Serial adapters. It is recommended to use a USB-to-Serial adapter with either the PL2303 or FTDI chipset, but others may work depending on your hardware platform and underlying Linux support. Once you have attached these adapters to your device, you can attach the null modem serial cable between the two ports.

    "},{"location":"java-application-development/how-to-serial-ports/#determine-serial-device-nodes","title":"Determine Serial Device Nodes","text":"

    This step is hardware specific. If your hardware device has integrated serial ports, contact your hardware device manufacturer or review the documentation to find out how the ports are named in the operating system. The device identifiers should be similar to the following:

    /dev/ttyS*xx*\n/dev/ttyUSB*xx*\n/dev/ttyACM*xx*\n

    If you are using USB-to-Serial adapters, Linux usually allocates the associated device nodes dynamically at the time of insertion. In order to determine what they are, run the following command at a terminal on the embedded gateway:

    tail -f /var/log/syslog\n

    Warning

    Depending on your specific Linux implementation, other possible log files may be: /var/log/kern.log, /var/log/kernel, or /var/log/dmesg.

    With the above command running, insert your USB-to-Serial adapter. You should see output similar to the following:

    root@localhost:/root> tail -f /var/log/syslog\nAug 15 18:43:47 localhost kernel: usb 3-2: new full speed USB device using uhci_hcd and address 3\nAug 15 18:43:47 localhost kernel: pl2303 3-2:1.0: pl2303 converter detected\nAug 15 18:43:47 localhost kernel: usb 3-2: pl2303 converter now attached to ttyUSB10\n

    In this example, our device is a PL2303-compatible device and is allocated a device node of \u201c/dev/ttyUSB10\u201d. While your results may differ, the key is to identify the \u201ctty\u201d device that was allocated. For the rest of this tutorial, this device will be referred to as [device_node_1], which in this example is /dev/ttyUSB10. During development, it is also important to keep in mind that these values are dynamic; therefore, from one boot to the next and one insertion to the next, these values may change. To stop \u2018tail\u2019 from running in your console, escape with \u2018 c\u2019.

    If you are using two USB-to-Serial adapters, repeat the above procedure for the second serial port. The resulting device node will be referred to as [device_node_2].

    "},{"location":"java-application-development/how-to-serial-ports/#implement-the-bundle","title":"Implement the Bundle","text":"

    Now that you have two serial ports connected to each other, you are ready to implement the code. You will use the same general method that is described in section Hello World Application with the following exceptions:

    1. process to export the OSGi bundle will have an additional step,
    2. the actual code in this example will have the following differences:
      • The new Plug-in Project is named \u201corg.eclipse.kura.example.serial\u201d
      • A class named \u201cSerialExample\u201d is created in the org.eclipse.kura.example.serial project
      • The following bundles are included in the Automated Management of Dependencies section in the MANIFEST.MF:
        • javax.comm
        • javax.microedition.io
        • org.eclipse.kura.cloud
        • org.eclipse.kura.comm
        • org.eclipse.kura.configuration
        • org.osgi.service.component
        • org.osgi.service.io
        • org.slf4j

    The following files need to be implemented:

    • META-INF/MANIFEST.MF \u2013 OSGI manifest that describes the bundle and its dependencies

    • OSGI-INF/component.xml \u2013 declarative services definition that describe what services are exposed and consumed by this bundle

    • OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml \u2013 configuration description of the bundle and its parameters, types, and defaults

    • org.eclipse.kura.example.serial.SerialExample.java \u2013 main implementation class

    "},{"location":"java-application-development/how-to-serial-ports/#meta-infmanifestmf-file","title":"META-INF/MANIFEST.MF File","text":"

    The META-INF/MANIFEST.MF file should appear as shown below when complete:

    NOTE: Whitespace is significant in this file. Make sure yours matches this file exactly with the exception that RequiredExecutionEnvironment may be JavaSE-1.6 or JavaSE-1.7, depending on the Java installation of your device.

    Manifest-Version: 1.0\nBundle-ManifestVersion: 2\nBundle-Name: Serial\nBundle-SymbolicName: org.eclipse.kura.example.serial\nBundle-Version: 1.0.0.qualifier\nBundle-RequiredExecutionEnvironment: JavaSE-1.7\nService-Component: OSGI-INF/component.xml\nBundle-ActivationPolicy: lazy\nImport-Package: javax.comm;version=\"1.2.0\",\n  javax.microedition.io;resolution:=optional,\n  org.eclipse.kura.cloud;version=\"0.2.0\",\n  org.eclipse.kura.comm;version=\"0.2.0\",\n  org.eclipse.kura.configuration;version=\"0.2.0\",\n  org.osgi.service.component;version=\"1.2.0\",\n  org.osgi.service.io;version=\"1.0.0\",\n  org.slf4j;version=\"1.6.4\"\nBundle-ClassPath: .\n

    In addition, the build.properties file should have org.eclipse.equinox.io listed as an additional bundle similar to below:

    additional.bundles = org.eclipse.equinox.io\n
    "},{"location":"java-application-development/how-to-serial-ports/#osgi-infcomponentxml-file","title":"OSGI-INF/component.xml File","text":"

    Warning

    Starting from Kura 3.0, the configuration service will only track \"relevant services\" that, in their component description files, will provide the ConfigurableComponent or SelfConfigurableComponent interface. The old behavior can be restored by setting the \"org.eclipse.kura.core.configuration.legacyServiceTracking\" property to true.

    If Kura 2.1.0 or older versions are used or the org.eclipse.kura.core.configuration.legacyServiceTracking system property is set to true, the OSGI-INF/component.xml should appear as shown below when complete:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n  name=\"org.eclipse.kura.example.serial.SerialExample\" activate=\"activate\"\n  deactivate=\"deactivate\" modified=\"updated\" enabled=\"true\" immediate=\"true\"\n  configuration-policy=\"require\">\n\n  <implementation class=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.serial.SerialExample\"/>\n\n  <service>\n    <provide interface=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  </service>\n  <reference bind=\"setConnectionFactory\" cardinality=\"1..1\"\n    interface=\"org.osgi.service.io.ConnectionFactory\" name=\"ConnectionFactory\"\n    policy=\"static\" unbind=\"unsetConnectionFactory\" />\n</scr:component>\n

    If Kura 3.0 or newer versions are used and the org.eclipse.kura.core.configuration.legacyServiceTracking system property is set to false or not set, the OSGI-INF/component.xml should appear as shown below when complete:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n  name=\"org.eclipse.kura.example.serial.SerialExample\" activate=\"activate\"\n  deactivate=\"deactivate\" modified=\"updated\" enabled=\"true\" immediate=\"true\"\n  configuration-policy=\"require\">\n\n  <implementation class=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.serial.SerialExample\"/>\n\n  <service>\n    <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n  </service>\n  <reference bind=\"setConnectionFactory\" cardinality=\"1..1\"\n    interface=\"org.osgi.service.io.ConnectionFactory\" name=\"ConnectionFactory\"\n    policy=\"static\" unbind=\"unsetConnectionFactory\" />\n</scr:component>\n
    "},{"location":"java-application-development/how-to-serial-ports/#osgi-infmetatypeorgeclipsekuraexampleserialserialexamplexml-file","title":"OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml File","text":"

    The OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml file should appear as shown below when complete:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n  <OCD id=\"org.eclipse.kura.example.serial.SerialExample\"\n    name=\"SerialExample\"\n    description=\"Example of a Configuring KURA Application echoing data read from the serial port.\">\n\n    <Icon resource=\"http://sphotos-a.xx.fbcdn.net/hphotos-ash4/p480x480/408247_10151040905591065_1989684710_n.jpg\" size=\"32\"/>\n\n    <AD id=\"serial.device\"\n        name=\"serial.device\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"false\"\n        description=\"Name of the serial device (e.g. /dev/ttyS0, /dev/ttyACM0, /dev/ttyUSB0).\"/>\n\n    <AD id=\"serial.baudrate\"\n        name=\"serial.baudrate\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"9600\"\n        description=\"Baudrate.\">\n        <Option label=\"9600\" value=\"9600\"/>\n        <Option label=\"19200\" value=\"19200\"/>\n        <Option label=\"38400\" value=\"38400\"/>\n        <Option label=\"57600\" value=\"57600\"/>\n        <Option label=\"115200\" value=\"115200\"/>\n    </AD>\n\n    <AD id=\"serial.data-bits\"\n        name=\"serial.data-bits\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"8\"\n        description=\"Data bits.\">\n        <Option label=\"7\" value=\"7\"/>\n        <Option label=\"8\" value=\"8\"/>\n    </AD>\n\n    <AD id=\"serial.parity\"\n        name=\"serial.parity\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"none\"\n        description=\"Parity.\">\n        <Option label=\"none\" value=\"none\"/>\n        <Option label=\"even\" value=\"even\"/>\n        <Option label=\"odd\" value=\"odd\"/>\n    </AD>\n\n    <AD id=\"serial.stop-bits\"\n        name=\"serial.stop-bits\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"1\"\n        description=\"Stop bits.\">\n        <Option label=\"1\" value=\"1\"/>\n        <Option label=\"2\" value=\"2\"/>\n    </AD>\n\n  </OCD>\n  <Designate pid=\"org.eclipse.kura.example.serial.SerialExample\">\n    <Object ocdref=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  </Designate>\n</MetaData>\n
    "},{"location":"java-application-development/how-to-serial-ports/#orgeclipsekuraexampleserialserialexamplejava-file","title":"org.eclipse.kura.example.serial.SerialExample.java File","text":"

    The org.eclipse.kura.example.serial.SerialExample.java file should appear as shown below when complete:

    package org.eclipse.kura.example.serial;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport org.osgi.service.component.ComponentContext;\nimport org.osgi.service.io.ConnectionFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class SerialExample implements ConfigurableComponent {\n\n  private static final Logger s_logger = LoggerFactory.getLogger(SerialExample.class);\n\n  private static final String SERIAL_DEVICE_PROP_NAME= \"serial.device\";\n  private static final String SERIAL_BAUDRATE_PROP_NAME= \"serial.baudrate\";\n  private static final String SERIAL_DATA_BITS_PROP_NAME= \"serial.data-bits\";\n  private static final String SERIAL_PARITY_PROP_NAME= \"serial.parity\";\n  private static final String SERIAL_STOP_BITS_PROP_NAME= \"serial.stop-bits\";\n\n  private ConnectionFactory m_connectionFactory;\n  private CommConnection m_commConnection;\n  private InputStream m_commIs;\n  private OutputStream m_commOs;\n  private ScheduledThreadPoolExecutor m_worker;\n  private Future<?> m_handle;\n  private Map<String, Object> m_properties;\n\n  // ----------------------------------------------------------------\n  //\n  // Dependencies\n  //\n  // ----------------------------------------------------------------\n  public void setConnectionFactory(ConnectionFactory connectionFactory) {\n    this.m_connectionFactory = connectionFactory;\n  }\n\n  public void unsetConnectionFactory(ConnectionFactory connectionFactory) {\n    this.m_connectionFactory = null;\n  }\n\n  // ----------------------------------------------------------------\n  //\n  // Activation APIs\n  //\n  // ----------------------------------------------------------------\n\n  protected void activate(ComponentContext componentContext, Map<String,Object> properties) {\n    s_logger.info(\"Activating SerialExample...\");\n\n    m_worker = new ScheduledThreadPoolExecutor(1);\n    m_properties = new HashMap<String, Object>();\n    doUpdate(properties);\n    s_logger.info(\"Activating SerialExample... Done.\");\n  }\n\n  protected void deactivate(ComponentContext componentContext) {\n    s_logger.info(\"Deactivating SerialExample...\");\n\n    // shutting down the worker and cleaning up the properties\n    m_handle.cancel(true);\n    m_worker.shutdownNow();\n    //close the serial port\n    closePort();\n    s_logger.info(\"Deactivating SerialExample... Done.\");\n  }\n\n  public void updated(Map<String,Object> properties) {\n    s_logger.info(\"Updated SerialExample...\");\n\n    doUpdate(properties);\n    s_logger.info(\"Updated SerialExample... Done.\");\n  }\n\n  // ----------------------------------------------------------------\n  //\n  // Private Methods\n  //\n  // ----------------------------------------------------------------\n\n  /**\n   * Called after a new set of properties has been configured on the service\n   */\n  private void doUpdate(Map<String, Object> properties) {\n    try {\n      for (String s : properties.keySet()) {\n        s_logger.info(\"Update - \"+s+\": \"+properties.get(s));\n      }\n\n      // cancel a current worker handle if one if active\n      if (m_handle != null) {\n        m_handle.cancel(true);\n      }\n\n      //close the serial port so it can be reconfigured\n      closePort();\n\n      //store the properties\n      m_properties.clear();\n      m_properties.putAll(properties);\n\n      //reopen the port with the new configuration\n      openPort();\n\n      //start the worker thread\n      m_handle = m_worker.submit(new Runnable() {\n        @Override\n        public void run() {\n          doSerial();\n        }\n      });\n\n    } catch (Throwable t) {\n        s_logger.error(\"Unexpected Throwable\", t);\n      }\n  }\n\n  private void openPort() {\n    String port = (String) m_properties.get(SERIAL_DEVICE_PROP_NAME);\n\n    if (port == null) {\n      s_logger.info(\"Port name not configured\");\n      return;\n    }\n\n    int baudRate = Integer.valueOf((String) m_properties.get(SERIAL_BAUDRATE_PROP_NAME));\n    int dataBits = Integer.valueOf((String) m_properties.get(SERIAL_DATA_BITS_PROP_NAME));\n    int stopBits = Integer.valueOf((String) m_properties.get(SERIAL_STOP_BITS_PROP_NAME));\n    String sParity = (String) m_properties.get(SERIAL_PARITY_PROP_NAME);\n    int parity = CommURI.PARITY_NONE;\n\n    if (sParity.equals(\"none\")) {\n      parity = CommURI.PARITY_NONE;\n    } else if (sParity.equals(\"odd\")) {\n        parity = CommURI.PARITY_ODD;\n    } else if (sParity.equals(\"even\")) {\n        parity = CommURI.PARITY_EVEN;\n    }\n\n    String uri = new CommURI.Builder(port)\n    .withBaudRate(baudRate)\n    .withDataBits(dataBits)\n    .withStopBits(stopBits)\n    .withParity(parity)\n    .withTimeout(1000)\n    .build().toString();\n\n    try {\n      m_commConnection = (CommConnection) m_connectionFactory.createConnection(uri, 1, false);\n      m_commIs = m_commConnection.openInputStream();\n      m_commOs = m_commConnection.openOutputStream();\n      s_logger.info(port+\" open\");\n    } catch (IOException e) {\n      s_logger.error(\"Failed to open port \" + port, e);\n      cleanupPort();\n    }\n  }\n\n  private void cleanupPort() {\n\n    if (m_commIs != null) {\n      try {\n        s_logger.info(\"Closing port input stream...\");\n        m_commIs.close();\n        s_logger.info(\"Closed port input stream\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port input stream\", e);\n      }\n      m_commIs = null;\n    }\n\n    if (m_commOs != null) {\n      try {\n        s_logger.info(\"Closing port output stream...\");\n        m_commOs.close();\n        s_logger.info(\"Closed port output stream\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port output stream\", e);\n      }\n      m_commOs = null;\n    }\n\n    if (m_commConnection != null) {\n      try {\n        s_logger.info(\"Closing port...\");\n        m_commConnection.close();\n        s_logger.info(\"Closed port\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port\", e);\n      }\n      m_commConnection = null;\n    }\n  }\n\n  private void closePort() {\n    cleanupPort();\n  }\n\n  private void doSerial() {\n    if (m_commIs != null) {\n      try {\n        int c = -1;\n        StringBuilder sb = new StringBuilder();\n        while (m_commIs != null) {\n          if (m_commIs.available() != 0) {\n            c = m_commIs.read();\n          } else {\n            try {\n              Thread.sleep(100);\n              continue;\n            } catch (InterruptedException e) {\n                return;\n            }\n          }\n\n        // on reception of CR, publish the received sentence\n        if (c==13) {\n          s_logger.debug(\"Received serial input, echoing to output: \" + sb.toString());\n          sb.append(\"\\r\\n\");\n          String dataRead = sb.toString();\n          //echo the data to the output stream\n          m_commOs.write(dataRead.getBytes());\n          //reset the buffer\n          sb = new StringBuilder();\n        } else if (c!=10) {\n          sb.append((char) c);\n        }\n      }\n\n      } catch (IOException e) {\n          s_logger.error(\"Cannot read port\", e);\n      } finally {\n          try {\n            m_commIs.close();\n          } catch (IOException e) {\n            s_logger.error(\"Cannot close buffered reader\", e);\n          }\n      }\n    }\n  }\n}\n
    At this point, the bundle implementation is complete. Make sure to save all files before proceeding.

    "},{"location":"java-application-development/how-to-serial-ports/#export-the-bundle","title":"Export the Bundle","text":"

    To build the Serial Example bundle as a stand-alone OSGi plugin, right-click the project and select Export.

    From the wizard, select Plug-in Development | Deployable plug-ins and fragments and click Next.

    The Export window appears. Under Available Plug-ins and Fragments, verify that the newly created plug-in is selected.

    Under Destination, select the Directory option button and use the Browse button to select an appropriate place to save the JAR file on the local file system.

    Info

    You will need to know the location where this JAR file is saved for the deployment process.

    Under Options, select the checkbox Use class files compiled in the workspace in addition to the checkboxes already enabled, and click Finish.

    Doing so will create a JAR file in the selected directory (e.g., /home/joe/myPlugins/plugins/org.eclipse.kura.example.serial_1.0.0.201410311510.jar).

    "},{"location":"java-application-development/how-to-serial-ports/#deploy-the-bundle","title":"Deploy the Bundle","text":"

    In order to proceed, you need to know the IP address of your embedded gateway that is running Kura. Once you have this IP address, follow the mToolkit instructions for installing a single bundle to a remote target device (refer to section 2.03 Testing and Deploying Bundles).

    Once the installation successfully completes, you should see a message from the /var/log/kura.log file indicating that the bundle was successfully installed and configured. You can also run this example with the emulator in a Linux or OS X environment as shown sample output below. Make sure that your user account has owner permission for the serial device in /dev.

    "},{"location":"java-application-development/how-to-serial-ports/#validate-the-bundle","title":"Validate the Bundle","text":"

    Next, you need to test that your bundle does indeed echo characters back by opening minicom and configuring it to use [device_node_2] that was previously determined.

    Open minicom using the following command at a Linux terminal on the remote gateway device:

    minicom -s\n

    This command opens a view similar to the following screen capture:

    Scroll down to Serial port setup and press . A new dialog window opens as shown below:

    Use the minicom menu options on the left (i.e., A, B, C, etc.) to change desired fields. Set the fields to the same values as shown in the previous screen capture except the Serial Device should match the [device_node_2] on your target device. Once this is set, press \\<ENTER> to exit from this menu.

    In the main configuration menu, select Exit (do not select the option Exit from Minicom). At this point, you have successfully started minicom on the second serial port attached to your null modem cable allowing minicom to act as a serial device that can send and receive commands to your Kura bundle. You can verify this operation by typing characters and pressing . The function (specifically a \u2018\\n\u2019 character) signals to the Kura application to echo the buffered characters back to the serial device (minicom in this case).

    Upon startup, minicom sends an initialization string to the serial device. These characters are sent to the minicom terminal because they were echoed back by Kura listening on the port at the other end of the null modem cable.

    When you are done, exit minicom by pressing \u2018 a\u2019, then \u2018q\u2019, and finally \u2018\u2019. Doing so brings you back to the Linux command prompt.

    This tutorial instructed you how to write and deploy a Kura bundle on your target device that listens for serial data (coming from the minicom terminal and being received on [device_node_1]). This tutorial also demonstrated that the application echoes data back to the same serial port that is received in minicom, which acts a serial device that sends and receives data. If supported by the device, Kura may send and receive binary data instead of ASCII.

    "},{"location":"java-application-development/how-to-use-beacon-apis/","title":"How to Use Beacon APIs","text":""},{"location":"java-application-development/how-to-use-beacon-apis/#overview","title":"Overview","text":"

    Eclipse Kura implements a set of APIs for managing Bluetooth Low Energy and Beacon devices.

    The purpose of the BLE Beacon APIs is to simplify the development of applications that interact with Bluetooth LE Beacon devices, offering clear and easy-to-use methods for advertising and scanning. Eclipse Kura offers out-of-the-box the implementation of the Beacon APIs for iBeacon\u2122 and Eddystone\u2122 technologies.

    "},{"location":"java-application-development/how-to-use-beacon-apis/#how-to-use-kura-ibeacontm-apis","title":"How to use Kura iBeacon\u2122 APIs","text":"

    This section briefly presents how to use the iBeacon\u2122 implementation of the Kura Beacon APIs, providing several code snippets to explain how to perform common operations on iBeacons. For a complete example on iBeacon advertising and scanning, please refer to the iBeacon\u2122 advertiser and iBeacon\u2122 scanner examples. For more information about iBeacon\u2122 please refer to official page.

    An application that wants to use the iBeacon\u2122 implementation of Kura Beacon APIs should bind the BluetoothLeService and BluetoothLeIBeaconService OSGI services, as shown in the following Java snippet:

    public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n\npublic void setBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {\n    this.bluetoothLeIBeaconService = bluetoothLeIBeaconService;\n}\n\npublic void unsetBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {\n    this.bluetoothLeIBeaconService = null;\n}\n

    and in the component definition:

    <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n\n<reference bind=\"setBluetoothLeIBeaconService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.ble.ibeacon.BluetoothLeIBeaconService\" \n            name=\"BluetoothLeIBeaconService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeIBeaconService\"/>\n

    The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeIBeaconScanner and BluetoothLeIBeaconAdvertiser. As explained here, the adapter can be retrieved and powered on as follows:

    this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

    where adapterName is the name of the adapter, e.g. hci0.

    "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-ibeacontm-advertiser","title":"Create an iBeacon\u2122 advertiser","text":"

    In order to properly configure an iBeacon\u2122 advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconAdvertiser instance bound to a specific Bluetooth adapter:

    try {\n    BluetoothLeBeaconAdvertiser<BluetoothLeIBeacon> advertiser = this.bluetoothLeIBeaconService.newBeaconAdvertiser(this.bluetoothLeAdapter);\n} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {\n    logger.error(\"Beacon Advertiser not available on {}\", this.bluetoothLeAdapter.getInterfaceName(),e);\n}\n

    Then a BluetoothLeIBeacon object should be created, containing all the information to be broadcasted. In the following snippet, the BluetoothLeIBeacon object is instantiated and added to the advertiser. Then the broadcast time interval is set and the beacon advertising is started.

    try {\n    BluetoothLeIBeacon iBeacon = new BluetoothLeIBeacon(uuid, major, minor, txPower);\n    advertiser.updateBeaconAdvertisingData(iBeacon);\n    advertiser.updateBeaconAdvertisingInterval(minInterval, maxInterval;\n\n    advertiser.startBeaconAdvertising();\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"IBeacon configuration failed\", e);\n}\n

    The BluetoothLeIBeacon represents the beacon packet that will be broadcasted by the advertiser and it should be configured will the following parameters:

    • uuid a unique number that identifies the beacon.
    • major a number that identifies a subset of beacons within a large group.
    • minor a number that identifies a specific beacon.
    • txPower the transmitter power level indicating the signal strength one meter from the device.

    Warning

    Only one advertising packet can be broadcasted at a time on a specific Bluetooth adapter.

    Finally, in the following snippet the advertiser is stopped and removed from the BluetoothLeIBeaconService:

    try {\n    advertiser.stopBeaconAdvertising();\n    this.bluetoothLeIBeaconService.deleteBeaconAdvertiser(advertiser);\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Stop iBeacon advertising failed\", e);\n}\n
    "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-ibeacontm-scanner","title":"Create an iBeacon\u2122 scanner","text":"

    As done for the advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconScanner instance bound to a specific Bluetooth adapter:

    bluetoothLeBeaconScanner<BluetoothLeIBeacon> scanner = this.bluetoothLeIBeaconService.newBeaconScanner(this.bluetoothLeAdapter);\n

    A BluetoothLeIBeaconScanner needs a listener to collect the iBeacon packets that the Bluetooth adapter detects. In the following snippet, a simple listener that prints the iBeacon packet configuration is added to the scanner object:

    private class iBeaconListener implements BluetoothLeBeaconListener<BluetoothLeIBeacon> {\n\n    @Override\n    public void onBeaconsReceived(BluetoothLeIBeacon beacon) {\n        logger.info(\"iBeacon received from {}\", beacon.getAddress());\n        logger.info(\"UUID : {}\", beacon.getUuid());\n        logger.info(\"Major : {}\", beacon.getMajor());\n        logger.info(\"Minor : {}\", beacon.getMinor());\n        logger.info(\"TxPower : {}\", beacon.getTxPower());\n        logger.info(\"RSSI : {}\", beacon.getRssi());   \n    }\n\n}\n
    scanner.addBeaconListener(listener);\n

    The scanner is started for a specific time interval (in this case 10 seconds):

    scanner.startBeaconScan(10);\n

    Finally the scanner should be stopped, if needed, and the resources are released:

    if (scanner.isScanning()) {\n    scanner.stopBeaconScan();\n}\nscanner.removeBeaconListener(listener);\nthis.bluetoothLeIBeaconService.deleteBeaconScanner(scanner);\n

    Note

    The kura.legacy.bluetooth.beacon.scan property in the kura.properties file defines how the scan for beacons is performed. If set to true, the deprecated hcitool command is used. This guarantees that all the advertisement packets are reported. If set to false, the library will communicate to the OS using dbus. In the latter case, the rate of reports is limited and not all the advertisement packets are reported.

    "},{"location":"java-application-development/how-to-use-beacon-apis/#how-to-use-kura-eddystonetm-apis","title":"How to use Kura Eddystone\u2122 APIs","text":"

    Eddystone\u2122 is a protocol specification that defines a BLE message format for proximity beacon messages. It describes several different frame types that may be used individually or in combinations to create beacons that can be used for a variety of applications. For more information please see here and here.

    In this section the Eddystone\u2122 implementation of the Kura Beacon APIs is presented, providing several code snippets to explain how to perform common operations on them. For a complete example on Eddystone\u2122 advertising and scanning, please refer to the Eddystone\u2122 advertiser and Eddystone\u2122 scanner examples.

    Warning

    Only Eddystone UID and URL frame types are currently supported.

    As done with the iBeacon\u2122 implementation, an application has to bind the BluetoothLeService and BluetoothLeEddystoneService OSGI services, as shown in the following Java snippet:

    public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n\npublic void setBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {\n    this.bluetoothLeEddystoneService = bluetoothLeEddystoneService;\n}\n\npublic void unsetBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {\n    this.bluetoothLeEddystoneService = null;\n}\n

    and in the component definition:

    <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n\n<reference bind=\"setBluetoothLeEddystoneService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.ble.eddystone.BluetoothLeEddystoneService\" \n            name=\"BluetoothLeEddystoneService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeEddystoneService\"/>\n

    The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeEddystoneScanner and BluetoothLeEddystoneAdvertiser. As explained here, the adapter can be retrieved and powered on as follows:

    this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

    where adapterName is the name of the adapter, e.g. hci0.

    "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-eddystonetm-advertiser","title":"Create an Eddystone\u2122 advertiser","text":"

    In order to properly configure an Eddystone\u2122 advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneAdvertiser instance bound to a specific Bluetooth adapter:

    try {\n    BluetoothLeBeaconAdvertiser<BluetoothLeEddystone> advertiser = this.advertising = this.bluetoothLeEddystoneService.newBeaconAdvertiser(this.bluetoothLeAdapter);\n} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {\n    logger.error(\"Beacon Advertiser not available on {}\", this.bluetoothLeAdapter.getInterfaceName(),e);\n}\n

    The advertiser has to be configured with a BluetoothLeEddystone object that contains all the information to be broadcasted. Currently, UID and URL frame types are supported. A UID frame can be created as follows:

    BluetoothLeEddystone eddystone = new BluetoothLeEddystone();\neddystone.configureEddystoneUIDFrame(namespace, instance, txPower);\n

    where namespace and instance are respectively 10-byte and 6-byte long sequences that compose a unique 16-byte Beacon ID. The txPower is the calibrated transmission power at 0 m.

    A URL frame is created as follows:

    BluetoothLeEddystone eddystone = new BluetoothLeEddystone();\neddystone.configureEddystoneURLFrame(url, txPower);\n

    where url is the URL to be broadcasted and the txPower is the calibrated transmission power at 0 m.

    After the BluetoothLeEddystone creation, the packet is added to the advertiser and the broadcast time interval is set. Then the advertiser is started:

    try {\n    advertiser.updateBeaconAdvertisingData(eddystone);\n    advertiser.updateBeaconAdvertisingInterval(this.options.getMinInterval(), this.options.getMaxInterval());\n    advertiserstartBeaconAdvertising();\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Eddystone configuration failed\", e);\n}\n

    Finally, in the following snippet the advertiser is stopped and removed from the BluetoothLeEddystoneService:

    try {\n    advertiser.stopBeaconAdvertising();\n    this.bluetoothLeEddystoneService.deleteBeaconAdvertiser(advertiser);\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Stop Advertiser advertising failed\", e);\n}\n
    "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-eddystonetm-scanner","title":"Create an Eddystone\u2122 scanner","text":"

    As done for the advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneScanner instance bound to a specific Bluetooth adapter:

    bluetoothLeBeaconScanner<BluetoothLeEddystone> scanner = this.bluetoothLeEddystoneService.newBeaconScanner(this.bluetoothLeAdapter);\n

    A BluetoothLeEddystoneScanner needs a listener to collect the Eddystone packets that the Bluetooth adapter detects. In the following snippet, a simple listener that detects the frame type and prints the packet content is added to the scanner object:

    private class EddystoneListener implements BluetoothLeBeaconListener<BluetoothLeEddystone> {\n\n    @Override\n    public void onBeaconsReceived(BluetoothLeEddystone beacon) {\n        logger.info(\"Eddystone {} received from {}\", eddystone.getFrameType(), eddystone.getAddress());\n        if (\"UID\".equals(eddystone.getFrameType())) {\n            logger.info(\"Namespace : {}\", bytesArrayToHexString(eddystone.getNamespace()));\n            logger.info(\"Instance : {}\", bytesArrayToHexString(eddystone.getInstance()));\n        } else if (\"URL\".equals(eddystone.getFrameType())) {\n            logger.info(\"URL : {}\", eddystone.getUrlScheme() + eddystone.getUrl());\n        }\n        logger.info(\"TxPower : {}\", eddystone.getTxPower());\n        logger.info(\"RSSI : {}\", eddystone.getRssi());  \n    }\n\n}\n
    scanner.addBeaconListener(listener);\n

    The scanner is started for a specific time interval (in this case 10 seconds):

    scanner.startBeaconScan(10);\n

    Finally the scanner should be stopped, if needed, and the resources are released:

    if (scanner.isScanning()) {\n    scanner.stopBeaconScan();\n}\nscanner.removeBeaconListener(listener);\nthis.bluetoothLeEddystoneService.deleteBeaconScanner(scanner);\n

    Note

    The kura.legacy.bluetooth.beacon.scan property in the kura.properties file defines how the scan for beacons is performed. If set to true, the deprecated hcitool command is used. This guarantees that all the advertisement packets are reported. If set to false, the library will communicate to the OS using dbus. In the latter case, the rate of reports is limited and not all the advertisement packets are reported.

    "},{"location":"java-application-development/how-to-use-beacon-apis/#add-new-beacon-apis-implementation","title":"Add new Beacon APIs implementation","text":"

    Eclipse Kura offers the implementation for iBeacon\u2122 and Eddystone\u2122 protocols, but it is possible to add implementations of different kinds of beacon protocols.

    The org.eclipse.kura.bluetooth.le.beacon package contains the interfaces used by the beacon implementations:

    • BluetoothLeBeaconService is the entry point for applications that want to use the Beacon APIs.
    • BluetoothLeBeaconManager is used by the BluetoothLeBeaconService and provides methods to create and delete Beacon advertisers and scanners.
    • BluetoothLeBeaconAdvertiser allows configuring advertisement packets and managing advertising.
    • BluetoothLeBeaconScanner is used to search for specific Beacon packets.
    • BluetoothLeBeaconEncoder implements methods for encoding a Beacon object to a stream of bytes.
    • BluetoothLeBeaconDecoder implements methods for decoding a stream of bytes in to a Beacon object.
    • BluetoothLeBeacon represents a generic Beacon packet.

    The BluetoothLeBeaconManager, BluetoothLeBeaconScanner and BluetoothLeBeaconAdvertiser interfaces handles generic BluetoothLeBeacon objects and their implementations are provided by the org.eclipse.kura.ble.provider. The others interfaces, instead, are Beacon specific and their implementations depend on the specific protocol that is used. As a consequence, who wants to support a new Beacon protocol, should provide the implementation of the BluetoothLeBeaconService, BluetoothLeBeaconEncoder and BluetoothLeBeaconDecoder interfaces and extend the BluetoothLeBeacon class.

    As an example, the org.eclipse.kura.ble.ibeacon.provider provides the implementation of the above APIs for the iBeacon\u2122 protocol. In this case, the org.eclipse.kura.ble.ibeacon package contains the following:

    • BluetoothLeIBeacon implements the BluetoothLeBeacon interface for the iBeacon\u2122 packet.
    • BluetoothLeIBeaconEncoder is a marker interface that extends BluetoothLeBeaconEncoder.
    • BluetoothLeIBeaconDecoder is a marker interface that extends BluetoothLeBeaconDecoder.
    • BluetoothLeIBeaconService is a marker interface that extends BluetoothLeBeaconService and is the entry point for applications that wants to use an iBeacon\u2122.

    The org.eclipse.kura.internal.ble.ibeacon provides the implementations for the above interfaces:

    • BluetoothLeIBeaconEncoderImpl implements BluetoothLeIBeaconEncoder offering a method to encode the BluetoothLeIBeacon into a byte stream.
    • BluetoothLeIBeaconDecoderImpl implements BluetoothLeIBeaconDecoder offering a method to decode a stream of bytes into a BluetoothLeIBeacon object.
    • BluetoothLeIBeaconServiceImpl is the implementation of BluetoothLeIBeaconService and uses the generic BluetoothLeBeaconManager service to create scanners and advertisers.

    The following image shows the UML diagram.

    "},{"location":"java-application-development/how-to-use-bt-le-apis/","title":"How to Use Bluetooth LE APIs","text":""},{"location":"java-application-development/how-to-use-bt-le-apis/#overview","title":"Overview","text":"

    Eclipse Kura implements a set of APIs for managing Bluetooth Low Energy and Beacon devices.

    The purpose of the BLE APIs is to simplify the development of applications that interact with Bluetooth LE devices, offering clear and easy-to-use methods, and add new features to correctly manage the connection with remote devices. Moreover, the APIs organize the methods in a logical way to access all levels of a GATT client, from GATT services to GATT characteristics and descriptors, using UUIDs to identify the correct resource.

    "},{"location":"java-application-development/how-to-use-bt-le-apis/#bluez-dbus-ble-gatt-api","title":"Bluez-Dbus - BLE GATT API","text":"

    The implementation of the Kura BLE APIs is based on the Bluez-Dbus library that provides an easy to use Bluetooth LE API based on BlueZ over DBus. The library eases the access to GATT services and the management of BLE connections and discovery, without using any wrapper library as it is based on a newer version of dbus-java which uses jnr-unixsocket.

    "},{"location":"java-application-development/how-to-use-bt-le-apis/#apis-description","title":"APIs description","text":"

    The BLE APIs are exported in the org.eclipse.kura.bluetooth.le package. The interfaces are briefly described in the following.

    • BluetoothLeService is the entry point of the OSGI service. It allows to get all the Bluetooth interfaces installed on the gateway or a specific one using the name of the adapter.
    • BluetoothLeAdapter represents the physical Bluetooth adapter on the gateway. It allows to start/stop a discovery, search a specific BLE device based on the BD address, power up/down the adapter and get information about the adapter.
    • BluetoothLeDevice represents a Bluetooth LE device. The interface provides methods for connections and disconnections, list the GATT services or search a specific one based on the UUID and get generic information about the device.
    • BluetoothLeGattService represents a GATT service and allows listing the GATT characteristics provided by the device.
    • BluetoothLeGattCharacteristic represents a GATT characteristic. It provides methods to read from and write to the characteristic, enable or disable notifications and get the properties.
    • BluetoothLeGattDescriptor represents a GATT descriptor associated with the characteristic.

    More information about the APIs can be found in API Reference.

    "},{"location":"java-application-development/how-to-use-bt-le-apis/#how-to-use-the-kura-ble-api","title":"How to use the Kura BLE API","text":"

    This section briefly presents how to use the Kura BLE APIs, providing several code snippets to explain how to perform common bluetooth operations. For a complete example, please refer to the SensorTag application.

    An application that wants to use the Kura BLE APIs should bind the BluetoothLeService OSGI service, as shown in the following Java snippet:

    public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n

    and in the component definition:

    <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n
    "},{"location":"java-application-development/how-to-use-bt-le-apis/#get-the-bluetooth-adapter","title":"Get the Bluetooth adapter","text":"

    Once bound to the BluetoothLeService, an application can get the Bluetooth adapter and power on it, if needed:

    this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

    where adapterName is the name of the adapter, i.e. hci0.

    "},{"location":"java-application-development/how-to-use-bt-le-apis/#search-for-ble-devices","title":"Search for BLE devices","text":"

    The BluetoothLeAdapter provides several methods to search for a device, a.k.a. perform a BLE discovery:

    • Future<BluetoothLeDevice> findDeviceByAddress(long timeout, String address) search for a BLE device with the specified address. The method will perform a BLE discovery for at most timeout seconds or until the device is found. It will return a Future instance and the discovered device can be retrieved using the get() method.
    • Future<BluetoothLeDevice> findDeviceByName(long timeout, String name) search for a BLE device with the specified system name and return a Future.
    • void findDeviceByAddress(long timeout, String address, Consumer<BluetoothLeDevice> consumer) search for a BLE device with the specified address. The method will perform a BLE discovery for at most timeout seconds or until the device is found. When the device is found or the timeout is reached the consumer is used to get the device.
    • void findDeviceByAddress(long timeout, String address, Consumer<BluetoothLeDevice> consumer) search for a BLE device with the specified name and use the provided consumer to return the device.
    • Future<List<BluetoothLeDevice>> findDevices(long timeout) and void findDevices(long timeout, Consumer<List<BluetoothLeDevice>> consumer) are similar to the methods above, but they get a list of Bluetooth devices.

    The following snippet shows how to perform a discovery of 10 seconds using findDevices method:

    if (this.bluetoothLeAdapter.isDiscovering()) {\n    try {\n        this.bluetoothLeAdapter.stopDiscovery();\n    } catch (KuraException e) {\n        logger.error(\"Failed to stop discovery\", e);\n    }\n}\nFuture<List<BluetoothLeDevice>> future = this.bluetoothLeAdapter.findDevices(10);\ntry {\n    List<BluetoothLeDevice>; devices = future.get();\n} catch (InterruptedException | ExecutionException e) {\n    logger.error(\"Scan for devices failed\", e);\n}\n
    "},{"location":"java-application-development/how-to-use-bt-le-apis/#get-the-gatt-services-and-characteristics","title":"Get the GATT services and characteristics","text":"

    To get the GATT services using the BluetoothLeDevice, use the following snippet:

    try {\n    List<BluetoothLeGattService>; services = device.findServices();\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT services\", e);\n}\n

    A specific GATT service can be retrieved using its UUID:

    try {\n    BluetoothLeGattService service = device.findService(uuid);\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT service\", e);\n}\n

    Using the GATT service, it is possible to get a specific GATT characteristic (or the complete list) and the GATT descriptor from it:

    try {\n    BluetoothLeGattCharacteristic characteristic = service.findCharacteristic(characteristicUuid);\n    BluetoothLeGattDescriptor descriptor = characteristic.findDescriptor(descriptorUuid);\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT resources\", e);\n}\n
    "},{"location":"java-application-development/how-to-use-bt-le-apis/#io-operations-on-gatt-characteristics-and-descriptors","title":"IO operations on GATT characteristics and descriptors","text":"

    The Kura BLE APIs provides methods to manage the IO operations on GATT characteristics and descriptors. The following snippet provides an example on how to read and write data to a characteristic.

    try {\n    byte[] valueRead = characteristic.readValue();\n    byte[] valueWrite = { 0x01};\n    characteristic.writeValue(valueWrite);\n} catch (KuraBluetoothIOException e) {\n    logger.error(\"IO operation failed\", e);\n}\n

    In the following example, instead, a notification listener is configured to periodically receive the data from a GATT characteristic and print the first value of the given array. The period is internally set by the BLE device.

    try {\n    Consumer<byte[]>; callback = valueBytes -> System.out.println((int) valueBytes[0]);\n    characteristic.enableValueNotifications(callback);\n} catch (KuraBluetoothNotificationException e) {\n    logger.error();\n}\n
    "},{"location":"java-application-development/how-to-use-bt-le-apis/#configure-bluez-on-the-raspberry-pi","title":"Configure Bluez on the Raspberry Pi","text":"

    The minimum version of Bluez supported by Kura Bluetooth LE APIs is 5.42. The Raspbian Stretch OS comes with Bluez 5.43, but older OS couldn't have an updated Bluez version. In this case, it is possible to compile and install Bluez from sources using a Raspberry Pi. The Bluez sources can be found here Proceed as follows:

    • Install the packages needed for compile Bluez:

    sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev\n
    * Download bluez-5.43.tar.xz (or newer version) from here. * Decompress the compressed archive:

    tar -xf bluez-5.43.tar.x\n
    • Compile the sources:
    cd bluez-5.43\n./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-library -disable-systemd --enable-experimental --enable-maintainer-mod\nmake\nmake install\n
    "},{"location":"java-application-development/how-to-use-can-bus/","title":"How to Use CAN bus","text":"

    The Kura CAN bus protocol implementation is based on the SocketCAN interface, which provides a socket interface to userspace applications. The sockets are designed as can0 and can1. The SocketCAN package is an implementation of Controller Area Network (CAN) protocols. For more information, refer to the following link: https://www.kernel.org/doc/Documentation/networking/can.txt.

    "},{"location":"java-application-development/how-to-use-can-bus/#configure-the-can-bus-driver","title":"Configure the CAN bus Driver","text":"

    The CAN network must be initialized prior to communications. Verify that the CAN driver module has been enabled in the kernel by issuing the following command:

    ifconfig -a\n
    The connections \u201ccan0\u201d and \u201ccan1\u201d should be displayed.

    Next, the sockets must be enabled and configured using the following commands (the bitrate value must be set according to the bitrate of the device that will be connected):

    ip link set can0 type can bitrate 50000 triple-sampling on\nip link set can0 up\nip link set can1 type can bitrate 50000 triple-sampling on\nip link set can1 up\n

    "},{"location":"java-application-development/how-to-use-can-bus/#use-the-can-bus-driver-in-kura","title":"Use the CAN bus Driver in Kura","text":"

    To use the Can bus Driver in Kura, the bundle org.eclipse.kura.protocol.can must be installed. Refer to the section Application Management for more information.

    Once this bundle is installed and verified, the CanConnectionService provides access to basic functionalities of the CAN network, including:

    • sendCanMessage \u2013 sends an array of bytes in RAW mode.

    • receiveCanMessage \u2013 reads frames in RAW mode waiting on socket CAN.

    Refer to the following Kura javadocs for more information: http://download.eclipse.org/kura/docs/api/5.2.0/apidocs/.

    Also, for information about the wrapper that this service utilizes, refer to the following link: https://github.com/entropia/libsocket-can-java.

    "},{"location":"java-application-development/how-to-use-dummy-signature-service/","title":"How to use the Dummy Container Signature Validation Service","text":"

    The Dummy Container Signature Validation Service is an example implementation of the Container Signature Validation Service interface which mainly serves as a reference for future implementations and testing.

    The purpose of this component is to have a service whose configuration dictates the signature verification outcome. In the main text area is possible to set the container image reference that the service will report as correctly signed.

    The format accepted by the service is: <imageName>:<imageTag>@<imageDigest> where

    • <imageName>: is the container image name. The value will need to be expressed in the form registryURL/imagename in case of a custom registry. Example: nginx or nvcr.io/nvidia/deepstream.
    • <imageTag>: is the container image tag. Example: latest or 6.4-gc-triton-devel.
    • <imageDigest>: is the image digest in the OCI specification format.

    Each image should be separated by a newline. The service will report a successful signature validation for each image inside its configuration, returning the provided image digest as a result.

    "},{"location":"java-application-development/how-to-use-gpio/","title":"How to use GPIO","text":"

    GPIO resources can be accessed either using the GPIO Service provided by Kura, or directly using the OpenJDK Device I/O embedded library.

    "},{"location":"java-application-development/how-to-use-gpio/#gpio-service","title":"GPIO Service","text":"

    Access to GPIO resources is granted by the GPIOService. Once retrieved, the service can be used to acquire a GPIO Pin and use it as a digital output or a digital input.

    The GPIO Service exposes methods to retrieve a GPIO Pin via its name or index as shown below.

    KuraGpioPin thePin = gpioServiceInstance.getPinByTerminal(18);\nKuraGpioPin thePin = gpioServiceInstance.getPinByName(\"IgnitionPin\");\n

    The KuraGpioPin object is used to manipulate GPIO Pins and exposes methods to read the status of an input, or set the status of digital output as shown below.

    //sets digital output value to high\nthePin.setValue(true);\n\n//get value of a digital input pin\nboolean active = thePin.getValue();\n\n//listen for status change on a digital input pin\ntry {  \n      thePin.addPinStatusListener(new PinStatusListener() {\n          @Override    \n          public void pinStatusChange(boolean value) {      \n          // Perform tasks when pin status changes    \n          }  \n      });\n} catch (KuraClosedDeviceException e) {\n  // Here if GPIO cannot be acquired\n  } catch (IOException e) {\n    // Here on I/O error\n  }\n

    "},{"location":"java-application-development/how-to-use-gpio/#pin-configuration","title":"Pin Configuration","text":"

    Pin names, indexes, and configuration are defined in the jdk.dio.properties file.

    Although GPIO pins can be accessed with their default configuration, the settings of each pin can be changed when acquiring it with the GPIO Service as shown below.

    KuraGpioPin customInputPin = gpioServiceInstance.getPinByTerminal(\n  14, \n  KuraGPIODirection.INPUT, \n  KuraGPIOMode.INPUT_PULL_UP, \n  KuraGPIOTrigger.BOTH_LEVELS);\n

    "},{"location":"java-application-development/how-to-use-gpio/#default-configuration","title":"Default Configuration","text":"

    Default hardware configuration for the hardware platform is defined in the jdk.dio.properties file. Standard configuration for complex devices can be added on a per-device basis as shown below.

    #Default PIN configuration. To be overwritten in the following lines\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\n\n#Standard PIN configuration\n64 = deviceType: gpio.GPIOPin, pinNumber:64, name:RELAY1\n

    Starting from Kura 5.5.0, the default pin configuration can be described in the form of symbolic links. The syntax is as follows:

    /dev/digital_in1 = deviceType: gpio.GPIOPin, direction:0, mode=1, name:digital_in1\n

    where /dev/digital_in1 points to /sys/class/gpio/gpioXYZ. The pin number is set to XYZ and it can be retrieved by name.

    "},{"location":"java-application-development/how-to-use-gpio/#openjdk-device-io","title":"OpenJDK Device I/O","text":"

    Linux-level access in Kura is granted through OpenJDK Device I/O, a third-party library that leverages standard Java ME Device I/O APIs to Java SE. Kura is distributed with the relevant native libraries, together with the default hardware configuration, for each platform on which it runs.

    I2C, SPI, and GPIO resources can be directly accessed through the jdk.dio library present in the target platform.

    "},{"location":"java-application-development/how-to-use-gpio/#apis","title":"APIs","text":"

    Kura supports the full set of APIs for the listed device types. Refer to References for further API information.

    "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-with-openjdk-device-io","title":"Accessing a GPIO Pin with OpenJDK Device I/O","text":"

    A GPIO Pin can be accessed by referencing its index in the properties file, or by creating a Pin configuration object and feeding it to the DeviceManager as shown in the code examples below.

    "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-by-its-index","title":"Accessing a GPIO Pin by its Index","text":"
    #Accessing the GPIO Pin number 17. The default behaviour is defined in the\n#jdk.dio.properties file\n#\n#i.e.:\n# gpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\n# 17 = deviceType: gpio.GPIOPin, pinNumber:17, name:GPIO_USER_1\n\nGPIOPin led = (GPIOPin)DeviceManager.open(17);\n\nled.setValue(true) //Turns the LED on\nled.setValue(false) //Turns the LED off\nboolean status = led.getValue() //true if the LED is on\n
    "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-using-a-device-configuration-object","title":"Accessing a GPIO Pin Using a Device Configuration Object","text":"
    #Accessing the Pin number 17 with custom configuration\n\nGPIOPinConfig pinConfig = new GPIOPinConfig(\n    DeviceConfig.DEFAULT,                       //GPIO Controller number or name\n    17,                                                 //GPIO Pin number\n    GPIOPinConfig.DIR_INPUT_ONLY,               //Pin direction\n    GPIOPinConfig.MODE_INPUT_PULL_DOWN,     //Pin resistor\n    GPIOPinConfig.TRIGGER_BOTH_EDGES,       //Triggers\n    false                                           //initial value (for outputs)\n);\n\nGPIOPin button = (GPIOPin) DeviceManager.open(GPIOPin.class, pinConfig);\n\nbutton.setInputListener(new PinListener(){\n        @Override\n        public void valueChanged(PinEvent event) {\n            System.out.println(\"PIN Status Changed!\");\n            System.out.println(event.getLastTimeStamp() + \" - \" + event.getValue());\n        }\n});\n
    "},{"location":"java-application-development/how-to-use-modbus/","title":"How to Use Modbus","text":"

    ModbusProtocolDevice is a service that provides a connection to a device or a network of devices over Serial Line (RS-232/RS-485) or Ethernet using the Modbus protocol. This service implements a subset of the Modbus Application Protocol as defined by Modbus Organization (for more information, refer to http://www.modbus.org/specs.php).

    The ModbusProtocolDevice service needs to receive a valid Modbus configuration including the following parameters:

    • Modbus protocol mode - defines the protocol mode as RTU or ASCII (only RTU mode for Ethernet connections).

    • Timeout - sets the timeout in order to detect a disconnected device.

    The ModbusProtocolDevice service also requires a valid Serial Line or Ethernet connection configuration including the following parameters:

    • Serial Line
    • port name
    • baudrate
    • bits
    • stops
    • parity

    • Ethernet

    • ip address
    • port number

    When a valid configuration is received, the ModbusProtocolDevice service tries to open the communication port. Serial Line communication uses the CommConnection class; Ethernet communication is based on java.net.Socket. When the communication is established, the client makes direct calls to the Modbus functions. The first parameter of each method is the Modbus address of the queried unit. This address must be in the range of 1 - 247.

    "},{"location":"java-application-development/how-to-use-modbus/#function-codes","title":"Function Codes","text":"

    The following function codes are implemented within the ModbusProtocolDevice service:

    • 01 (0x01) readCoils(int unitAddr,\u00a0 int dataAddress, int count) - read 1 to 2000 maximum contiguous status of coils from the attached field device with address \"unitAddr\". An array of booleans representing the requested data points is returned.

    • 02 (0x02) readDiscreteInputs(int unitAddr,\u00a0 int dataAddress, int count) - read 1 to 2000 maximum contiguous status of discrete inputs from the attached field device with address \"unitAddr\". An array of booleans representing the requested data points is returned.

    • 03 (0x03) readHoldingRegisters(int unitAddr,\u00a0 int dataAddress, int count) - read contents of 1 to 125 maximum contiguous block of holding registers from the attached field device with address \"unitAddr\". An array of int representing the requested data points (data registers on 2 bytes) is returned.

    • 04 (0x04) readInputRegisters(int unitAddr,\u00a0 int dataAddress, int count) - read contents of 1 to 125 maximum contiguous block of input registers from the attached field device with address \"unitAddr\". An array of int representing the requested data points (data registers on 2 bytes) is returned.

    • 05 (0x05) writeSingleCoil(int unitAddr,\u00a0 int dataAddress, boolean data) - write a single output to either ON or OFF in the attached field device with address \"unitAddr\".

    • 06 (0x06) writeSingleRegister(int unitAddr,\u00a0 int dataAddress, int data) - write a single holding register in the attached field device with address \"unitAddr\".

    • 15 (0x0F) writeMultipleCoils(int unitAddr,\u00a0 int dataAddress, boolean[ ] data) - write multiple coils in a sequence of coils to either ON or OFF in the attached field device with address \"unitAddr\".

    • 16 (0x10) writeMultipleRegister(int unitAddr,\u00a0 int dataAddress, int[ ] data) - write a block of contiguous registers (1 to 123) in the attached field device with address \"unitAddr\".

    All functions throw a ModbusProtocolException. Valid exceptions include:

    • INVALID_CONFIGURATION

    • NOT_AVAILABLE

    • NOT_CONNECTED

    • TRANSACTION_FAILURE

    "},{"location":"java-application-development/how-to-use-modbus/#code-examples","title":"Code Examples","text":"

    The ModbusProtocolDeviceService is an OSGi declarative service referenced in the client XML definition file:

    <reference bind=\"setModbusProtocolDeviceService\"\n       cardinality=\"1..1\"\n       interface=\"org.eclipse.kura.protocol.modbus.ModbusProtocolDeviceService\"\n       name=\"ModbusProtocolDeviceService\"\n       policy=\"static\"\n       unbind=\"unsetModbusProtocolDeviceService\"/>\n
    public void setModbusProtocolDeviceService(ModbusProtocolDeviceService modbusService) {\n  this.m_protocolDevice = modbusService;\n}\n\npublic void unsetModbusProtocolDeviceService(ModbusProtocolDeviceService modbusService) {\n  this.m_protocolDevice = null;\n}\n
    if(m_protocolDevice!=null){\n  m_protocolDevice.disconnect();\n  m_protocolDevice.configureConnection(modbusSerialProperties);\n}\n
    If no exception occurs, the ModbusProtocolDevice can then be used to exchange data:
    boolean[] digitalInputs = m_protocolDevice.readDiscreteInputs(1, 2048, 8);\nint[] analogInputs = m_protocolDevice.readInputRegisters(1, 512, 8);\nboolean[] digitalOutputs = m_protocolDevice.readCoils(1, 2048, 6); // LEDS\n\n// to set LEDS\nm_protocolDevice.writeSingleCoil(1, 2047 + LED, On?TurnON:TurnOFF);\n

    "},{"location":"java-application-development/how-to-use-watchdog/","title":"How to use watchdog","text":""},{"location":"java-application-development/how-to-use-watchdog/#overview","title":"Overview","text":"

    When enabled, the watchdog is a peripheral monitor that will reboot the system if it is not refreshed during a certain time interval. In Kura, the WatchdogService can be used by critical applications. If the specified application is alive, the service notifies the watchdog; if the application is down, the service stops notifying the watchdog and a hardware reset occurs.

    The WatchdogService notifies the kernel watchdog driver using the /dev/watchdog device file. You can verify that the watchdog driver is installed using the following command:

    ls \u2013l /dev/watchdog\n
    "},{"location":"java-application-development/how-to-use-watchdog/#configuration","title":"Configuration","text":"

    To configure the WatchdogService, select the WatchdogService option located in the Services area as shown in the screen capture below.

    The WatchdogService provides the following configuration parameters:

    • enabled - sets whether or not this service is enabled or disabled. If enabled, you must set a pingInterval periodicity compatible with the watchdog driver.

    • pingInterval - specifies the time between two watchdog notifications. This time is hardware dependent. Generally, the maximum time between two notifications should be between 30 seconds and 1 minute. 10000 milliseconds for the pingInterval is typically a good choice.

    "},{"location":"java-application-development/how-to-use-watchdog/#code-example","title":"Code Example","text":"

    The WatchdogService references a list of Critical Components that correspond to the applications implementing the CriticalComponent interface.

    CriticalComponent is an interface that can be used to denote a component that is crucial to system operations. If a component implements CriticalComponent, then it must state its name as well as its criticalComponentTimeout. The name is a unique identifier in the system. The timeout is the length of time in milliseconds that the CriticalComponent must \"check in\" with the WatchdogService. If the CriticalComponent extends beyond the period of time specified in this timeout, a system reboot will be performed based on the WatchdogService configuration.

    If at least one of the registered CriticalComponents has not \"checked in\" during the pingInterval time, the WatchdogService stops notifying the watchdog driver. The system reboots when the time interval reaches the hardware time that is programmed for the watchdog. When the WatchdogService is enabled and no application is using it, the service runs silently in the background.

    An example of the WatchdogService can be found here.

    The following code snippets demonstrate how to implement the CriticalComponent interface:

    public class ModbusManager implements ConfigurableComponent, CriticalComponent, CloudClientListener\n

    Registration of the class in WatchdogService::

    if(m_watchdogService!=null){\n  m_watchdogService.registerCriticalComponent(this);\n}\n

    Periodic call to checkin method of WatchdogService in the main loop (keeps watchdog notification alive):

    if(m_watchdogService!=null){\n  m_watchdogService.checkin(this);\n}\n
    "},{"location":"java-application-development/kura-workspace-setup/","title":"Kura Workspace Setup","text":"

    This document describes how to set up the Eclipse Kura workspace development environment, which consists of the following components:

    • JVM (Java JDK SE 8)
    • Eclipse IDE
    • Eclipse Kura Workspace

    This setup will allow you to develop applications or bundles running on Eclipse Kura. It will install only the APIs and the examples. If you want to contribute to the Eclipse Kura project follow this guide instead.

    The Eclipse Kura development environment may be installed on Windows, Linux, or Mac OS. The setup instructions will be the same across each OS though each system may have unique characteristics.

    Info

    The local emulation of Eclipse Kura code is only supported in Linux and Mac, not in Windows.

    "},{"location":"java-application-development/kura-workspace-setup/#jvm-installation","title":"JVM Installation","text":"

    Download and install JDK SE 8 from the following links as appropriate for your OS.

    For Windows and Linux users, the JDK can be downloaded from the following link: Java SE 8 Downloads. Use the latest version of the Java SE Development Kit and download the version appropriate for your system.

    For additional information regarding the installation of Java 8 on all supported operating systems, see JDK 8 and JRE 8 Installation Guide.

    "},{"location":"java-application-development/kura-workspace-setup/#installing-eclipse-ide","title":"Installing Eclipse IDE","text":"

    Before installing Eclipse, you should choose directory locations for the Eclipse install and its workspaces.

    Info

    The following points should be kept in mind regarding Eclipse installs and workspaces:

    • The directory location of the Eclipse workspaces should be chosen carefully. Once Eclipse is installed and workspaces are created, they should never be moved to another location in the file system.
    • There may be multiple installs of Eclipse (of different or similar versions), and single instances of each install can be run simultaneously; but there should never be more that one instance of a specific install running at the same time (to avoid corruption to the Eclipse environment).
    • Each workspace should be used with only one Eclipse install. You should avoid opening the workspace from more than one installation of Eclipse.

    Download the current distribution of Eclipse for your OS from Eclipse official website. Choose the Eclipse IDE for Eclipse Committers.

    The zipped Eclipse file will be downloaded to the local file system and can be saved to a temporary location that can be deleted after Eclipse has been installed. After the file has been downloaded, it should be extracted to the Eclipse installs directory. The following screen capture shows the installation in Linux using an eclipse\\installs directory. The Eclipse executable will then be found in the eclipse\\installs\\eclipse directory. This installation will be different depending on the operating system.

    Because there may potentially be future Eclipse installs extracted into this location, before doing anything else, rename the directory, such as eclipse\\installs\\juno1\\.

    Warning

    Once you begin using this Eclipse install, it should NOT be moved or renamed.

    "},{"location":"java-application-development/kura-workspace-setup/#workspaces","title":"Workspaces","text":""},{"location":"java-application-development/kura-workspace-setup/#creating-an-eclipse-workspace","title":"Creating an Eclipse Workspace","text":"

    Run Eclipse by clicking its executable in the install directory.

    When Eclipse is run for the first time, a workspace needs to be created. A single workspace will contain all the Java code/projects/bundles, Eclipse configuration parameters, and other relevant files for a specific business-level product. If the Use this as the default option is selected, the designated workspace becomes the default each time you run Eclipse.

    If a workspace has not already been defined, or if you are creating a different workspace for another development project, enter a new workspace name. The workspace should be named appropriate to the project/product being developed.

    Warning

    Once you begin using a particular workspace, it should NOT be moved or renamed at any time.

    Otherwise, select an existing workspace and click OK. After Eclipse is running, you can select the Eclipse menu File | Switch Workspace | Other to create or open a different workspace.

    After the new workspace opens, click the Workbench icon to display the development environment.

    Info

    Additional workspace configuration:

    • In the Eclipse workspace modify the lifecycle mapping by adding these XML lines to the lifecycle-mapping-metadata.xml in Eclipse Kura workspace. You can find the file in the Windows -> Preferences -> Maven -> Lifecycle Mappings -> Open workspace lifecycle mappings metadata. After editing the file, reload it by pressing the \"Reload workspace lifecycle mappings metadata\" button.
      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lifecycleMappingMetadata>\n    <lifecycleMappingFilters>\n        <lifecycleMappingFilter>\n            <symbolicName>org.eclipse.m2e.pde.connector</symbolicName>\n            <versionRange>[2.1.2,)</versionRange>\n            <packagingTypes>\n                <packagingType>eclipse-test-plugin</packagingType>\n                <packagingType>eclipse-plugin</packagingType>\n                <packagingType>eclipse-feature</packagingType>\n            </packagingTypes>\n        </lifecycleMappingFilter>\n    </lifecycleMappingFilters>\n</lifecycleMappingMetadata>\n
    • Install the eclipse-tycho plugin following this steps:
      1. Menu Help -> Install new software... -> Paste the m2eclipse-tycho repository URL in the Work with: text field -> expand the category and select the Tycho Project Configurators Feature and proceed with the installation.
      2. Then restart Eclipse.
    "},{"location":"java-application-development/kura-workspace-setup/#importing-the-eclipse-kura-user-workspace","title":"Importing the Eclipse Kura User Workspace","text":"

    To set up your Eclipse Kura project workspace, you will need to download the Eclipse Kura User Workspace archive from Eclipse Kura Download Page.

    From the Eclipse File menu, select the Import option. In the Import dialog box, expand the General heading, select Existing Projects into Workspace, and then click Next.

    Now click the Select archive file option button and browse to the archive file, such as user_workspace_archive_.zip.

    Finally, click Finish to import the projects. At this point, you should have four projects in your workspace. The four projects are as follows:

    • org.eclipse.kura.api \u2013 the core Eclipse Kura API.

    • org.eclipse.kura.demo.heater \u2013 an example project that you can use as a starting point for creating your own bundle.

    • org.eclipse.kura.emulator \u2013 the emulator project for running Eclipse Kura within Eclipse (Linux/Mac only).

    • target-definition \u2013 a set of required bundles that are dependencies of the APIs and Eclipse Kura.

    Eclipse will also report some errors at this point. See the next section to resolve those errors.

    "},{"location":"java-application-development/kura-workspace-setup/#workspace-setup","title":"Workspace Setup","text":"

    This section will guide the users to configure the development workspace environment.

    "},{"location":"java-application-development/kura-workspace-setup/#jre-configuration","title":"JRE Configuration","text":"

    The latest Eclipse IDEs require and configure, by default, a Java 11 environment. In order to be able to leverage and develop using the new workspace for Eclipse Kura, the user will be required to perform a one-time operation to specify to the IDE a Java 8 JDK. Opening the Eclipse preferences and selecting the Installed JREs in the Java section, the user has to select an installed Java 8 instance.

    After applying the configuration change, the user will be prompted to align also the compiler options. To do so, selecting the Compiler entry in the Java section, the user has to select 1.8 from the list of available Java versions.

    After applying the changes, the user will be prompted to recompile the environment.

    "},{"location":"java-application-development/kura-workspace-setup/#target-definition-setup","title":"Target Definition Setup","text":"

    Click the arrow next to the target-definition project in the workspace and double-click kura-equinox_.target to open it.

    In the Target Definition window, click the link Set as Target Platform. Doing so will reset the target platform, rebuild the Eclipse Kura projects, and clear the errors that were reported. At this point, you are ready to begin developing Eclipse Kura-based applications for your target platform.

    "},{"location":"java-application-development/kura-workspace-setup/#run-the-eclipse-kura-emulator","title":"Run the Eclipse Kura Emulator","text":"

    To start the Eclipse Kura emulator, select the \"Eclipse Kura Emulator.launch\" profile from \"Other Projects\" -> \"setups\" -> \"launchers\" and open it with \"Run as\" -> \"Run Configurations...\". Then click on the \"Arguments\" tab and update the \"VM arguments\" as follows to adapt the paths to the folder structure created by the Oomph installer:

    -Dkura.have.net.admin=false -Dorg.osgi.framework.storage=/tmp/osgi/framework_storage -Dosgi.clean=true -Dosgi.noShutdown=true -Declipse.ignoreApp=true -Dorg.eclipse.kura.mode=emulator -Dkura.configuration=file:${workspace_loc}/../git/kura/kura/emulator/org.eclipse.kura.emulator/src/main/resources/kura.properties -Ddpa.configuration=/tmp/kura/dpa.properties -Dlog4j.configurationFile=file:${workspace_loc}/../git/kura/kura/emulator/org.eclipse.kura.emulator/src/main/resources/log4j.xml -Dkura.data=${workspace_loc}/kura/data -Dkura.snapshots=${workspace_loc}/kura/user/snapshots -Dorg.eclipse.equinox.http.jetty.customizer.class=org.eclipse.kura.jetty.customizer.KuraJettyCustomizer\n

    The Eclipse Kura Web UI will be available at the following URL: http://127.0.0.1:8080 with username and password admin.

    "},{"location":"java-application-development/ram-usage-considerations/","title":"RAM Usage Considerations","text":"

    During application development and before moving to production, it is advisable to understand if the amount of free RAM available on the device is enough for correct device operation.

    Since RAM usage is application dependent, it is important to perform some stress tests to bring the device in the worst case conditions and verify system behavior.

    Some of the aspects that should be taken into account are the following:

    "},{"location":"java-application-development/ram-usage-considerations/#java-heap-memory-usage","title":"Java Heap memory usage","text":"

    Java heap is used to store the Java objects and classes at runtime.

    The heap should be:

    1. Large enough to satisfy the requirements of applications running inside Kura.
    2. Small enough so that the requirements of the system and applications running outside Kura are satisfied.

    The size of the heap is controlled by the -Xms and -Xmx Java command line arguments. These parameters are defined in the /opt/eclipse/kura/bin/start_kura_debug.sh (for development mode) and /opt/eclipse/kura/bin/start_kura_background.sh (for production mode).

    The -Xms parameter defines the initial size of Java heap and -Xmx defines the maximum size. The JVM will start using Xms as the size of the heap, and then it will grow the heap at runtime up to Xmx if needed, depending on application memory demand.

    Resizing the heap has a cost in terms of performance, for this reason Xms and Xmx are set to the same size by default on most platforms.

    In order to understand if the heap is large enough, it is advisable to perform a stress test simulating the conditions of maximum memory demand by the applications running inside Kura. For example, if a in-memory database instance is used by a DataService instance, during the test the database can be filled up to the maximum capacity to verify if this causes any issue.

    Regarding point 2., it should be noted that heap memory is not necessarily backed by physical memory immediately after JVM startup. Even if the JVM performs an allocation of size Xmx immediately, physical memory will be assigned to the Java process by the kernel only when the memory pages are actually accessed by the JVM.

    For this reason the amount of physical memory used by the JVM might appear small right after system boot and grow with time, up to the maximum size. This can happen even if the applications running inside Kura do not have high memory requirements, and can lead to potential issues that show up only after some time.

    In order to recreate such issues, the -XX:+AlwaysPreTouch JVM command line option can be used during development to force the JVM to access all heap memory after start, causing the JVM process to use the maximum amount of physical memory immediately.

    "},{"location":"java-application-development/ram-usage-considerations/#logging","title":"Logging","text":"

    Another aspect that can lead to RAM related issues is logging. As a general rule, it is recommended to reduce the amount of log messages produced by Kura during normal operation.

    Kura default logging configuration (/opt/eclipse/kura/log4j/log4j.xml) depends on the platform.

    The size of the files in the /var/log directory will be checked periodically and the files will be rotated to the persisted /var/old_logs directory if needed.

    "},{"location":"java-application-development/ram-usage-considerations/#external-application-ram-usage","title":"External application RAM usage","text":"

    If external applications are installed on the system (e.g. Docker containers), their RAM usage should be analyzed as well.

    Stress tests related to Java heap size, log size and external applications can be run simultaneously to simulate a worst case scenario.

    "},{"location":"java-application-development/remote-debugging-on-target-platform/","title":"Remote debugging on target platform","text":"

    Eclipse Kura can be started with Java Debug Wire Protocol (JDWP) support, allowing the remote debugging of the developed application using Eclipse IDE. The procedure for remote debugging is presented in the following.

    • Connect to the target platform (i.e. RaspberryPi) and stop the Kura application typing sudo systemctl stop kura or sudo /etc/init.d/kura stop.

    • Start Kura with Java Debug Wire Protocol (JDWP) typing sudo /opt/eclipse/kura/bin/start_kura_debug.sh. This will start Kura and open an OSGi console. It will also start listening for socket connections on port 8000.

    Warning

    Starting from Java 9, the JDWP socket connector accepts only local connections by default (see here for further details). To enable remote debugging on Java 9, the following line in /opt/eclipse/kura/bin/start_kura_debug.sh: -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n \\has to be replaced with the following one: -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=*:8000,suspend=n \\

    • Open the tcp port 8000 in the firewall. This can be done through the firewall tab in Kura web interface or using iptables.

    • Install your application bundle on the target platform.

    • From Eclipse IDE, set a breakpoint in the application code at a point that will be reached (i.e. activation method, common logging statement, etc.). Then:

    • Go to \"Run -> Debug Configurations\u2026\"
    • Select \u201cRemote Java Application\u201d and click the \u201cNew launch configuration\u201d button
    • For \u201cProject:\u201d, select the bundle project to be debugged
    • For \u201cConnection Type:\u201d, select the default \u201cStandard (Socket Attach)\u201d
    • For \u201cConnection Properties:\u201d, enter the IP address of the target platform and the tcp port 8000
    • Click Debug

    • Eclipse will connect to the target platform VM and switch to the Debug Perspective when the breakpoint will have been hit.

    • To stop the remote debugging, select the \u201cDisconnect\u201d button from the Debug Perspective.

    "},{"location":"kura-wires/assets-as-wire-components/","title":"Assets as Wire Components","text":"

    An Asset can be used inside a Wire Graph, in this case it is represented as node with two ports an input port and an output port. An Asset used in this way is called WireAsset.

    "},{"location":"kura-wires/assets-as-wire-components/#read-mode","title":"Read mode","text":"

    Every time a WireAsset receives an envelope on its input port, it will read the values of all of its channels with READ or READ_WRITE type. The result is emitted as a WireEnvelope with a single WireRecord. The WireRecord contains the following properties:

    • a property with key assetName, value type STRING and the emitting asset asset name as value
    • a property with key assetError, value type STRING if emit.errors and emit.connection.errors is enabled and a general connection failure is returned by the Driver. See the description of the emit.connection.errors parameter for more details.
    • a property with key assetTimestamp, value type LONG, reporting a timestamp in milliseconds since Unix epoch (midnight, January 1, 1970 UTC). This property may be present or not depending on the value of the timestamp.mode configuration parameter or in case of general connection exception reported by the Driver, see the configuration parameter description for more details.

    For each channel in asset configuration with READ or READ_WRITE type named name:

    • a property with key = name, value type = value.type in channel configuration and value = value obtained from read operation. This property will be present only if the read operation is successful.
    • a property with key = <name>_timestamp value type = LONG reporting a timestamp in milliseconds since UNIX epoch. This property will be present only if the timestamp.mode Asset configuration property is set to PER_CHANNEL
    • a property with key = <name>_error, value type = STRING reporting an error message. This property will be present only if read operation fails and the emit.errors Asset configuration property is set to true.

    For example, if an Asset that has the channel configuration shown in the picture below receives a WireEnvelope on its input port, it can emit an envelope with the following content, assuming that the read operation for each channel succeed:

    • WireEnvelope
      • WireRecord[0]
        • assetName: modbusAsset (type = STRING)
        • LED1: true (type = BOOLEAN)
        • LED1_timestamp: 1597925188 (type = LONG)
        • LED2: false (type = BOOLEAN)
        • LED2_timestamp: 1597925188 (type = LONG)
        • LED3: true (type = BOOLEAN)
        • LED3_timestamp: 1597925188 (type = LONG)
        • LED4-RED: false (type = BOOLEAN)
        • LED4-RED_timestamp: 1597925188 (type = LONG)
        • LED4-GREEN: false (type = BOOLEAN)
        • LED4-GREEN_timestamp: 1597925188 (type = LONG)
        • LED4-BLUE: true (type = BOOLEAN)
        • LED4-BLUE_timestamp: 1597925188 (type = LONG)
        • Toggle-4: true (type = BOOLEAN)
        • Toggle-4_timestamp: 1597925188 (type = LONG)
        • Toggle-5: false (type = BOOLEAN)
        • Toggle-5_timestamp: 1597925188 (type = LONG)
        • Toggle-6: true (type = BOOLEAN)
        • Toggle-6_timestamp: 1597925188 (type = LONG)
        • Counter-3: 123 (type = INTEGER)
        • Counter-3_timestamp: 1597925188 (type = LONG)
        • Quad-Counter: 11 (type = INTEGER)
        • Quad-Counter_timestamp: 1597925188 (type = LONG)
        • Reset-Counter3: false (type = BOOLEAN)
        • Reset-Counter3_timestamp: 1597925188 (type = LONG)
        • Reset-Quad-Counter: false (type = BOOLEAN)
        • Reset-Quad-Counter_timestamp: 1597925188 (type = LONG)

    The emitted WireEnvelope contains a single record containing the properties described above.

    The Logger WireComponent can be used to inspect the messages emitted on a specific output port of a WireComponent by creating a connection between the output port of the component to the input port of the Logger. In this case the content of the received envelopes will be printed on device log (/var/log/kura.log).

    As mentioned above, the read operation is performed only if an envelope is received on the input port of the WireAsset. In order to achieve this, another component must be connected to the input port of the WireAsset.

    An example of such component can be the Timer, this component can be configured to periodically emit an envelope containing a single wire record with a single property named TIMER reporting the current UNIX timestamp. Connecting this component to a WireAsset allows to implement a simple read polling cycle. The configuration of the timer defines the polling interval.

    "},{"location":"kura-wires/assets-as-wire-components/#listen-mode","title":"Listen mode","text":"

    Enabling the listen flag allows to enable unsolicited notifications from the driver.

    When this happens, the Asset will emit an WireEnvelope containing the updated value for the channel involved in the event. The content of this envelope is the same as the one generated in case of a read operation on the channel.

    For example if listen is ticked for LED1 and the driver above detects a value change in that channel, the Asset will emit the following envelope:

    • WireEnvelope
      • WireRecord[0]
        • assetName: modbusAsset (type = STRING)
        • LED1: false (type = BOOLEAN)
        • LED1_timestamp: 1597925200 (type = LONG)

    This mode does not require to connect any component to the input port of the driver. The conditions that trigger the events for the channels and their meaning is reported in the Driver specific documentation.

    Note: The example above is not completely realistic since the Modbus driver does not support listen mode. In this case ticking the listen flag will have no effect. The support for listen mode is mentioned in driver documentation.

    Listen mode and Read mode are not mutually exclusive. If a channel is defined as READ or READ_WRITE and the listen flag is ticked, the driver will emit the channel value when a WireEnvelope is received on its input port or when a driver event is generated.

    "},{"location":"kura-wires/assets-as-wire-components/#write-mode","title":"Write mode","text":"

    Additionally, the Wire Graph can also be used to update asset values through write operations, according to the following rule.

    Every time a WireAsset receives an envelope on its input port, for each property contained in the received WireRecords with key <key>, value type <value type> and value <value>, the driver will perform this operation: If a channel with name <key> is defined in asset configuration whose value.type is equal to <value type> and type is WRITE or READ_WRITE, then the Asset will write <value> to the channel.

    For example if the Asset above receives the following envelope:

    • WireEnvelope
      • WireRecord[0]
        • LED1: false (type = BOOLEAN)
        • LED2: 78 (type = LONG)
        • Toggle-4: true (type = BOOLEAN)
        • foo: bar (type = STRING)

    The following operations will happen:

    • Since the Asset configuration contains a channel named LED1, with value.type = BOOLEAN, and type = READ_WRITE, the driver will write false to that channel for the rule mentioned above.
    • The LED2: 78 property will have no effect, since the Asset configuration contains a channel named LED2 with type = READ_WRITE, but value.type = BOOLEAN != LONG.
    • The Toggle-4: true property will have no effect, since the asset contains a channel named Toggle-4, with value.type = BOOLEAN but type = READ != WRITE | READ_WRITE
    • The foo: bar property will have no effect, since none of the defined channels has foo as name.
    • The Asset will read and emit all of the channel values with type = READ or READ_WRITE, since a WireEnvelope has been received.
    "},{"location":"kura-wires/assets-as-wire-components/#wire-asset-global-configuration-parameters","title":"Wire Asset global configuration parameters","text":"

    The WireAsset component provides the following global (non per-channel) configuration parameters that can be used to customize component behavior:

    • emit.all.channels: Specifies whether the values of all READ or READ_WRITE channels should be emitted when a channel event is received in listen mode. If set to true, the values for all channels will be read and emitted, if set to false, only the value for the channel related to the event will be emitted.

    • timestamp.mode: Allows to configure how timestamps are emitted, the following modes are supported:

      • NO_TIMESTAMPS: no timestamp-related properties will be emitted.

      • PER_CHANNEL: the component will emit a driver-generated timestamp property per channel, as the value of the <channel name>_timestamp property.

      • SINGLE_ASSET_GENERATED: the component will emit a single timestamp per request, generated by the Asset itself before emitting the envelope as the value of the assetTimestamp property.

      • SINGLE_DRIVER_GENERATED_MAX and SINGLE_DRIVER_GENERATED_MIN: the component will emit a single driver generated timestamp being respectively the max (most recent) or min (oldest) among the timestamps of the channels as the value of the assetTimestamp property.

    • emit.errors: Specifies whether channel specific errors should be included or not in emitted envelopes. If enabled, the component will add an additional property per channel, named <channel_name>_error. If the channel operation fails, the property value will be an error message reported by the Driver, if the operation succeeds the property value will be the empty string.

    • emit.connection.errors: Specifies whether the component should emit an envelope in case of a general connection exception reported by the Driver (for example due to the fact that the connection with a remote device cannot be established). The error message associated with the exception will be emitted in a property named assetError. In case of connection exception, channel values are not available and no channel related properties will be emitted. If the timestamp.mode property is set to a value other than NO_TIMESTAMPS, the component will also emit a assetTimestamp property reporting current system time. This property will be ignored if emit.errors is disabled. Example of emitted envelope contents:

    • WireEnvelope

      • WireRecord[0]
        • assetName: myAsset (type = STRING)
        • assetError: Connection refused (type = STRING)
        • assetTimestamp: 1597925200 (type = LONG)
    • emit.on.change: If set to true, this component will include a channel value in the output emitted in Kura Wires only if it differs from the last emitted value for that channel. Channel errors will always be emitted if emit.errors is set to true. If as a result of a read operation no changes are detected in any referenced channel, the Asset will emit an envelope containing no channel values, unless emit.empty.envelopes is set to false.

    • emit.empty.envelopes: If set to false, this component will not emit empty envelopes.

    "},{"location":"kura-wires/introduction/","title":"Introduction","text":"

    The Wires feature aims to simplify the development of IoT Edge Computing Applications leveraging reusable configurable components that can be wired together and which, eventually, allows configurable cooperation between these components.

    In the dataflow programming model, the application logic is expressed as a directed graph (flow) where each node can have inputs, outputs, and independent processing units. There are nodes that only produce outputs and ones that only consume inputs, which usually represent the start and the end of the flow. The inner-graph nodes process the inputs and produce outputs for downstream nodes. The processing unit of a node executes independently and does not affect the execution of other nodes. Thus, the nodes are highly reusable and portable.

    In this way, the developer can easily prototype its solution without sacrificing flexibility and working at a high level of abstraction: the graph can be extended adding new nodes or drawing new connections. Furthermore, the developer can take advantage of the Eclipse Marketplace integration, being able to use open source or commercial building blocks into the final solution, by simply dragging and dropping a link to the Eclipse Marketplace in the Administrative Web UI.

    "},{"location":"kura-wires/introduction/#data-model","title":"Data Model","text":"

    The communication over a single graph edge (Wire) is message oriented, messages are called WireEnvelopes. Each WireEnvelope contains a list of WireRecords. Each WireRecord contains a set of key-value pairs, called properties, the property key is always a string and it is unique within the same record, the property value can have one of the following types:

    • BOOLEAN
    • BYTE_ARRAY
    • DOUBLE
    • INTEGER
    • LONG
    • FLOAT
    • STRING
    "},{"location":"kura-wires/introduction/#wire-composer","title":"Wire Composer","text":"

    The Wire Composer is the main source of interaction with the Wires framework. It is accessible by clicking on the Wires button under System.

    The Wires page is composed by a central composer, where the graph can be actually designed, a lower part that is populated when a wire component is clicked and that allows to update the component configuration and a section in the right with the available Wire Components.

    "},{"location":"kura-wires/introduction/#wire-components","title":"Wire Components","text":"

    The following components are distributed with Kura:

    • Timer ticks every x seconds and starts the graph;
    • Publisher publishes every message received from a Wire (Wire Message). It is configurable in order to use a specific Cloud Service;
    • Subscriber subscribes to a configurable topic via a specific Cloud Service. It receives a message from a Cloud Platform, wraps it as a Wire Message and sends it through the connected wires to the other components that are part of the Wire Graph;
    • Wire Record Store allows the storage of Wire Messages into a specific stored collection. It has rules for message cleanup and retention;
    • Wire Record Query, allows the filtering of messages residing in a store via a specific query. The corresponding messages are sent as Wire Messages to the connected Wire Components;
    • Logger logs the received messages;
    • Asset \u200ballows the definition of Wire Channels that will be used to communicate with a field device through the associated Driver instance.
    "},{"location":"kura-wires/introduction/#graph-download","title":"Graph Download","text":"

    In the top left part of the Wires page the Download button allows to download the configuration of the graph and of all the components that are part of the graph.

    This snapshot can be used to replicate the same configuration across all the fleet of devices.

    To upload the stored graph, the user has to access the Settings page and in the Snapshots section click the Upload and Apply button.

    Warning

    The graph configuration will be actually merged with the one existing. Be careful to Delete the existing graph and apply, if you don't want to merge with the existing Wires configuration.

    "},{"location":"kura-wires/wire-graph-service-configuration-format/","title":"WireGraphService Configuration Format","text":"

    This document describes the configuration format for the WireGraphService component.

    The WireGraphService configuration contains all the information related to the Wire Graph topology and rendering properties. The pid of the WireGraphService configuration is org.eclipse.kura.wire.graph.WireGraphService.

    The WireGraphService configuration represents the current graph layout as a single string typed property named WireGraph that represents a serialized JSON representation of a WireGraph object.

    "},{"location":"kura-wires/wire-graph-service-configuration-format/#json-definitions","title":"JSON definitions","text":""},{"location":"kura-wires/wire-graph-service-configuration-format/#position","title":"Position","text":"

    An object representing a Wire Component position.

    Properties:

    • x: number
    • optional If not specified, 0.0 will be used as default value The x coordinate of the Wire Component inside the graph canvas
    • y: number
    • optional If not specified, 0.0 will be used as default value The y coordinate of the Wire Component inside the graph canvas

    {\n  \"x\": 40,\n  \"y\": 0\n}\n
    {\n  \"x\": 1.5\n}\n
    {}\n

    "},{"location":"kura-wires/wire-graph-service-configuration-format/#portnamelist","title":"PortNameList","text":"

    An object that specifies custom names for Wire Component input and output ports. The properties name for this object must be represented as an integer starting from 0, matching the index of the port whose name needs to be assigned. If the property name is not specified, the default port name will be used.

    Properties:

    • _portIndex: string The name for the port of index _portIndex

    {\n  \"0\": \"foo\",\n  \"1\": \"bar\"\n}\n
    {}\n

    "},{"location":"kura-wires/wire-graph-service-configuration-format/#renderingproperties","title":"RenderingProperties","text":"

    An object describing some Wire Component rendering parameters like position and custom port names.

    Properties:

    • position: object
    • optional If not specified the component coordinates will be set to 0.0.
      • Position
    • inputPortNames: object
    • optional If not specified, the default input port names will be used.
      • PortNameList
    • outputPortNames: object
    • optional If not specified, the default output port names will be used.
      • PortNameList

    {\n  \"inputPortNames\": {},\n  \"outputPortNames\": {\n    \"0\": \"foo\",\n    \"1\": \"bar\"\n  },\n  \"position\": {\n    \"x\": 40,\n    \"y\": 0\n  }\n}\n
    {\n  \"inputPortNames\": {},\n  \"outputPortNames\": {\n    \"0\": \"foo\",\n    \"1\": \"bar\"\n  }\n}\n
    {\n  \"position\": {\n    \"x\": 40,\n    \"y\": 0\n  }\n}\n
    {}\n

    "},{"location":"kura-wires/wire-graph-service-configuration-format/#wirecomponent","title":"WireComponent","text":"

    An object that describes a Wire Component that is part of a Wire Graph

    Properties:

    • pid: string The Wire Component pid
    • inputPortCount: number An integer reporting the number of input ports of the Wire Component.
    • outputPortCount: number An integer reporting the number of output ports of the Wire Component.
    • renderingProperties: object
    • optional If not specified, the default rendering properties will be used
      • RenderingProperties

    {\n  \"inputPortCount\": 1,\n  \"outputPortCount\": 2,\n  \"pid\": \"cond\",\n  \"renderingProperties\": {\n    \"inputPortNames\": {},\n    \"outputPortNames\": {\n      \"0\": \"foo\",\n      \"1\": \"bar\"\n    },\n    \"position\": {\n      \"x\": 40,\n      \"y\": 0\n    }\n  }\n}\n
    {\n  \"inputPortCount\": 0,\n  \"outputPortCount\": 1,\n  \"pid\": \"timer\",\n  \"renderingProperties\": {\n    \"inputPortNames\": {},\n    \"outputPortNames\": {},\n    \"position\": {\n      \"x\": -220,\n      \"y\": -20\n    }\n  }\n}\n

    "},{"location":"kura-wires/wire-graph-service-configuration-format/#wire","title":"Wire","text":"

    An object that describes a Wire connecting two Wire Components.

    Properties:

    • emitter: string The pid of the emitter component.
    • emitterPort: number The index of the output port of the emitter component that is connected to this Wire.
    • receiver: string The pid of the receiver component.
    • receiverPort: number The index of the input port of the receiver component that is connected to this Wire.
    {\n  \"emitter\": \"timer\",\n  \"emitterPort\": 0,\n  \"receiver\": \"cond\",\n  \"receiverPort\": 0\n}\n
    "},{"location":"kura-wires/wire-graph-service-configuration-format/#wiregraph","title":"WireGraph","text":"

    An object that describes the topology and rendering properties of a Wire Graph

    Properties:

    • components: array The list of the wire components contained in the Wire Graph
      • array elements: object
      • WireComponent
    • wires: array The list of Wires contained in the Wire Graph
      • array elements: object
      • Wire
    {\n  \"components\": [\n    {\n      \"inputPortCount\": 0,\n      \"outputPortCount\": 1,\n      \"pid\": \"timer\",\n      \"renderingProperties\": {\n        \"inputPortNames\": {},\n        \"outputPortNames\": {},\n        \"position\": {\n          \"x\": -220,\n          \"y\": -20\n        }\n      }\n    },\n    {\n      \"inputPortCount\": 1,\n      \"outputPortCount\": 2,\n      \"pid\": \"cond\",\n      \"renderingProperties\": {\n        \"inputPortNames\": {},\n        \"outputPortNames\": {\n          \"0\": \"foo\",\n          \"1\": \"bar\"\n        },\n        \"position\": {\n          \"x\": 40,\n          \"y\": 0\n        }\n      }\n    }\n  ],\n  \"wires\": [\n    {\n      \"emitter\": \"timer\",\n      \"emitterPort\": 0,\n      \"receiver\": \"cond\",\n      \"receiverPort\": 0\n    }\n  ]\n}\n
    "},{"location":"kura-wires/wire-service-references/","title":"References","text":"

    Additional information about Wires is available at the following resources:

    "},{"location":"kura-wires/wire-service-references/#dzone","title":"DZONE","text":"
    • Kura Wires Can Help Overcome Challenges of Industrial IoT.
    • Kura Wires: A Sneak Peek.
    • Kura Wires: A Different Perspective to Develop IIoT Applications.
    • Different Dataflow Programming Approaches and Comparison With Kura Wires.
    "},{"location":"kura-wires/wire-service-references/#master-thesis","title":"Master Thesis","text":"
    • Kura Wires: Design and Development of a Component for managing Devices and Drivers in Eclipse Kura 2.0 by Amit Kumar Mondal.
    "},{"location":"kura-wires/wire-service-references/#conferences-and-slides","title":"Conferences and slides","text":"
    • Building IoT Mashups for Industry 4.0 with Eclipse Kura and Kura Wires.
    • Industry 4.0 with Eclipse Kura.
    "},{"location":"kura-wires/wire-service-references/#youtube","title":"Youtube","text":"
    • Kura Wires - A Mashup in Eclipse Kura for Industry 4.0.
    • Kura Wires: Industry 4.0 with Eclipse Kura - EclipseCon Europe 2016 IoT Day.
    "},{"location":"kura-wires/wire-service-rest-v1/","title":"Wire Service V1 REST APIs and MQTT Request Handler","text":"

    The WIRE-V1 cloud request handler and the corresponding REST APIs allow to update, delete and get the current Wire Graph status. The request handler also supports creating, updating and deleting Asset and Driver instances and retrieving the metadata required for supporting Wire Graph editing applications.

    The GET/graph/shapshot and PUT/graph/snapshot requests use the same format as the Wire Graph snapshot functionality of the Kura Web UI.

    A Wire Graph snapshot can be obtained by navigating to the Wires section of Kura Web UI, clicking the Download button and selecting the JSON format.

    Accessing the REST APIs requires to use an identity with the rest.wires.admin permission assigned.

    • Wire Service V1 REST APIs and MQTT Request Handler
    • Request definitions
      • GET/graph/shapshot
      • PUT/graph/snapshot
      • DEL/graph
      • GET/drivers/pids
      • GET/assets/pids
      • GET/graph/topology
      • POST/configs/byPid
      • DEL/configs/byPid
      • PUT/configs
      • GET/metadata
      • GET/metadata/wireComponents/factoryPids
      • GET/metadata/wireComponents/definitions
      • POST/metadata/wireComponents/definitions/byFactoryPid
      • GET/metadata/drivers/factoryPids
      • GET/metadata/driver/ocds
      • POST/metadata/drivers/ocds/byFactoryPid
      • GET/metadata/drivers/channelDescriptors
      • POST/metadata/drivers/channelDescriptors/byPid
      • GET/metadata/assets/channelDescriptor
    • JSON definitions
      • WireComponentDefinition
      • DriverChannelDescriptor
      • WireGraphMetadata
    • Wire Graph snapshot example
    "},{"location":"kura-wires/wire-service-rest-v1/#request-definitions","title":"Request definitions","text":""},{"location":"kura-wires/wire-service-rest-v1/#getgraphshapshot","title":"GET/graph/shapshot","text":"
    • REST API path : /services/wire/v1/graph/snapshot
    • description : Returns the current Wire Graph Configuration. The received configuration includes the WireGraphService configuration containing the graph layout, the configuration of the components currently referenced by the Wire Graph, and the configuration of the existing Driver instances.
    • responses :
      • 200
      • description : The current wire graph configuration.
      • response body :
        • ComponentConfigurationList
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#putgraphsnapshot","title":"PUT/graph/snapshot","text":"
    • REST API path : /services/wire/v1/graph/snapshot
    • description : Updates the current Wire Graph.
    • request body :
      • ComponentConfigurationList
    • responses :
      • 200
      • description : The current Wire Graph has been updated.
      • 400
      • description : The request body is not valid JSON or it contains invalid parameters.
      • response body :
        • GenericFailureReport
      • 500
      • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: updateGraph for the graph update operation, update:$pid or delete:$pid for update or delete operations performed on configurations not referenced by the Wire Graph, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
      • response body :

        Variants:

        • object
          • GenericFailureReport
        • object
          • BatchFailureReport

    This request will replace the current graph topology with the received one. The received configuration must satisfy the following requirements. If any of the requirements is not met, the operation will fail and no changes will be applied to the system: * The configuration of the org.eclipse.kura.wire.graph.WireGraphService component must be specified. * The configuration of the org.eclipse.kura.wire.graph.WireGraphService component must contain a property named WireGraph of STRING type containing the graph layout as described in the WireGraphService document. * The inputPortCount and outputPortCount properties must be specified for all components in WireGraphService configuration. * The configuration of all components referenced by WireGraphService configuration that do not exist on target device must be specified. * The configuration of all components referenced by WireGraphService configuration that do not exist on target device must specify the service.factoryPid configuration property reporting the component factory pid.

    If a component already exists on the system and its configuration is supplied as part of the request, the component configuration will be updated. In this case the usual configuration merge semantics will be applied, the set of received properties will be merged with the existing one. The properties in the request body will overwrite the existing ones with the same name.

    WireAsset configurations are treated sligtly differently, an update to a WireAsset configuration is performed by deleting the existing component and creating a new instance with the received configuration. This behavior is necessary in order to allow channel removal.

    It is also allowed to specify Driver or Asset configurations that are not referenced by the Wire Graph included in the request body.

    "},{"location":"kura-wires/wire-service-rest-v1/#delgraph","title":"DEL/graph","text":"
    • REST API path : /services/wire/v1/graph
    • description : Deletes the current Wire Graph.
    • responses :
      • 200
      • description : The current wire graph has been deleted.
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getdriverspids","title":"GET/drivers/pids","text":"
    • REST API path : /services/wire/v1/drivers/pids
    • description : Returns the list of existing Driver pids.
    • responses :
      • 200
      • description : The list of driver pids
      • response body :
        • PidAndFactoryPidSet
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getassetspids","title":"GET/assets/pids","text":"
    • REST API path : /services/wire/v1/assets/pids
    • description : Returns the list of existing Asset pids. The returned pids may or may not be referenced by the current Wire Graph.
    • responses :
      • 200
      • description : The list of driver pids
      • response body :
        • PidAndFactoryPidSet
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getgraphtopology","title":"GET/graph/topology","text":"
    • REST API path : /services/wire/v1/graph/topology
    • description : Returns the current Wire Graph topology as a WireGraph object. The returned object is the current value of the WireGraph property in Wire Graph Service configuration. This request allows to inspect the Wire Graph topology without downloading the entire Wire Graph snapshot with GET/graph/shapshot.
    • responses :
      • 200
      • description : The current wire graph topology.
      • response body :
        • WireGraph
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#postconfigsbypid","title":"POST/configs/byPid","text":"
    • REST API path : /services/wire/v1/configs/byPid
    • description : Returns the list of configurations referenced by the provided pids. This request can only be used to retrieve Wire Component, Driver or Asset configurations.
    • request body :
      • PidSet
    • responses :
      • 200
      • description : The returned configurations. If a configuration cannot be found, it will not be included in the response. The returned configuration list can be empty.
      • response body :
        • ComponentConfigurationList
      • 400
      • description : The request body is not valid JSON or it contains invalid parameters.
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#delconfigsbypid","title":"DEL/configs/byPid","text":"
    • REST API path : /services/wire/v1/configs/byPid
    • description : Deletes the configurations referenced by the provided pids. This request can only be used to delete Wire Component, Driver or Asset configurations. This request does not allow to delete configurations that are referenced by the current Wire Graph.
    • request body :
      • PidSet
    • responses :
      • 200
      • description : The request succeeded.
      • 400
      • description : The request body is not valid JSON or it contains invalid parameters. This status will be returned also if the request references pids that are currenly part of the Wire Graph, or components that are not Wire Components, Driver or Asset instances. If this status is retured, no changes will be applied to the system.
      • response body :
        • GenericFailureReport
      • 500
      • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for delete operations, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
      • response body :

        Variants:

        • object
          • GenericFailureReport
        • object
          • BatchFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#putconfigs","title":"PUT/configs","text":"
    • REST API path : /services/wire/v1/configs
    • description : Updates or creates the provided configurations. This request can only be used to process Wire Component, Driver or Asset configurations. This request does not allow to create Wire Component configurations that are not referenced by the current Wire Graph, except from WireAsset instances. The component creation/update semantics are the same as PUT/graph/snapshot, this request can be used to perform configuration updates whithout knowing or specifying the Wire Graph topology.
    • request body :
      • ComponentConfigurationList
    • responses :
      • 200
      • description : The request succeeded
      • 400
      • description : The request body is not valid JSON or it contains invalid parameters. This status will be returned also if the request involves the creation of components that are not Wire Components, Driver or Asset instances, or the creation of Wire Components that are not referenced by the current Wire Graph. If this status is retured, no changes will be applied to the system.
      • response body :
        • GenericFailureReport
      • 500
      • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid or delete:$pid for update or delete operations, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
      • response body :

        Variants:

        • object
          • GenericFailureReport
        • object
          • BatchFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadata","title":"GET/metadata","text":"
    • REST API path : /services/wire/v1/metadata
    • description : Returns all available Wire Component, Asset and Driver metadata in a single request.
    • responses :
      • 200
      • description : The request succeeded. Single fields in the response can be missing if the corresponding list is empty (e.g. driverDescriptors can be missing if no Driver instances exist on the system)
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatawirecomponentsfactorypids","title":"GET/metadata/wireComponents/factoryPids","text":"
    • REST API path : /services/wire/v1/metadata/wireComponents/factoryPids
    • description : Return the list of available Wire Component factory pids
    • responses :
      • 200
      • description : The request succeeded.
      • response body :
        • PidSet
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatawirecomponentsdefinitions","title":"GET/metadata/wireComponents/definitions","text":"
    • REST API path : /services/wire/v1/metadata/wireComponents/definitions
    • description : Returns all available Wire Component definitions
    • responses :
      • 200
      • description : The available Wire Component definitions. All fields except at most wireComponentDefinitions will be missing.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatawirecomponentsdefinitionsbyfactorypid","title":"POST/metadata/wireComponents/definitions/byFactoryPid","text":"
    • REST API path : /services/wire/v1/metadata/wireComponents/definitions/byFactoryPid
    • description : Returns the Wire Component definitions for the given set of factory pids
    • request body :
      • PidSet
    • responses :
      • 200
      • description : The available Wire Component definitions. All fields except at most wireComponentDefinitions will be missing. If the metadata for a given factoryPid is not found, the request will succeed and it will not be included in the list.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriversfactorypids","title":"GET/metadata/drivers/factoryPids","text":"
    • REST API path : /services/wire/v1/metadata/drivers/factoryPids
    • description : Return the list of available Driver factory pids
    • responses :
      • 200
      • description : The request succeeded.
      • response body :
        • PidSet
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriverocds","title":"GET/metadata/driver/ocds","text":"
    • REST API path : /services/wire/v1/metadata/drivers/ocds
    • description : Returns all available Driver OCDs
    • responses :
      • 200
      • description : The available Driver OCDs. All fields except at most driverOCDs will be missing.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatadriversocdsbyfactorypid","title":"POST/metadata/drivers/ocds/byFactoryPid","text":"
    • REST API path : /services/wire/v1/metadata/drivers/ocds/byFactoryPid
    • description : Returns the Driver OCDSs for the given set of factory pids
    • request body :
      • PidSet
    • responses :
      • 200
      • description : The requested Driver OCDs. All fields except at most driverOCDs will be missing. If the metadata for a given factoryPid is not found, the request will succeed and it will not be included in the list.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriverschanneldescriptors","title":"GET/metadata/drivers/channelDescriptors","text":"
    • REST API path : /wire/v1/metadata/drivers/channelDescriptors
    • description : Returns the list of all available Driver channel descriptors
    • responses :
      • 200
      • description : The list of Driver channel descriptors. All fields except at most driverChannelDescriptors will be missing.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatadriverschanneldescriptorsbypid","title":"POST/metadata/drivers/channelDescriptors/byPid","text":"
    • REST API path : /services/wire/v1/metadata/drivers/channelDescriptors/byPid
    • description : Returns the Driver channel descriptors for the given set of pids
    • request body :
      • PidSet
    • responses :
      • 200
      • description : The requested Driver channel descriptors. All fields except at most driverChannelDescriptors will be missing. If the metadata for a given pid is not found, the request will succeed and it will not be included in the list.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#getmetadataassetschanneldescriptor","title":"GET/metadata/assets/channelDescriptor","text":"
    • REST API path : /wire/v1/metadata/assets/channelDescriptor
    • description : Returns the Asset channel descriptor
    • responses :
      • 200
      • description : The Asset channel descriptors. All fields except assetChannelDescriptor will be missing.
      • response body :
        • WireGraphMetadata
      • 500
      • description : An unexpected internal error occurred.
      • response body :
        • GenericFailureReport
    "},{"location":"kura-wires/wire-service-rest-v1/#json-definitions","title":"JSON definitions","text":""},{"location":"kura-wires/wire-service-rest-v1/#wirecomponentdefinition","title":"WireComponentDefinition","text":"

    A Wire Component definition object

    Properties:

    • factoryPid: string The component factory pid
    • minInputPorts: number The minimum input port count
    • maxInputPorts: number The maximum number of input ports
    • defaultInputPorts: number The default number of input ports
    • minOutputPorts: number The minimum number of output ports
    • maxOutputPorts: number The maximum number of output ports
    • defaultOutputPorts: number The default number of output ports
    • inputPortNames: object
    • optional If no custom input port names are defined
      • PortNameList
    • outputPortNames: object
    • optional If no custom output port names are defined
      • PortNameList
    • componentOcd: array
    • optional If the component OCD is empty The component OCD
      • array elements: object
      • AttributeDefinition
    {\n  \"componentOCD\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"50\",\n      \"description\": \"The maximum number of envelopes that can be stored in the queue of this FIFO component\",\n      \"id\": \"queue.capacity\",\n      \"isRequired\": true,\n      \"name\": \"queue.capacity\",\n      \"type\": \"INTEGER\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"false\",\n      \"description\": \"Defines the behavior in case of full queue: if set to true new envelopes will be dropped,              otherwise, if an emitter delivers an envelope to this component it will block until the envelope can be successfully enqueued.\",\n      \"id\": \"discard.envelopes\",\n      \"isRequired\": true,\n      \"name\": \"discard.envelopes\",\n      \"type\": \"BOOLEAN\"\n    }\n  ],\n  \"defaultInputPorts\": 1,\n  \"defaultOutputPorts\": 1,\n  \"factoryPid\": \"org.eclipse.kura.wire.Fifo\",\n  \"maxInputPorts\": 1,\n  \"maxOutputPorts\": 1,\n  \"minInputPorts\": 1,\n  \"minOutputPorts\": 1\n}\n
    "},{"location":"kura-wires/wire-service-rest-v1/#driverchanneldescriptor","title":"DriverChannelDescriptor","text":"

    An object that describes Driver specific channel configuration properties

    Properties:

    • pid: string The Driver pid
    • factoryPid: string The Driver factory pid
    • channelDescriptor: array
    • optional If the driver does not define any channel property The list of Driver specific channel configuration properties
      • array elements: object
      • AttributeDefinition
    {\n  \"channelDescriptor\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"AA:BB:CC:DD:EE:FF\",\n      \"description\": \"sensortag.address\",\n      \"id\": \"sensortag.address\",\n      \"isRequired\": true,\n      \"name\": \"sensortag.address\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"TEMP_AMBIENT\",\n      \"description\": \"sensor.name\",\n      \"id\": \"sensor.name\",\n      \"isRequired\": true,\n      \"name\": \"sensor.name\",\n      \"option\": [\n        {\n          \"label\": \"TEMP_AMBIENT\",\n          \"value\": \"TEMP_AMBIENT\"\n        },\n        {\n          \"label\": \"TEMP_TARGET\",\n          \"value\": \"TEMP_TARGET\"\n        },\n        {\n          \"label\": \"HUMIDITY\",\n          \"value\": \"HUMIDITY\"\n        },\n        {\n          \"label\": \"ACCELERATION_X\",\n          \"value\": \"ACCELERATION_X\"\n        },\n        {\n          \"label\": \"ACCELERATION_Y\",\n          \"value\": \"ACCELERATION_Y\"\n        },\n        {\n          \"label\": \"ACCELERATION_Z\",\n          \"value\": \"ACCELERATION_Z\"\n        },\n        {\n          \"label\": \"MAGNETIC_X\",\n          \"value\": \"MAGNETIC_X\"\n        },\n        {\n          \"label\": \"MAGNETIC_Y\",\n          \"value\": \"MAGNETIC_Y\"\n        },\n        {\n          \"label\": \"MAGNETIC_Z\",\n          \"value\": \"MAGNETIC_Z\"\n        },\n        {\n          \"label\": \"GYROSCOPE_X\",\n          \"value\": \"GYROSCOPE_X\"\n        },\n        {\n          \"label\": \"GYROSCOPE_Y\",\n          \"value\": \"GYROSCOPE_Y\"\n        },\n        {\n          \"label\": \"GYROSCOPE_Z\",\n          \"value\": \"GYROSCOPE_Z\"\n        },\n        {\n          \"label\": \"LIGHT\",\n          \"value\": \"LIGHT\"\n        },\n        {\n          \"label\": \"PRESSURE\",\n          \"value\": \"PRESSURE\"\n        },\n        {\n          \"label\": \"GREEN_LED\",\n          \"value\": \"GREEN_LED\"\n        },\n        {\n          \"label\": \"RED_LED\",\n          \"value\": \"RED_LED\"\n        },\n        {\n          \"label\": \"BUZZER\",\n          \"value\": \"BUZZER\"\n        },\n        {\n          \"label\": \"KEYS\",\n          \"value\": \"KEYS\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"1000\",\n      \"description\": \"notification.period\",\n      \"id\": \"notification.period\",\n      \"isRequired\": true,\n      \"name\": \"notification.period\",\n      \"type\": \"INTEGER\"\n    }\n  ],\n  \"factoryPid\": \"org.eclipse.kura.driver.ble.sensortag\",\n  \"pid\": \"sensortag\"\n}\n
    "},{"location":"kura-wires/wire-service-rest-v1/#wiregraphmetadata","title":"WireGraphMetadata","text":"

    An object contiaining metatada describing Wire Components, Drivers and Assets

    Properties:

    • wireComponentDefinitions: array
    • optional See request specific documentation The list of Wire Component definitions
      • array elements: object
      • WireComponentDefinition
    • driverOCDs: array
    • optional See request specific documentation The list of Driver factory component OCDs
      • array elements: object
      • ComponentConfiguration
    • driverChannelDescriptors: array
    • optional See request specific documentation The list of Driver channel descriptors
      • array elements: object
      • DriverChannelDescriptor
    • assetChannelDescriptor: array
    • optional See request specific documentation The list of Asset specific channel configuration properties
      • array elements: object
      • AttributeDefinition
    {\n  \"assetChannelDescriptor\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"true\",\n      \"description\": \"Determines if the channel is enabled or not\",\n      \"id\": \"+enabled\",\n      \"isRequired\": true,\n      \"name\": \"enabled\",\n      \"type\": \"BOOLEAN\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"Channel-1\",\n      \"description\": \"Name of the Channel\",\n      \"id\": \"+name\",\n      \"isRequired\": true,\n      \"name\": \"name\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"READ\",\n      \"description\": \"Type of the channel\",\n      \"id\": \"+type\",\n      \"isRequired\": true,\n      \"name\": \"type\",\n      \"option\": [\n        {\n          \"label\": \"READ\",\n          \"value\": \"READ\"\n        },\n        {\n          \"label\": \"READ_WRITE\",\n          \"value\": \"READ_WRITE\"\n        },\n        {\n          \"label\": \"WRITE\",\n          \"value\": \"WRITE\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"INTEGER\",\n      \"description\": \"Value type of the channel\",\n      \"id\": \"+value.type\",\n      \"isRequired\": true,\n      \"name\": \"value.type\",\n      \"option\": [\n        {\n          \"label\": \"BOOLEAN\",\n          \"value\": \"BOOLEAN\"\n        },\n        {\n          \"label\": \"BYTE_ARRAY\",\n          \"value\": \"BYTE_ARRAY\"\n        },\n        {\n          \"label\": \"DOUBLE\",\n          \"value\": \"DOUBLE\"\n        },\n        {\n          \"label\": \"INTEGER\",\n          \"value\": \"INTEGER\"\n        },\n        {\n          \"label\": \"LONG\",\n          \"value\": \"LONG\"\n        },\n        {\n          \"label\": \"FLOAT\",\n          \"value\": \"FLOAT\"\n        },\n        {\n          \"label\": \"STRING\",\n          \"value\": \"STRING\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"description\": \"Scale to be applied to the numeric value of the channel\",\n      \"id\": \"+scale\",\n      \"isRequired\": false,\n      \"name\": \"scale\",\n      \"type\": \"DOUBLE\"\n    },\n    {\n      \"cardinality\": 0,\n      \"description\": \"Offset to be applied to the numeric value of the channel\",\n      \"id\": \"+offset\",\n      \"isRequired\": false,\n      \"name\": \"offset\",\n      \"type\": \"DOUBLE\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"\",\n      \"description\": \"Unit associated to the value of the channel\",\n      \"id\": \"+unit\",\n      \"isRequired\": false,\n      \"name\": \"unit\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"false\",\n      \"description\": \"Specifies if WireAsset should emit envelopes on Channel events\",\n      \"id\": \"+listen\",\n      \"isRequired\": true,\n      \"name\": \"listen\",\n      \"type\": \"BOOLEAN\"\n    }\n  ],\n  \"driverChannelDescriptors\": [\n    {\n      \"channelDescriptor\": [\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"MyNode\",\n          \"description\": \"node.id\",\n          \"id\": \"node.id\",\n          \"isRequired\": true,\n          \"name\": \"node.id\",\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"2\",\n          \"description\": \"node.namespace.index\",\n          \"id\": \"node.namespace.index\",\n          \"isRequired\": true,\n          \"name\": \"node.namespace.index\",\n          \"type\": \"INTEGER\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"DEFINED_BY_JAVA_TYPE\",\n          \"description\": \"opcua.type\",\n          \"id\": \"opcua.type\",\n          \"isRequired\": true,\n          \"name\": \"opcua.type\",\n          \"option\": [\n            {\n              \"label\": \"DEFINED_BY_JAVA_TYPE\",\n              \"value\": \"DEFINED_BY_JAVA_TYPE\"\n            },\n            {\n              \"label\": \"BOOLEAN\",\n              \"value\": \"BOOLEAN\"\n            },\n            {\n              \"label\": \"SBYTE\",\n              \"value\": \"SBYTE\"\n            },\n            {\n              \"label\": \"INT16\",\n              \"value\": \"INT16\"\n            },\n            {\n              \"label\": \"INT32\",\n              \"value\": \"INT32\"\n            },\n            {\n              \"label\": \"INT64\",\n              \"value\": \"INT64\"\n            },\n            {\n              \"label\": \"BYTE\",\n              \"value\": \"BYTE\"\n            },\n            {\n              \"label\": \"UINT16\",\n              \"value\": \"UINT16\"\n            },\n            {\n              \"label\": \"UINT32\",\n              \"value\": \"UINT32\"\n            },\n            {\n              \"label\": \"UINT64\",\n              \"value\": \"UINT64\"\n            },\n            {\n              \"label\": \"FLOAT\",\n              \"value\": \"FLOAT\"\n            },\n            {\n              \"label\": \"DOUBLE\",\n              \"value\": \"DOUBLE\"\n            },\n            {\n              \"label\": \"STRING\",\n              \"value\": \"STRING\"\n            },\n            {\n              \"label\": \"BYTE_STRING\",\n              \"value\": \"BYTE_STRING\"\n            },\n            {\n              \"label\": \"BYTE_ARRAY\",\n              \"value\": \"BYTE_ARRAY\"\n            },\n            {\n              \"label\": \"SBYTE_ARRAY\",\n              \"value\": \"SBYTE_ARRAY\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"STRING\",\n          \"description\": \"node.id.type\",\n          \"id\": \"node.id.type\",\n          \"isRequired\": true,\n          \"name\": \"node.id.type\",\n          \"option\": [\n            {\n              \"label\": \"NUMERIC\",\n              \"value\": \"NUMERIC\"\n            },\n            {\n              \"label\": \"STRING\",\n              \"value\": \"STRING\"\n            },\n            {\n              \"label\": \"GUID\",\n              \"value\": \"GUID\"\n            },\n            {\n              \"label\": \"OPAQUE\",\n              \"value\": \"OPAQUE\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"Value\",\n          \"description\": \"attribute\",\n          \"id\": \"attribute\",\n          \"isRequired\": true,\n          \"name\": \"attribute\",\n          \"option\": [\n            {\n              \"label\": \"NodeId\",\n              \"value\": \"NodeId\"\n            },\n            {\n              \"label\": \"NodeClass\",\n              \"value\": \"NodeClass\"\n            },\n            {\n              \"label\": \"BrowseName\",\n              \"value\": \"BrowseName\"\n            },\n            {\n              \"label\": \"DisplayName\",\n              \"value\": \"DisplayName\"\n            },\n            {\n              \"label\": \"Description\",\n              \"value\": \"Description\"\n            },\n            {\n              \"label\": \"WriteMask\",\n              \"value\": \"WriteMask\"\n            },\n            {\n              \"label\": \"UserWriteMask\",\n              \"value\": \"UserWriteMask\"\n            },\n            {\n              \"label\": \"IsAbstract\",\n              \"value\": \"IsAbstract\"\n            },\n            {\n              \"label\": \"Symmetric\",\n              \"value\": \"Symmetric\"\n            },\n            {\n              \"label\": \"InverseName\",\n              \"value\": \"InverseName\"\n            },\n            {\n              \"label\": \"ContainsNoLoops\",\n              \"value\": \"ContainsNoLoops\"\n            },\n            {\n              \"label\": \"EventNotifier\",\n              \"value\": \"EventNotifier\"\n            },\n            {\n              \"label\": \"Value\",\n              \"value\": \"Value\"\n            },\n            {\n              \"label\": \"DataType\",\n              \"value\": \"DataType\"\n            },\n            {\n              \"label\": \"ValueRank\",\n              \"value\": \"ValueRank\"\n            },\n            {\n              \"label\": \"ArrayDimensions\",\n              \"value\": \"ArrayDimensions\"\n            },\n            {\n              \"label\": \"AccessLevel\",\n              \"value\": \"AccessLevel\"\n            },\n            {\n              \"label\": \"UserAccessLevel\",\n              \"value\": \"UserAccessLevel\"\n            },\n            {\n              \"label\": \"MinimumSamplingInterval\",\n              \"value\": \"MinimumSamplingInterval\"\n            },\n            {\n              \"label\": \"Historizing\",\n              \"value\": \"Historizing\"\n            },\n            {\n              \"label\": \"Executable\",\n              \"value\": \"Executable\"\n            },\n            {\n              \"label\": \"UserExecutable\",\n              \"value\": \"UserExecutable\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"1000\",\n          \"description\": \"listen.sampling.interval\",\n          \"id\": \"listen.sampling.interval\",\n          \"isRequired\": true,\n          \"name\": \"listen.sampling.interval\",\n          \"type\": \"DOUBLE\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"10\",\n          \"description\": \"listen.queue.size\",\n          \"id\": \"listen.queue.size\",\n          \"isRequired\": true,\n          \"name\": \"listen.queue.size\",\n          \"type\": \"LONG\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"true\",\n          \"description\": \"listen.discard.oldest\",\n          \"id\": \"listen.discard.oldest\",\n          \"isRequired\": true,\n          \"name\": \"listen.discard.oldest\",\n          \"type\": \"BOOLEAN\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"false\",\n          \"description\": \"listen.subscribe.to.children\",\n          \"id\": \"listen.subscribe.to.children\",\n          \"isRequired\": true,\n          \"name\": \"listen.subscribe.to.children\",\n          \"type\": \"BOOLEAN\"\n        }\n      ],\n      \"factoryPid\": \"org.eclipse.kura.driver.opcua\",\n      \"pid\": \"opcuaDriver\"\n    }\n  ],\n  \"driverOCDs\": [\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"default-server\",\n            \"description\": \"OPC-UA Endpoint IP Address\",\n            \"id\": \"endpoint.ip\",\n            \"isRequired\": true,\n            \"name\": \"Endpoint IP\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"53530\",\n            \"description\": \"OPC-UA Endpoint Port\",\n            \"id\": \"endpoint.port\",\n            \"isRequired\": true,\n            \"min\": \"1\",\n            \"name\": \"Endpoint port\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"OPC-UA-Server\",\n            \"description\": \"OPC-UA Server Name\",\n            \"id\": \"server.name\",\n            \"isRequired\": false,\n            \"name\": \"Server Name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"false\",\n            \"description\": \"If set to true the driver will use the hostname, port, and server name parameters specified in the configuration instead of the values contained in endpoint descriptions fetched from the server.\",\n            \"id\": \"force.endpoint.url\",\n            \"isRequired\": false,\n            \"name\": \"Force endpoint URL\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"120\",\n            \"description\": \"Session timeout (in seconds)\",\n            \"id\": \"session.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Session timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"60\",\n            \"description\": \"Request timeout (in seconds)\",\n            \"id\": \"request.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Request timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"40\",\n            \"description\": \"The time to wait for the server response to the 'Hello' message (in seconds)\",\n            \"id\": \"acknowledge.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Acknowledge timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"opc-ua client\",\n            \"description\": \"OPC-UA application name\",\n            \"id\": \"application.name\",\n            \"isRequired\": true,\n            \"name\": \"Application name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"urn:kura:opcua:client\",\n            \"description\": \"OPC-UA application uri\",\n            \"id\": \"application.uri\",\n            \"isRequired\": true,\n            \"name\": \"Application URI\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"1000\",\n            \"description\": \"The publish interval in milliseconds for the subscription created by the driver.\",\n            \"id\": \"subscription.publish.interval\",\n            \"isRequired\": true,\n            \"name\": \"Subscription publish interval\",\n            \"type\": \"LONG\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"PFX or JKS Keystore\",\n            \"description\": \"Absolute path of the PKCS or JKS keystore that contains the OPC-UA client certificate, private key and trusted server certificates\",\n            \"id\": \"certificate.location\",\n            \"isRequired\": true,\n            \"name\": \"Keystore path\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"0\",\n            \"description\": \"Security Policy\",\n            \"id\": \"security.policy\",\n            \"isRequired\": true,\n            \"name\": \"Security policy\",\n            \"option\": [\n              {\n                \"label\": \"None\",\n                \"value\": \"0\"\n              },\n              {\n                \"label\": \"Basic128Rsa15\",\n                \"value\": \"1\"\n              },\n              {\n                \"label\": \"Basic256\",\n                \"value\": \"2\"\n              },\n              {\n                \"label\": \"Basic256Sha256\",\n                \"value\": \"3\"\n              }\n            ],\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"description\": \"OPC-UA server username\",\n            \"id\": \"username\",\n            \"isRequired\": false,\n            \"name\": \"Username\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"description\": \"OPC-UA server password\",\n            \"id\": \"password\",\n            \"isRequired\": false,\n            \"name\": \"Password\",\n            \"type\": \"PASSWORD\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"client-ai\",\n            \"description\": \"Alias for the client certificate in the keystore\",\n            \"id\": \"keystore.client.alias\",\n            \"isRequired\": true,\n            \"name\": \"Client certificate alias\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"false\",\n            \"description\": \"Specifies whether to enable or not server certificate verification\",\n            \"id\": \"authenticate.server\",\n            \"isRequired\": true,\n            \"name\": \"Enable server authentication\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"PKCS12\",\n            \"description\": \"Keystore type\",\n            \"id\": \"keystore.type\",\n            \"isRequired\": true,\n            \"name\": \"Keystore type\",\n            \"option\": [\n              {\n                \"label\": \"PKCS11\",\n                \"value\": \"PKCS11\"\n              },\n              {\n                \"label\": \"PKCS12\",\n                \"value\": \"PKCS12\"\n              },\n              {\n                \"label\": \"JKS\",\n                \"value\": \"JKS\"\n              }\n            ],\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"password\",\n            \"description\": \"Configurable Property to set keystore password (default set to password)\",\n            \"id\": \"keystore.password\",\n            \"isRequired\": true,\n            \"name\": \"Keystore password\",\n            \"type\": \"PASSWORD\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"200\",\n            \"description\": \"Maximum number of items that will be included in a single request to the server.\",\n            \"id\": \"max.request.items\",\n            \"isRequired\": true,\n            \"name\": \"Max request items\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"BROWSE_PATH\",\n            \"description\": \"The format to be used for channel name for subtree subscriptions.     If set to BROWSE_PATH, the channel name will contain the browse path of the source node relative to the subscription root.     If set to NODE_ID, the name will contain the node id of the source node.\",\n            \"id\": \"subtree.subscription.name.format\",\n            \"isRequired\": true,\n            \"name\": \"Subtree subscription events channel name format\",\n            \"option\": [\n              {\n                \"label\": \"BROWSE_PATH\",\n                \"value\": \"BROWSE_PATH\"\n              },\n              {\n                \"label\": \"NODE_ID\",\n                \"value\": \"NODE_ID\"\n              }\n            ],\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"OPC-UA Driver\",\n        \"id\": \"org.eclipse.kura.driver.opcua\",\n        \"name\": \"OpcUaDriver\"\n      },\n      \"pid\": \"org.eclipse.kura.driver.opcua\"\n    }\n  ],\n  \"wireComponentDefinitions\": [\n    {\n      \"componentOCD\": [\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"records[0].TIMER !== null && records[0].TIMER.getValue() > 10 && records[0]['TIMER'].getValue() < 30;\\n\",\n          \"description\": \"The boolean expression to be evaluated by this component when a wire envelope is              received.\",\n          \"id\": \"condition\",\n          \"isRequired\": true,\n          \"name\": \"condition\",\n          \"type\": \"STRING\"\n        }\n      ],\n      \"defaultInputPorts\": 1,\n      \"defaultOutputPorts\": 2,\n      \"factoryPid\": \"org.eclipse.kura.wire.Conditional\",\n      \"inputPortNames\": {\n        \"0\": \"if\"\n      },\n      \"maxInputPorts\": 1,\n      \"maxOutputPorts\": 2,\n      \"minInputPorts\": 1,\n      \"minOutputPorts\": 2,\n      \"outputPortNames\": {\n        \"0\": \"then\",\n        \"1\": \"else\"\n      }\n    }\n  ]\n}\n
    "},{"location":"kura-wires/wire-service-rest-v1/#wire-graph-snapshot-example","title":"Wire Graph snapshot example","text":"
    {\n  \"configs\": [\n    {\n      \"pid\": \"org.eclipse.kura.wire.graph.WireGraphService\",\n      \"properties\": {\n        \"WireGraph\": {\n          \"type\": \"STRING\",\n          \"value\": \"{\\\"components\\\":[{\\\"pid\\\":\\\"timer\\\",\\\"inputPortCount\\\":0,\\\"outputPortCount\\\":1,\\\"renderingProperties\\\":{\\\"position\\\":{\\\"x\\\":-300,\\\"y\\\":-20},\\\"inputPortNames\\\":{},\\\"outputPortNames\\\":{}}},{\\\"pid\\\":\\\"logger\\\",\\\"inputPortCount\\\":1,\\\"outputPortCount\\\":0,\\\"renderingProperties\\\":{\\\"position\\\":{\\\"x\\\":-100,\\\"y\\\":-20},\\\"inputPortNames\\\":{},\\\"outputPortNames\\\":{}}}],\\\"wires\\\":[{\\\"emitter\\\":\\\"timer\\\",\\\"emitterPort\\\":0,\\\"receiver\\\":\\\"logger\\\",\\\"receiverPort\\\":0}]}\"\n        }\n      }\n    },\n    {\n      \"pid\": \"timer\",\n      \"properties\": {\n        \"componentDescription\": {\n          \"type\": \"STRING\",\n          \"value\": \"A wire component that fires a ticking event on every configured interval\"\n        },\n        \"componentId\": {\n          \"type\": \"STRING\",\n          \"value\": \"timer\"\n        },\n        \"componentName\": {\n          \"type\": \"STRING\",\n          \"value\": \"Timer\"\n        },\n        \"cron.interval\": {\n          \"type\": \"STRING\",\n          \"value\": \"0/10 * * * * ?\"\n        },\n        \"emitter.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 1\n        },\n        \"factoryComponent\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": false\n        },\n        \"factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"timer\"\n        },\n        \"receiver.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"service.factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer-1642493602000-13\"\n        },\n        \"simple.custom.first.tick.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"simple.first.tick.policy\": {\n          \"type\": \"STRING\",\n          \"value\": \"DEFAULT\"\n        },\n        \"simple.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 10\n        },\n        \"simple.time.unit\": {\n          \"type\": \"STRING\",\n          \"value\": \"SECONDS\"\n        },\n        \"type\": {\n          \"type\": \"STRING\",\n          \"value\": \"SIMPLE\"\n        }\n      }\n    },\n    {\n      \"pid\": \"logger\",\n      \"properties\": {\n        \"componentDescription\": {\n          \"type\": \"STRING\",\n          \"value\": \"A wire component which logs data as received from upstream connected Wire Components\"\n        },\n        \"componentId\": {\n          \"type\": \"STRING\",\n          \"value\": \"logger\"\n        },\n        \"componentName\": {\n          \"type\": \"STRING\",\n          \"value\": \"Logger\"\n        },\n        \"emitter.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"factoryComponent\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": false\n        },\n        \"factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"logger\"\n        },\n        \"log.verbosity\": {\n          \"type\": \"STRING\",\n          \"value\": \"QUIET\"\n        },\n        \"receiver.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 1\n        },\n        \"service.factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger-1642493602046-14\"\n        }\n      }\n    }\n  ]\n}\n
    "},{"location":"kura-wires/wires-mqtt-namespace/","title":"Wires MQTT Namespace","text":"

    The CloudPublisher is a WireComponent that converts a WireEnvelope in a KuraPayload and publishes it over MQTT. Each WireRecord in a WireEnvelope is trivially converted to a KuraPayload and published on a configurable semantic data or control topic. During this process, the emitter PID of the WireEnvelope is discarded.

    The CloudPublisher is agnostic with respect to the contents of the WireEnvelope. It does not know for example if a WireEnvelope contains data readings emitted from a WireAsset. On the other hand, WireAssetS are first-class citizens of a Wire graph and the source of the information which is processed by the downstream Wire components. Eventually, the WireEnvelope representing the output of the processing is connected to a CloudPublisher and published to a Cloud platform.

    In the simplest case, a WireAsset is directly connected to a CloudPublisher and it would be useful to publish the telemetry data under a well-known topic namespace, for example the following full topic:

    <accountName>/<deviceID>/W1/A1/<assetName>\n

    In this case ${assetName} matches the emitterPID of the WireEnvelope emitted by the WireAsset and received by a CloudPublisher.

    Interested applications can then subscribe to (or query a message datastore for):

    • <accountName>/<deviceID>/W1/# for all Wires (W) topics
    • <accountName>/<deviceID>/W1/A1/# for all WireAsset (W/A) topics
    • <accountName>/<deviceID>/W1/A1/<assetName> for all topics for a specific WireAsset

    In a more complex scenario there might be filters between the WireAsset \u201csource\u201d and the CloudPublisher \u201csink\u201d and the emitterPID of the WireEnvelope received by the publisher no longer matches the emitterPID of the WireEnvelope emitted by the WireAsset. However the published data still represents \u201cAsset\u201d (filtered) data and should be published under the topic above.

    The Kura Wires model haven\u2019t the notion of source of an WireEnvelope since a given WireEnvelope instance does not move across the graph but only from one WireEmitter to downstream WireReceiverS that are free to emit something semantically different.

    To overcome this issue:

    • The CloudPublisher can be configured with a \u201csemantic topic template\u201d like W1/A1/$assetName where tokens prefixed with $ will be expanded to the value of a property with the same name in a WireEnvelope\u2019s WireRecord.
    • Into the WireAsset, it is possible to add an assetName property to the WireEnvelope\u2019s WireRecord.
    "},{"location":"kura-wires/multiport-wire-components/join-component/","title":"Join Component","text":"

    The Join Component is a Multiport-enabled component that merges into a single Wire Envelope the properties contained in the envelopes received in the input ports. It is provided by default in every Kura installation.

    In the image above a simple usage example of the Join component: two timers simulate separate paths in the graph and the envelopes received by the Conditional component are then merged into a single Wire Envelope that is then received by the logger component.

    The behaviour of the Join component is specified by the barrier property.

    "},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/","title":"Mathematical Components Example","text":"

    Mathematical Wire components can be installed from the Eclipse Marketplace. For these examples, the Wire Math Multiport Components DP will be used, but other more specific operators can be found on the marketplace (like trigonometric functions).

    The following Multiport-enabled Mathematical examples are provided:

    • Sum
    • Difference
    • Multiplication
    • Division
    "},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#sum","title":"Sum","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#difference","title":"Difference","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#multiplication","title":"Multiplication","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#division","title":"Division","text":""},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/","title":"Multiport Wire Components","text":"

    In order to allow a better routing of data through the Wire Graph, Kura introduces a new class of Wire components named Multiport Wire Components.

    With the addition of this new functionality, a compatible component instance can be defined with an arbitrary number of input and output ports.

    In the example provided, we have two components in the Wire Composer:

    • the Conditional component that implements the if-then-else logic
    • the Join component that joins into a single Wire the data processed by two separate branches of a graph

    Those components are available in every default installation of Kura.

    In order to show the potentialities of the new APIs, in the Eclipse Kura Marketplace and in the Kura downloads page, are available few more multiport-enabled components for Mathematical processing.

    "},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#convert-a-component-to-a-multiport-component","title":"Convert a Component to a Multiport Component","text":""},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#component-configuration-changes","title":"Component Configuration Changes","text":"

    The following properties need to be specified in the component configuration:

    • input.cardinality.minimum: an integer that specifies the minimum number of input ports that the component can be configured to manage.
    • input.cardinality.maximum: an integer that specifies the maximum number of input ports that the component can be configured to manage.
    • input.cardinality.default: an integer that specifies the default number of input ports that the component will be created with, if not specified in a different way.
    • output.cardinality.minimum: an integer that specifies the minimum number of output ports that the component can be configured to manage.
    • output.cardinality.maximum: an integer that specifies the maximum number of output ports that the component can be configured to manage.
    • output.cardinality.default: an integer that specifies the default number of output ports that the component will be created with, if not specified in a different way.
    • input.port.names: optional mapping between input ports and friendly names
    • output.port.names: optional mapping between output ports and friendly names

    The component should also provide service interface org.eclipse.kura.wire.WireComponent

    "},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#code-changes","title":"Code Changes","text":"

    To leverage all the new Multiport functionalities, a Multiport-enabled component must use the newly introduced MultiportWireSupport APIs that provide support to get the list of Emitter and Receiver Ports, as well as to create a new Wire Envelope from a list of Wire Records.

    For the conditional component, that has two output ports, the following code allows to get the proper Wire Support from the Wire Helper Service and to get the then and else ports to be used to push the processed envelopes.

    this.wireSupport = (MultiportWireSupport) this.wireHelperService.newWireSupport(this);\n        final List<EmitterPort> emitterPorts = this.wireSupport.getEmitterPorts();\n        this.thenPort = emitterPorts.get(0);\n        this.elsePort = emitterPorts.get(1);\n

    To emit the result, the code has to be adapted to use the Wire Support to create the Wire Envelope that has to be sent. Effectively, the envelope is sent to the corresponding wire invoking the emit method of the corresponding Port, as shown below.

    final WireEnvelope outputEnvelope = this.wireSupport.createWireEnvelope(inputRecords);\n\nif ((Boolean) decision) {\n    this.thenPort.emit(outputEnvelope);\n} else {\n    this.elsePort.emit(outputEnvelope);\n}\n
    "},{"location":"kura-wires/script-components/graalvm-conditional-component/","title":"GraalVM\u2122 Conditional Component","text":"

    The Conditional Component is a multiport-enabled component that implements the if-then-else logic in the Wire Composer.

    In the image above a simple usage example of the conditional component: a timer ticks and the envelope is received by the conditional component. If the timer has an even number of seconds, then it will evaluate to true and forward the received envelope to the then port: the 'verbose' logger will receive the input.

    The choice between the two ports is performed based on a condition expressed in the component configuration as in the image below.

    "},{"location":"kura-wires/script-components/graalvm-filter-component/","title":"GraalVM\u2122 Filter Component","text":"

    The Filter Component provides scripting functionalities in Kura Wires using the GraalVM\u2122 JavaScript engine:

    • The script execution is triggered when a wire envelope is received by the filter component.
    • It is possible to access the received envelope and inspect the wire records contained in it from the script.
    • The script can optionally emit a wire envelope containing one or more wire records for each wire envelope received.
    • The script context is persisted across multiple executions, allowing to perform stateful computations like running a counter, performing time averages etc.
    • A slf4j Logger is available to the script for debugging purposes.
    • The script context is restricted to allow only Wires-related processing. Any attempt to load additional Java classes will fail.
    • The default configuration contains an example script describing the component usage, it can be executed connecting a Timer and a Logger component.
    "},{"location":"kura-wires/script-components/graalvm-filter-component/#usage","title":"Usage","text":"

    The following global variables are available to the script:

    • input: an object that represents the received wire envelope.
    • output: an object that allows to emit wire records.
    • logger: a slf4j logger.

    The following utility functions are available (see Creating and emitting wire records for usage):

    • newWireRecord(Map<String, TypedValue<?>) -> WireRecord
    • newByteArray(int) -> byte[]
    • newBooleanValue(boolean) -> TypedValue
    • newByteArrayValue(byte[]) -> TypedValue
    • newDoubleValue(number) -> TypedValue
    • newIntegerValue(number) -> TypedValue
    • newLongValue(number) -> TypedValue
    • newStringValue(object) -> TypedValue

    The following global constants expose the org.eclipse.kura.type.DataType enum variants:

    • BOOLEAN
    • BYTE_ARRAY
    • DOUBLE
    • FLOAT
    • INTEGER
    • LONG
    • STRING
    "},{"location":"kura-wires/script-components/graalvm-filter-component/#received-envelope","title":"Received envelope","text":"

    The received envelope is represented by the input global variable and is mapped in Javascript as a WireEnvelope object.

    GraalVM Javascript Engine allows a 1:1 mapping of Java Objects to JavaScript ones. Hence, it is possible to access the WireRecords of the envelope and the emitter pid using the methods specified in the WireEnvelope Kura API:

    logger.info('Emitter pid is {}', input.getEmitterPid())\nvar records = input.getRecords()\n

    The records array can be iterated to extract the WireRecord properties (reference the WireRecord Kura API):

    for(let i=0; i<records.length; i++) {\n // WireRecord.getProperties() returns a map of String - TypedValue\n for (const [keyString, typedValue] of records[i].getProperties()) {\n logger.info('The {}-th record contains:'\\, String(i))\n logger.info('{}={}\\n', keyString, typedValue.getValue())\n }\n}\n

    As in the example above, wireRecord.getProperties returns a map of String, TypedValue. Refer to the TypedValue Kura API for the accessible public methods list.

    "},{"location":"kura-wires/script-components/graalvm-filter-component/#creating-and-emitting-wire-records","title":"Creating and emitting wire records","text":"

    New mutable WireRecord instances can be created using the newWireRecord(Map<String, TypedValue<?>) function. The properties of a mutable WireRecord can be modified by setting Javascript object properties. The properties of a WireRecord object must be instances of the TypedValue class created using the new<type>Value() family of functions. Setting different kind of objects as properties of a WireRecord will result in an exception.

    The output global variable is an object that can be used for emitting WireRecords. This object contains a list of WireRecords that will be emitted when the script execution finishes if no exceptions are thrown. The following code is an example of how to emit a list containing a single WireRecord:

    var output = new Array()\nvar outputMap = new Object()\n\nvar byteArray = newByteArray(4)\nbyteArray[0] = 1\nbyteArray[1] = 2\nbyteArray[2] = 0xaa\nbyteArray[3] = 0xbb\n\noutputMap['example.integer'] = newIntegerValue(10)\noutputMap['example.long'] = newLongValue(100)\noutputMap['example.float'] = newFloatValue(1.014)\noutputMap['example.double'] = newDoubleValue(10.12)\noutputMap['example.boolean'] = newBooleanValue(true)\noutputMap['example.string'] = newStringValue('Hello World!')\noutputMap['example.byte.array'] = newByteArrayValue(byteArray)\n\noutput[0] = newWireRecord(outputMap)\n
    "},{"location":"kura-wires/script-components/graalvm-filter-component/#script-context","title":"Script context","text":"

    The script.context.drop option allows to reset the script context. If set to true the script context will be dropped every time the component configuration is updated, resetting the value of any persisted variable.

    In the example below, with script.context.drop=false the following script will preserve the value of counter across executions. Setting script.context.drop=true will cause counter to be undefined every time the component is triggered.

    counter = typeof(counter) === 'undefined'\n ? 0 // counter is undefined, initialise it to zero\n : counter; // counter is already defined, keep the previous value\n
    "},{"location":"kura-wires/script-components/introduction/","title":"Introduction to the Script Components","text":"

    The Script Components allow for performing more complex operations on the received Wire Envelopes using a JavaScript Engine.

    Depending if the target device is running on Java 8 or Java 17, there are several components to choose from.

    For devices running a JRE with Nashorn JS Engine (Java < 15), a Script Filter and a Conditional Component are provided:

    • Nashorn-based Script Filter
    • Nashorn-based Conditional Component

    The above components will run only on Java < 15 since the Nashorn dependency is not included in the DP. The two components are available in the Eclipse Marketplace as two separate entries. These components are deprecated as of Kura version 5.3.

    The following components instead have the GraalVM\u2122 JavaScript Engine included in the DP and therefore do not require a JRE with Nashorn JS Engine:

    • GraalVM\u2122 Filter Component
    • GraalVM\u2122 Conditional Component

    The input scripts for these components are not compatible with the Nashorn implementations Script Filter and Conditional Component. Both components are shipped as a single DP named org.eclipse.kura.wire.script.tools. Since the JS engine dependency is shipped along with the DP, these components will work on both Java 8 and Java 17 devices but the DP is bigger in size (~18,6 MB).

    "},{"location":"kura-wires/script-components/nashorn-conditional-component/","title":"Nashorn Conditional Component","text":"

    Warning

    This component is deprecated as of Kura version 5.3 since no more available on Java 17.

    The Conditional component is a multiport-enabled component that implements the if-then-else logic in the Wire Composer. It is provided by default in every Kura installation.

    In the image above a simple usage example of the Conditional component: a timer ticks and the envelope is received by the Conditional component. The message is then processed and can be sent downstream to the logger component (then port) or to a publisher (else port).

    The choice between the two ports is performed based on a condition expressed in the Conditional component configuration.

    "},{"location":"kura-wires/script-components/nashorn-script-filter/","title":"Nashorn Script Filter","text":"

    Warning

    This component is deprecated as of Kura version 5.3 since no more available on Java 17.

    The Script Filter Component provides scripting functionalities in Kura Wires using the Nashorn Javascript engine:

    • The script execution is triggered when a wire envelope is received by the filter component.
    • It is possible to access to the received envelope and inspect the wire records contained in it form the script.
    • The script can optionally emit a wire envelope containing one or more wire records for each wire envelope received.
    • The script context is persisted across multiple executions, allowing to perform stateful computations like running a counter, performing time averages etc.
    • A slf4j Logger is available to the script for debug purposes.
    • The script context is restricted in order to allow only Wires related processing. Any attempt to load additional Java classes will fail.
    • The default configuration contains an example script describing the component usage, it can be executed connecting a Timer and a Logger component.
    "},{"location":"kura-wires/script-components/nashorn-script-filter/#usage","title":"Usage","text":"

    The following global variables are available to the script:

    • input: an object that represents the received wire envelope.
    • output: an object that allows to emit wire records.
    • logger: a slf4j logger

    The following utility functions are available (see Creating and emitting wire records for usage):

    • newWireRecord(void) -> WireRecordWrapper
    • newByteArray(void) -> byte[]
    • newBooleanValue(boolean) -> TypedValue
    • newByteArrayValue(byte[]) -> TypedValue
    • newDoubleValue(number) -> TypedValue
    • newFloatValue(number) -> TypedValue
    • newIntegerValue(number) -> TypedValue
    • newLongValue(number) -> TypedValue
    • newStringValue(object) -> TypedValue

    The following global constants expose the org.eclipse.kura.type.DataType enum variants:

    • BOOLEAN
    • BYTE_ARRAY
    • DOUBLE
    • FLOAT
    • INTEGER
    • LONG
    • STRING
    "},{"location":"kura-wires/script-components/nashorn-script-filter/#received-envelope","title":"Received envelope","text":"

    The received envelope is represented by the input global variable and it has the following properties:

    • emitterPid: the emitter pid of the received envelope as a String.
    • records: an immutable array that represents the Wire Records contained in the Wire Envelope.

    Each element of the records array is an immutable object that represents a received wire record. Wire record properties are directly mapped to Javascript object properties, and are instances of the org.eclipse.kura.type.TypedValue class. Each Wire Record property has the following methods available:

    • getType(void) -> DataType: Returns the type of the value, as a DataType enum variant. Can be matched against the data type constants described above.
    • getValue(void) -> Object: Returns the actual value.

    The javascript objects referred as WireRecords in this guide are not instances of the org.eclipse.kura.wire.WireRecord class, but are wrappers that map WireRecord properties into javascript properties. The following code is a simple example script that show how to use the filter:

    // get the first record from the envelope\nvar record = input.records[0]\n// let's assume it contains the LED boolean property and the TEMPERATURE double property\nrecord.LED1.getType() === BOOLEAN // evaluates to true\nif (record.LED1.getValue()) {\n    // LED1 is on\n}\nrecord.LED1.getType() === DOUBLE // evaluates to true\nif (record.TEMPERATURE.getValue() > 50) {\n    // temperature is high, do something\n}\n
    "},{"location":"kura-wires/script-components/nashorn-script-filter/#creating-and-emitting-wire-records","title":"Creating and emitting wire records","text":"

    New mutable WireRecord instances can be created using the newWireRecord(void) -> WireRecordWrapper function. The properties of a mutable WireRecord can be modified by setting Javascript object properties. The properties of a WireRecord object must be instances of the TypedValue class created using the new<type>Value() family of functions. Setting different kind of objects as properties of a WireRecord will result in an exception.

    The output global variable is an object that can be used for emitting WireRecords. This object contains a list of WireRecords that will be emitted when the script execution finishes, if no exceptions are thrown. New records can be added to the list using the add(WireRecordWrapper) function. It is also possible to emit records contained in the received WireEnvelope.

    The script filter will emit a wire envelope only if the WireRecord list is not empty when the script execution completes. The following code is an example about how to emit a value:

    var record = newWireRecord()\n\nvar byteArray = newByteArray()\nbyteArray[0] = 1\nbyteArray[1] = 2\nbyteArray[2] = 0xaa\nbyteArray[3] = 0xbb\n\nrecord.LED1 = newBooleanValue(true)\nrecord.foo = newStringValue('bar')\nrecord['myprop'] = newDoubleValue(123.456)\nrecord['example.long'] = newLongValue(100)\nrecord['example.float'] = newFloatValue(1.014)\nrecord['example.byte.array'] = newByteArrayValue(byteArray)\n\noutput.add(record)\n
    "},{"location":"kura-wires/script-components/nashorn-script-filter/#script-context","title":"Script context","text":"

    The script.context.drop option allows to reset the script context. If set to true the script context will be dropped every time the component configuration is updated, resetting the value of any persisted variable.

    In the example below, with script.context.drop=false the following script will preserve the value of counter across executions. Setting script.context.drop=true will cause counter to be undefined every time the component is triggered.

    counter = typeof(counter) === 'undefined'\n ? 0 // counter is undefined, initialise it to zero\n : counter; // counter is already defined, keep the previous value\n
    "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/","title":"AI Wire Component","text":"

    The component allows interacting with an InferenceEngineService to perform machine learning-related operations. For boards that are not explicitly made for AI, the component can be installed from the Eclipse Marketplace at this link.

    An InferenceEngineService is a Kura service that implements a simple API to interface with an Inference Engine. The Inference Engine allows to perform inference on trained Artificial Intelligence models commonly described by a file and some configuration for explaining its input and outputs. An example of Inference Engine implementation is the Nvidia\u2122 Triton Server inference engine.

    In a normal machine learning flow, the input is preprocessed before it is given to the machine learning algorithm, and the result is processed again to be adapted to the rest of the pipeline.

    Once these models are loaded in the engine, the AI wire component allows to specify the name of the models that are used in the pre-processing, infer, and post-processing steps. Only the infer model name is mandatory so that it is possible to just use the strictly necessary steps in case the pre/post-processing is performed directly by the infer step.

    "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#models-input-and-output-formats","title":"Models Input and Output formats","text":"

    The AI wire component takes a WireEnvelope as an input, it processes its records and feeds them to the specified preprocessing or inference model. The outputs of the inference or the post-processing step are then reconverted into a wire record. This section explains the inputs and output formats that the wire component is expecting. Not specifying the models according to this contract will result in a non-functioning inference.

    The 3 inference steps are applied on each WireRecord contained in the input WireEnvelope.

    The inputs and outputs will have assigned the corresponding Kura DataType, which can be one of:

    • BOOLEAN
    • DOUBLE
    • FLOAT
    • INTEGER
    • LONG
    • STRING
    • BYTE_ARRAY

    Reference to Introduction for the data types that are allowed to flow through the wires.

    The models that manage the input and the output must expect a list of inputs such that:

    • each input corresponds to an entry of the WireRecord properties
    • the entry key will become the input name (e.g. in the case of an asset, the channel name becomes the tensor name)
    • input shape will be [1]

    In the following, two example configurations for Triton Inference Engine models are provided. A complete usage example that implements an Anomaly Detector using a RaspberryPi SenseHat is provided in the Kura examples repository.

    "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#input-specification-example","title":"Input Specification Example","text":"

    Following, an example of a model configuration for the Nvidia\u2122 Triton Inference Engine. It expects the input from the WireEnvelope that contains a record with properties:

    • ACCELERATION of type Float
    • CHANNEL_0 of type Integer
    • STREAM of type byte[]
    • GYRO of type Boolean

    This record can be generated from an asset with channel names as above. The output will be a single tensor of type Float, of shape 1x13, and name OUT_PRE.

    Note that each input will have shape 1.

    name: \"preprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"ACCELERATION\"\n    data_type: FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"CHANNEL_0\"\n    data_type: INT32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"STREAM\"\n    data_type: BYTES\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"GYRO\"\n    data_type: BOOL\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"OUT_PRE\"\n    data_type: FP32\n    dims: [ 13 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
    "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#output-specification-example","title":"Output Specification Example","text":"

    Following, an example of a Nvidia\u2122 Triton Inference Engine configuration that takes input IN_POST and produces outputs that will be mapped to a WireRecord with the properties as follows: - RESULT0 of type Boolean - RESULT1 of type Integer - RESULT2 of type byte[] - RESULT3 of type Float

    Note that each output will have shape 1.

    name: \"postprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"IN_POST\"\n    data_type: FP32\n    dims: [ 1, 5 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT0\"\n    data_type: BOOL\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT1\"\n    data_type: INT32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT2\"\n    data_type: BYTES\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT3\"\n    data_type: FP32\n    dims: [ 1 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
    "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/","title":"Wire Record Store and Wire Record Query","text":"

    This tutorial will present how to use Wire Record Store and Wire Record Query components and provide an example based on the OPC-UA simulated server already used in OPC-UA Application.

    The Wire Record Store component allows the wire graphs to interact with a persistend Wire Record store implementation, for example a SQL database. It stores in a user-defined collection all the envelopes received by the component.

    The Wire Record Query component, instead, can run a custom query on the attached store and emit the result on the wire.

    Note

    The Wire Record Store and Wire Record Query components have been introduced in Kura 5.3.0 as a replacement of the Db Store and Db Filter, that have been deprecated.

    The reason of the deprecation is the fact that Db Store and Db Filter only support databases that provide a JDBC interface. Moreover, the old Db Store interacts with the database using a set of hardcoded SQL queries. This fact makes it difficult to use it with different databases since the syntax of the queries is usually database-specific.

    The new components use the org.eclipse.kura.wire.store.provider.WireRecordStoreProvider and org.eclipse.kura.wire.store.provider.QueryableWireRecordStoreProvider APIs introduced in 5.3.0 allowing to use them with generic data store implementations.

    Please note that Wire Record Query component is not portable by nature, since it allows to execute an arbitrary user defined query. The new APIs allows to use it with non-JDBC data stores.

    The Wire Record Store component can be configured as follows:

    • Record Collection Name: The name of the record collection that should be used. The implementation of the collection depends on the Wire Record Store implementation, if it is a SQL database, the collection will likely be a table.
    • Maximum Record Collection Size: The maximum number of records that is possible to store in the collection.
    • Cleanup Records Keep: The number of records in the collection to keep while performing a cleanup operation (if set to 0 all the records will be deleted). The cleanup operation is performed when a new record is inserted and the current size of the record collection is greater than the configured Maximum Record Collection Size.
    • WireRecordStoreProvider Target Filter : Specifies, as an OSGi target filter, the pid of the of the Wire Record Store instance to be used.

    The Wire Record Query component can be configured as follows:

    • Query: Query to be executed. The query syntax depends on the Queryable Wire Record Store implementation.
    • Cache Expiration Interval (Seconds): This component is capable of maintaining a cache of the records produced by the last query execution and emitting its contents on the Wire until it expires. This value specifies the cache validity interval in seconds. When cache expires, it will cause a new query execution. The query will be executed for every trigger received if the value is set to 0.
    • QueryableWireRecordStoreProvider Target Filter : Specifies, as an OSGi target filter, the pid of the of the Queryable Wire Record Store instance to be used.
    • Emit On Empty Result : Defines the behavior of the component if the result of the performed query is empty. If set to true, an empty envelope will be emitted in this case, if set to false no envelopes will be emitted.

    The following procedure will create a wire graph that collects data from a simulated OPC-UA Server, stores it in a table in the database, using the Wire Record Store component, and publishes it in the cloud platform. Moreover, the Wire Record Query component is used to read from the database and write data to the OPC-UA Server based on the values read.

    "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/#configure-opc-ua-server-simulator","title":"Configure OPC-UA server simulator","text":"
    1. Download the OPC-UA server simulator bundle and install it on your Kura instance. It will create a simulated OPC-UA server that exposes some sensors (light, temperature and water sensor) and some actuators (buzzer, led and fan).
    2. In the Kura Administrative Web Interface, select \u201cOPCUA Server demo\u201d in \u201cServices\u201d and set server.port to 1234. Click the Apply button. This will start an OPC-UA\u200b server on port 1234.
    "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/#configure-wires-opc-ua-application","title":"Configure Wires OPC-UA application","text":"
    1. Install the OPC-UA Driver from Eclipse Kura Marketplace.
    2. Use the local Kura Administrative Web Interface to create a new OPC-UA driver instance:
      • Select Drivers and Assets, click the New Driver button
      • Select org.eclipse.kura.driver.opcua, type in a name, and click Apply: a new service will show up under Services.
    3. Configure the new service as follows:

      • endpoint.ip: localhost
      • endpoint.port: 1234
      • server.name: leave blank
    4. Click on Wires under System

    5. Add a new Timer component and configure the interval at which the OPC-UA server will be sampled
    6. Add a new Asset with the previously added OPC-UA Driver
    7. Configure the new OPC-UA asset, adding new Channels as shown in the following image. Make sure that all the channels are set to READ.

    8. Add a new Wire Record Store named DBStore component and configure it as follows:

      • Record Collection Name: WR_data
      • Maximum Record Collection Size: 10000
      • Cleanup Records Keep: 0
      • WireRecordStoreProvider Target Filter : the Wire Record Store Provider pid to be used
    9. Add a new Publisher component and configure the chosen cloud platform stack in cloud.service.pid option
    10. Add Logger component
    11. Add another instance of Timer
    12. Add a new Wire Record Query component named DBFilter component and configure it as follows. The query will get the values from the light sensor and if they are less than 200, the fan is activated.
      • Query: SELECT (CASE WHEN \u201clight\u201d < 200 THEN 1 ELSE 0 END) AS \u201cled\u201d FROM \u201cWR_data\u201d ORDER BY TIMESTAMP DESC LIMIT 1;
      • Cache Expiration Interval (Seconds): 0
      • QueryableWireRecordStoreProvider Target Filter : the Queryable Wire Record Store Provider pid to be used
    13. Add another Asset with the OPC-UA Driver, configured as shown in the following image. Be sure that all the channels are set to WRITE.

      Note

      Be aware that the Query syntax can vary accordingly to the dialect used by the database. For example, the MySQL dialect doesn't allow to surround the table or columns names with double-quotes. In the H2DB, this is mandatory instead.

    14. Connect the components as shown in the following image, then click on \u201cApply\u201d and check the logs and the cloud platform that the data is correctly published.

    "},{"location":"kura-wires/single-port-wire-components/fifo-component/","title":"FIFO Component","text":"

    This page describes the usage of the FIFO component in Wires.

    The current Wires threading model allows any component to perform potentially blocking operations when a wire envelope is received.

    The fact that the wire envelopes are delivered synchronously implies that if a wire component performs blocking operations, other components in the same subgraph might be blocked as well, introducing delays in the processing of the graph.

    The FIFO component can be used for decoupling blocking or slow wire components from other parts of the graph that cannot tolerate delays.

    In the graph above, the NODELAY component cannot tolerate potential delays introduced by the DB component, adding a FIFO component allows to decouple the two components.

    This component implements a FIFO queue that operates as follows:

    • The received envelopes are added to the queue. Adding an envelope to the queue is usually (see below) a non-blocking operation.
    • A dedicated thread pops the envelopes from the queue and delivers them to downstream components.

    In this way, the threads running the upstream components are not affected by blocking operations performed by the downstream components.

    In the example above there will be two threads that manage the processing of the graph:

    1. A thread from the TIMER Quartz scheduler pool handles the processing for the NODELAY component and submits the envelopes produced by it to the queue of the FIFO component.

    2. A second thread introduced by the FIFO component pops the received envelopes from the queue and dispatches them to the DB component, performing the processing required by it.

    In this way, the NODELAY and DB components are decoupled because they are managed by different threads.

    "},{"location":"kura-wires/single-port-wire-components/fifo-component/#configuration","title":"Configuration","text":"

    The FIFO component configuration is composed of the following properties:

    • queue.capacity: The size of the queue in terms of the number of storable wire envelopes.

    • discard.envelopes : Configures the behavior of the component in case of a full queue.

    • If set to true, envelopes received when the queue is full will be dropped. In this mode, submitting an envelope to the queue is always a non-blocking operation. It should be used if occasionally losing wire envelopes is acceptable for the application, but introducing delays for upstream components is not.

    • If set to false, adding an envelope when the queue is full will block the submitting thread until there is space in the queue. Submitting an envelope to the FIFO component can be a blocking operation if the queue is full. This mode should be used if dropping wire envelopes is unacceptable for upstream components.

    The probability of dropping envelopes in discard.envelopes=true mode or the probability of blocking upstream components in discard.envelopes=false mode can be controlled by setting a proper queue size.

    "},{"location":"kura-wires/usage-examples/eddystone-driver-application/","title":"Eddystone\u2122 Driver Application with RuuviTag+","text":"

    As presented in Eddystone Driver, Kura provides a specific driver that can be used to listen for Eddystone\u2122 beacons.

    This tutorial will explain how to configure an Eddystone\u2122 driver and put it into a Wire graph that retrieves data from a RuuviTag+. For further information about RuuviTag see here.

    "},{"location":"kura-wires/usage-examples/eddystone-driver-application/#configure-kura-wires-eddystone-application","title":"Configure Kura Wires Eddystone application","text":"
    1. Install the Eddystone\u2122 driver from the Eclipse Kura Marketplace.

    2. On the Kura Web Interface, instantiate an Eddystone\u2122 Driver:

      • Under System, select Drivers and Assets and click on the New Driver button.
      • Select org.eclipse.kura.driver.eddystone as Driver Factory, type a name in to Driver Name and click Apply: a new driver will be instantiated and shown up under the Drivers and Assets tab.
      • Configure the new driver setting the bluetooth interface name (e.g. hci0).
    3. From the Drivers and Assets tab, add a new asset bound to the Eddystone\u2122 driver:

      • Click on the New Asset button and fill the form with the Asset Name and selecting the driver created in step 2. as Driver Name. Click Apply and a new asset will be listed under the Eddystone\u2122 driver.

      • Click on the new asset and configure it, adding the channels. Each channel represents a type of frame the Driver is interested to. Please note that in the above picture two channels are created: one for the UID type and the second for the URL. In this example only the URL will be used.
      • Check the listen checkbox for both channels.
      • Click \"Apply\".
    4. Click on Wires under System.

    5. Add a new Asset with the previously added Eddystone asset.

    6. Add a new Javascript Filter component. The filter will be configured to parse the URL frames coming from the RuuviTag+ and extract the environmental data from the on-board sensors. In the script window write the following code:

      function toHexString(str) {\n    var hex = '';\n    for ( i = 0; i < str.length; i++ ) {\n        var hexTemp = str.charCodeAt(i).toString(16)\n        hex += (hexTemp.length==2?hexTemp:'0'+hexTemp);\n}\nreturn hex;\n};\n\nfunction decodeValues(rawSensors) {\n    var rawSensorsDecoded = Base64.decode(rawSensors)\n    logger.info(toHexString(rawSensorsDecoded))\n    var sensorsValues = new Array();\n    // Data Format Definition (4)\n    var format = parseInt(rawSensorsDecoded[0].charCodeAt(0));\n    if (format == 4) {\n        sensorsValues.push(format)\n        // Humidity\n        sensorsValues.push(rawSensorsDecoded[1].charCodeAt(0) * 0.5)\n        // Temperature\n        sensorsValues.push(rawSensorsDecoded[2].charCodeAt(0) & 0x7f)\n        // Pressure\n        sensorsValues.push(parseInt(rawSensorsDecoded[4].charCodeAt(0) << 8 )+ parseInt(rawSensorsDecoded[5].charCodeAt(0) & 0xff) + 50000)\n        // Random id of tag\n        sensorsValues.push(rawSensorsDecoded[6].charCodeAt(0))\n    }\n    return sensorsValues;\n};\n\nload(\"https://gist.githubusercontent.com/jarus/948005/raw/524bea3b4e0b74c06c9cfd2a8e54429dda1918fe/base64.js\")\nvar record = input.records[0]\nif (record.URLEddystone != null) {\n    var values = record.URLEddystone.getValue().split(\";\")\n\n    if (values.length == 5 && values[1].split(\"#\").length == 2) {\n        var outRecord = newWireRecord()\n        var sensorsValues = decodeValues(values[1].split(\"#\")[1])\n        if (sensorsValues.length == 5) {\n            outRecord.format = newIntegerValue(sensorsValues[0])\n            outRecord.humidity = newDoubleValue(sensorsValues[1])\n            outRecord.temperature = newDoubleValue(sensorsValues[2])\n            outRecord.pressure = newIntegerValue(sensorsValues[3])\n            outRecord.id = newIntegerValue(sensorsValues[4])\n        }\n        outRecord.txPower = newIntegerValue(parseInt(values[2]))\n        outRecord.rssi = newIntegerValue(parseInt(values[3]))\n        outRecord.distance = newDoubleValue(parseFloat(values[4]))\n\n        output.add(outRecord)\n    }\n}\n
    7. Add Logger component and set the log.verbosity to VERBOSE.

    8. Connect the Asset to the Filter and this to the Logger.

    9. Click on Apply and check on the logs that the environmental data are correctly logged.

      INFO  o.e.k.w.s.f.p.ScriptFilter - 04541a00c35078\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.ScriptFilter-1537884418687-2\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     txPower : -7\nINFO  o.e.k.i.w.l.Logger -     rssi : -55\nINFO  o.e.k.i.w.l.Logger -     distance : 251.18864315095797\nINFO  o.e.k.i.w.l.Logger -     format : 4\nINFO  o.e.k.i.w.l.Logger -     temperature : 26.0\nINFO  o.e.k.i.w.l.Logger -     humidity : 42.0\nINFO  o.e.k.i.w.l.Logger -     pressure : 100000\nINFO  o.e.k.i.w.l.Logger -     id : 120\nINFO  o.e.k.i.w.l.Logger -\nINFO  o.e.k.w.s.f.p.ScriptFilter - 04401a00c35078\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.ScriptFilter-1537884418687-2\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     txPower : -7\nINFO  o.e.k.i.w.l.Logger -     rssi : -39\nINFO  o.e.k.i.w.l.Logger -     distance : 39.810717055349734\nINFO  o.e.k.i.w.l.Logger -     format : 4\nINFO  o.e.k.i.w.l.Logger -     temperature : 26.0\nINFO  o.e.k.i.w.l.Logger -     humidity : 32.0\nINFO  o.e.k.i.w.l.Logger -     pressure : 100000\nINFO  o.e.k.i.w.l.Logger -     id : 120\nINFO  o.e.k.i.w.l.Logger -\n
    "},{"location":"kura-wires/usage-examples/gpio-driver-application/","title":"GPIO Driver Application","text":"

    In this section a simple but effective example of the GPIO Driver on Wires will be presented. This example will implement a Wire graph that toggles a digital GPIO. A listener will be attached to an input GPIO externally connected to the first one.

    Setup a Raspberry Pi as shown in GPIO Driver section. Add a cable from the LED contact near the red cable to pin 37 (gpio 26) on the RaspberryPi.

    "},{"location":"kura-wires/usage-examples/gpio-driver-application/#configure-kura-wires-gpio-driver-application","title":"Configure Kura Wires GPIO Driver Application","text":"
    1. Install the GPIO Driver from the Eclipse Kura Marketplace.

    2. On the Kura web interface, instantiate a GPIO Driver:

      • Under \"System\", select \"Drivers and Assets\" and click on the \"New Driver\" button.
      • Select \"org.eclipse.kura.driver.gpio\" as \"Driver Factory\", type a name in to \"Driver Name\" and click \"Apply\": a new driver will be instantiated and shown up under the \"Drivers and Assets\" tab.
    3. From the \"Drivers and Assets\" tab, add a new asset bound to the GPIO driver:

      • Click on the \"New Asset\" button and fill the form with the \"Asset Name\" and selecting the driver created in step 2. as \"Driver Name\". Click \"Apply\" and a new asset will be listed under the GPIO driver.
      • Click on the new asset and configure it, adding only one channel called LED as shown in the following picture:

      • Click \"Apply\".
    4. As in point 3., create a new asset as shown below:

      • Click \"Apply\".
    5. Click on \"Wires\" under \"System\".

    6. Add a new \"Timer\" component and configure the interval at which the LED will be toggled.

    7. Add a new \"Script Filter\" (it can be downloaded from the Eclipse Marketplace and configure it with the following script:

      // create a persistent counter\ncounter = typeof(counter) === 'undefined' ? 0 : counter\ncounter++\n\n// emit the counter value in a different WireRecord\nvar counterRecord = newWireRecord()\ncounterRecord.LED = newBooleanValue(counter%2==0)\noutput.add(counterRecord)\n
    8. Add the \"Asset\" created at point 3 and connect the \"Timer\" to the \"Filter\" and the latter to the \"Asset\".

    9. Add the \"Asset\" created at point 4.

    10. Add \"Logger\" component and set log.verbosity to \"VERBOSE\".

    11. Connect the latter \"Asset\" to the \"Logger\". The resulting Wire Graph should be as below:

    12. Click on \"Apply\". After a while, the led on the breadboard should start to blink at a rate defined by the \"Timer\" as shown below:

      Moreover, the kura.log file should show a long sequencce of messages reporting that the value from the input gpio is changed:

      2018-04-09 13:08:42,990 [Thread-3289] INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1523276074484-23\n2018-04-09 13:08:42,991 [Thread-3289] INFO  o.e.k.i.w.l.Logger - Record List content: \n2018-04-09 13:08:42,992 [Thread-3289] INFO  o.e.k.i.w.l.Logger -   Record content: \n2018-04-09 13:08:42,993 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     LED_Feedback : false\n2018-04-09 13:08:42,993 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     assetName : GPIOAssetFeedback\n2018-04-09 13:08:42,994 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     LED_Feedback_timestamp : 1523279322990\n2018-04-09 13:08:42,994 [Thread-3289] INFO  o.e.k.i.w.l.Logger - \n2018-04-09 13:08:44,988 [Thread-3291] INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1523276074484-23\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger - Record List content: \n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -   Record content: \n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     LED_Feedback : true\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     assetName : GPIOAssetFeedback\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     LED_Feedback_timestamp : 1523279324988\n
    "},{"location":"kura-wires/usage-examples/ibeacon-driver-application/","title":"iBeacon\u2122 Driver Application","text":"

    As presented in the iBeacon\u2122 Driver, Kura provides a specific driver that can be used to listen for iBeacons packets.

    This tutorial will explain how to configure a Wire graph that get iBeacon\u2122 data and show them to a logger.

    "},{"location":"kura-wires/usage-examples/ibeacon-driver-application/#configure-the-wires-ibeacontm-application","title":"Configure the Wires iBeacon\u2122 Application","text":"
    1. Install the iBeacon driver from the Eclipse Kura Marketplace.

    2. On the Web Interface, instantiate the iBeacon Driver:

      • Under \"System\", select \"Drivers and Assets\" and click on the \"New Driver\" button.
      • Select \"org.eclipse.kura.driver.ibeacon\" as \"Driver Factory\", type a name in to \"Driver Name\" and click \"Apply\": a new driver will be instantiated and shown up under the \"Drivers and Assets\" tab.
      • Configure the new driver setting the bluetooth interface name (e.g. hci0).
    3. From the \"Drivers and Assets\" tab, add a new asset binded to the iBeacon driver:

      • Click on the \"New Asset\" button and fill the form with the \"Asset Name\" and selecting the driver created in step 2. as \"Driver Name\". Click \"Apply\" and a new asset will be listed under the iBeacon driver.

      • Click on the new asset and configure it, adding a single channel that represents a listener for iBeacon\u2122 advertising packets. Check the listen checkbox for the channel.

      • Click \"Apply\".

    4. Click on \"Wires\" under \"System\".

    5. Add a new \"Asset\" with the previously added iBeacon asset.

    6. Add a new \"Javascript Filter\" component. The filter will be configured to parse the iBeacon packets and extract relevant data from it. In the script window write the following code:

      var record = input.records[0]\nif (record.ibeacon != null) {\n    var values = record.ibeacon.getValue().split(\";\")\n\n    if (values.length == 6) {\n        var outRecord = newWireRecord()\n        outRecord.uuid = newStringValue(values[0])  \n        outRecord.txPower = newIntegerValue(parseInt(values[1]))\n        outRecord.rssi = newIntegerValue(parseInt(values[2]))\n        outRecord.major = newIntegerValue(parseInt(values[3]))\n        outRecord.minor = newIntegerValue(parseInt(values[4]))\n        outRecord.distance = newDoubleValue(parseFloat(values[5]))\n\n        output.add(outRecord)\n    }\n}\n
    7. Add \"Logger\" component and set the log.verbosity to VERBOSE

    8. Connect the \"Asset\" to the \"Filter\" and this to the \"Logger\".
    9. Click on \"Apply\".

      Using this graph, every iBeacon packet will be detected and reported to the log, as shown below. To simulate an iBeacon device, it is possible to use another gateway with the iBeacon advertiser example.

      INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1537886139797-15\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     assetName : iBeaconAsset\nINFO  o.e.k.i.w.l.Logger -     iBeacon : aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee;0;-38;0;0;79.43282347242814\nINFO  o.e.k.i.w.l.Logger -     iBeacon_timestamp : 1537886424085\nINFO  o.e.k.i.w.l.Logger -\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1537886139797-15\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     assetName : iBeaconAsset\nINFO  o.e.k.i.w.l.Logger -     iBeacon : aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee;0;-42;0;0;125.89254117941674\nINFO  o.e.k.i.w.l.Logger -     iBeacon_timestamp : 1537886425086\nINFO  o.e.k.i.w.l.Logger -\n
    "},{"location":"kura-wires/usage-examples/opcua-application/","title":"OPC-UA Application","text":"

    This tutorial will describe how to collect data from an OPC-UA device and publish them on a cloud platform using Wires. The OPC-UA server device will be emulated using a bundle running on Kura.

    "},{"location":"kura-wires/usage-examples/opcua-application/#configure-opc-ua-server-simulator","title":"Configure OPC-UA server simulator","text":"
    1. Download the OPC-UA server simulator bundle and install it on Kura. It will create a simulated OPC-UA server that exposes some sensors (light, temperature and water sensor) and some actuators (buzzer, led and fan).
    2. On the Kura web interface, select OPCUA Server demo in Services and set server.port to 1234. Click the Apply button. This will start an OPC-UA server on port 1234.
    "},{"location":"kura-wires/usage-examples/opcua-application/#configure-wires-opc-ua-application","title":"Configure Wires OPC-UA application","text":"
    1. Install the OPC-UA driver from Eclipse Kura Marketplace.
    2. Use the local Kura Administrative Web Interface to create a new OPC-UA driver instance:
      • Select Drivers and Assets, click the New Driver button
      • Select org.eclipse.kura.driver.opcua, type in a name, and click Apply: a new service will show up under Services.
    3. Configure the new service as follows:
      • endpoint.ip: localhost
      • endpoint.port: 1234
      • server.name: leave blank
    4. Click on Wires under System
    5. Add a new Timer component and configure the interval at which the OPC-UA server will be sampled
    6. Add a new Asset with the previously added OPC-UA driver
    7. Configure the new OPC-UA asset, adding new Channels as shown in the following image.
    8. Add a new Publisher component and configure the chosen cloud platform stack in cloud.service.pid option
    9. Add a Logger component
    10. Connect the Timer to the Asset, and the Asset to the Publisher and Logger as shown in the image below.
    11. Click on Apply and check the logs and the cloud platform in order to verify that the data is correctly published.
    "},{"location":"kura-wires/usage-examples/ti-sensortag-application/","title":"TI SensorTag Application","text":"

    As presented in the Ti SensorTag Driver, Kura provides a specific driver that can be used to interact with Texas Instruments SensorTag devices.

    This tutorial will explain how to configure a Wire graph that connects with a SensorTag, reads sensor values and publishes data to a cloud platform.

    Warning

    The SensorTag driver can be used only with TI SensorTags with firmware version >1.20. If your device has an older firmware, please update it.

    "},{"location":"kura-wires/usage-examples/ti-sensortag-application/#configure-the-ti-sensortag-application","title":"Configure the TI SensorTag Application","text":"
    1. Install the TI SensorTag driver from the Eclipse Kura Marketplace

    2. On the Kura Administrative Web Interface, instantiate a SensorTag Driver:

      • Under System, select Drivers and Assets and click on the New Driver button.
      • Select org.eclipse.kura.driver.ble.sensortag as Driver Factory, type a name in Driver Name and click the Apply button: a new driver will be instantiated and listed in the page.
      • Select the newly created Driver instance and configure the Bluetooth interface name (i.e. hci0).

    3. In the Drivers and Assets tab add a new asset and associate it to the SensorTag driver:

    4. Click on the New Asset button and fill the form with the Asset Name, selecting the driver created at point 2. Click Apply and a new asset will be listed.
    5. Click on the new asset and configure it, adding the channels. Each channel represents a single sensor on the SensorTag and it can be chosen from the sensor.name menu. Fill the sensortag.address with the DB address of the SensorTag you want to connect to. The value.type should be set to double, but also the other choices are possible.
    6. Click Apply.

    7. Apply the following configuration for the Asset instance:

    8. Create a Wire Graph as in the following picture.

      Please note that the driver supports also unsolicited inputs, setting up a notification for the given channel. In this case, it is sufficient to check the listen option for the chosen channel. The Timer is not needed because the SensorTag will automatically emit the values every notification.period milliseconds.

    "},{"location":"quality-assurance/intro/","title":"Introduction","text":""},{"location":"quality-assurance/intro/#eclipsetm-kura-qa","title":"Eclipse\u2122 Kura QA","text":"

    Kura QA activities focused on two main areas:

    • unit and integration tests that are executed each time the project builds and
    • manual tests, most of which are executed in the weeks before releases.

    More on the unit and integration tests can be read in unit testing. More about manual system testing can be read in system testing.

    "},{"location":"quality-assurance/system-testing/","title":"System Testing","text":""},{"location":"quality-assurance/system-testing/#qa-procedure","title":"QA Procedure","text":"

    A set of automated and manual test are performed before releasing a new Eclipse\u2122 Kura version to ensure software follows our quality standards.

    Once a Release Candidate (RC) is tagged on its maintenance branch the QA process starts. The QA process involves a set of automated and manual tests performed on the target environment listed below. These tests are updated continuosly to follow the large amount of features added in each release. The QA process continues with new Release Candidate builds until the amount of defects in the software is reduced. When this happens the RC is promoted to final release and tagged on the maintenance branch.

    "},{"location":"quality-assurance/system-testing/#environment","title":"Environment","text":""},{"location":"quality-assurance/system-testing/#hardware","title":"Hardware","text":"
    • Raspberry Pi 3/4
    • Intel Up2
    • Nvidia Jetson Nano
    • Docker
    "},{"location":"quality-assurance/system-testing/#os","title":"OS","text":"
    • Raspberry Pi OS
    • Ubuntu 20.04
    • Ubuntu 18.04
    • CentOS 7
    "},{"location":"quality-assurance/system-testing/#java","title":"Java","text":"
    • Eclipse Adoptium Temurin\u2122 JDK 1.8
    "},{"location":"quality-assurance/unit-testing/","title":"Unit Testing","text":""},{"location":"quality-assurance/unit-testing/#build-time-testing","title":"Build-time testing","text":"

    Build-time testing is further divided into unit testing and integration testing.

    Unit testing is focused on testing separate methods or groups of methods, preferably in a single class. This way it can verify the correct operation of difficult-to-reach corner cases.

    Integration testing is a more-high-level testing that tests certain functionality on a group of connected services in an approximation of the real environment. It verifies that the services can successfully register in the environment and connect to other services as well as perform their tasks.

    Code coverage of the develop branch can be observed in Jenkins.

    For some tips on running the tests also check Kura GitHub Wiki.

    "},{"location":"quality-assurance/unit-testing/#unit-testing_1","title":"Unit Testing","text":"

    Unit tests should try to cover as many corner cases in the code as possible. Add them for (all) the new code you decide to contribute.

    "},{"location":"quality-assurance/unit-testing/#test-location","title":"Test Location","text":"

    Kura discourages to introduce test-only dependencies on the implementation level, so all tests are located in their own projects under test/. The proper folder to put the tests in is src/test/java.

    "},{"location":"quality-assurance/unit-testing/#code-conventions","title":"Code Conventions","text":"
    • Preferably use <package name>.test as the name of the test project. Add it as a module in test/pom.xml that also serves as maven artifact's parent.
    • Only add src/main/java to build.properties' source...
    • Make the bundle a fragment of the class-under-test's bundle so that you gain access to its internal packages.
    • Use the same package for the test as the class under test. Subpackages are OK.
    • Use the same coding style as Kura. Try to incorporate the suggestions SonarLint may have for your tests.
    "},{"location":"quality-assurance/unit-testing/#running-the-tests","title":"Running the Tests","text":"

    The basic flow is to build your implementation using maven and then also build and run the unit tests using mvn clean test (or some other phase e.g. install, which also runs integration tests).

    Advanced test running and running them in IDE is described in Kura GitHub Wiki.

    "},{"location":"quality-assurance/unit-testing/#integration-testing","title":"Integration Testing","text":"

    These test proper behavior in the OSGi environment. Some additional configuration is therefore necessary.

    "},{"location":"quality-assurance/unit-testing/#test-location_1","title":"Test Location","text":"

    We don't want to mess with the implementation code here, either, so all tests are again located in the test projects under test/. It can be the same project as for unit tests. The proper folder to put the tests in is src/main/java.

    "},{"location":"quality-assurance/unit-testing/#code-conventions_1","title":"Code Conventions","text":"
    • Preferably use <package name>.test as the name of the test project. Add it as a module in test/pom.xml that also serves as maven artifact's parent.
    • Only add src/main/java to build.properties' source...
    • Make the bundle a fragment of the class-under-test's bundle so that you gain access to its internal packages.
    • Use the <package name>.test as the package to put the test in. Also add .test suffix to any subpackages that are also under test.
    • Use the same coding style as Kura. Try to incorporate the suggestions SonarLint may have for your tests.
    "},{"location":"quality-assurance/unit-testing/#running-the-tests_1","title":"Running the Tests","text":"

    The basic flow is to build your implementation using maven and then also build and run the integration tests using mvn clean install.

    Advanced test running and running them in IDE is described in Kura GitHub Wiki.

    "},{"location":"references/javadoc/","title":"Javadoc","text":"
    • Kura API Documentation.
    • Java Communications API (javax.comm).
    • Java USB (javax.usb).
    • Java HIDAPI Javadoc.
    • OpenJDK Device I/O API Documentation.
    • CANBus API Documentation.
    "},{"location":"references/mqtt-namespace/","title":"MQTT Namespace","text":"

    This section provides guidelines on how to structure the MQTT topic namespace for messaging interactions with applications running on IoT gateway devices.

    Interactions may be solicited by a remote server to the gateway using a request/response messaging model, or unsolicited when the gateway simply reports messages or events to a remote server based on periodic or event-driven patterns.

    The table below defines some basic terms used in this document:

    Term Description account_name Identifies a group of devices and users. It can be seen as partition of the MQTT topic namespace. For example, access control lists can be defined so that users are only given access to the child topics of a given account_name. client_id Identifies a single gateway device within an account (typically the MAC address of a gateway\u2019s primary network interface). The client_id maps to the Client Identifier (Client ID) as defined in the MQTT specifications. app_id Unique string identifier for application (e.g., \u201cCONF-V1\u201d, \u201cCONF-V2\u201d, etc.). resource_id Identifies a resource(s) that is owned and managed by a particular application. Management of resources (e.g., sensors, actuators, local files, or configuration options) includes listing them, reading the latest value, or updating them to a new value. A resource_id may be a hierarchical topic, where, for example, \u201csensors/temp\u201d may identify a temperature sensor and \u201csensor/hum\u201d a humidity sensor.

    A gateway, as identified by a specific client_id and belonging to a particular account_name, may have one or more applications running on it (e.g., \u201capp_id1\u201d, \u201capp_id2\u201d, etc.). Each application can manage one or more resources identified by a distinct resource_id(s).

    Based on this criterion, an IoT application running on an IoT gateway may be viewed in terms of the resources it owns and manages as well as the unsolicited events it reports.

    "},{"location":"references/mqtt-namespace/#mqtt-requestresponse-conversations","title":"MQTT Request/Response Conversations","text":"

    Solicited interactions require a request/response message pattern to be established over MQTT. To initiate a solicited conversation, a remote server first sends a request message to a given application running on a specific device and then waits for a response.

    To ensure the delivery of request messages, applications that support request/response conversations via MQTT should subscribe to the following topic on startup:

    $EDC/account_name/client_id/app_id/#\n

    The $EDC prefix is used to mark topics that are used as control topics for remote management. This prefix distinguishes control topics from data topics that are used in unsolicited reports and marks the associated messages as transient (not to be stored in the historical data archive, if present).

    Note

    While Kura currently requires \u201c$EDC\u201d as the prefix for control topics, this prefix may change in the future for the following reasons:

    • MQTT 3.1.1 discourages the use of topic starting with \u201c$\u201d for application purposes.

    • As a binding of LWM2M over MQTT is taking shape, it would make sense to use a topic prefix for management messages like \u201cLWM2M\u201d or similar abbreviations (e.g. \"LW2\u201d, \u201cLWM\u201d).

    A requester (i.e., the remote server) initiates a request/response conversation through the following events:

    1. Generating a conversation identifier known as a request.id (e.g., by concatenating a random number to a timestamp)

    2. Subscribing to the topic where the response message will be published, where requester.client.id is the client ID of the requester, such as:

      $EDC/account_name/requester.client.id/app_id/REPLY/request.id\n
    3. Sending the request message to the appropriate application-specific topic with the following fields in the payload:

      • request.id (identifier used to match a response with a request)
      • requester.client.id (client ID of the requester)

    The application receives the request, processes it, and responds on a REPLY topic structured as:

    $EDC/account_name/requester.client.id/app_id/REPLY/request.id\n

    Note

    While this recommendation does not mandate the format of the message payload, which is application-specific, it is important that the request.id and requester.client.id fields are included in the payload. Kura leverages an MQTT payload encoded through Google Protocol Buffers. Kura includes the request.id and the requester.client.id as two named metrics of the Request messages. The Kura payload definition can be found here.

    Once the response for a given request is received, the requester unsubscribes from the REPLY topic.

    "},{"location":"references/mqtt-namespace/#mqtt-requestresponse-example","title":"MQTT Request/Response Example","text":"

    The following sample request/response conversation shows the device configuration being provided for an application:

    account_name: guest\ndevice client_id: F0:D2:F1:C4:53:DB\napp_id: CONF-V1\nRemote Service Requester client_id: 00:E0:C7:01:02:03\n

    The remote server publishes a request message similar to the following:

    • Request Topic:

      • $EDC/guest/F0:D2:F1:C4:53:DB/CONF-V1/GET/configurations
    • Request Payload:

      • request.id: 1363603920892-8078887174204257595
      • requester.client.id: 00:E0:C7:01:02:03

    The gateway device replies with a response message similar to the following:

    • Response Topic:

      • $EDC/guest/00:E0:C7:01:02:03/CONF-V1/REPLY/1363603920892-8078887174204257595
    • Response Payload, where the following properties are mandatory:

      • response.code Possible response code values include:
        • 200 (RESPONSE_CODE_OK)
        • 400 (RESPONSE_CODE_BAD_REQUEST)
        • 404 (RESPONSE_CODE_NOTFOUND)
        • 500 (RESPONSE_CODE_ERROR)
        • response.exception.message (value is null or an exception message) response.exception.message (value is null or an exception stack trace)

    Note

    In addition to the mandatory properties, the response payload may also have custom properties whose description is beyond the scope of this document.

    It is recommended that the requester server employs a timeout to control the length of time that it waits for a response from the gateway device. If a response is not received within the timeout interval, the server can expect that either the device or the application is offline.

    "},{"location":"references/mqtt-namespace/#mqtt-remote-resource-management","title":"MQTT Remote Resource Management","text":"

    A remote server interacts with the application\u2019s resources through read, create and update, delete, and execute operations. These operations are based on the previously described request/response conversations.

    "},{"location":"references/mqtt-namespace/#read-resources","title":"Read Resources","text":"

    An MQTT message published on the following topic is a read request for the resource identified by the resource_id:

    $EDC/account_name/client_id/app_id/GET/resource_id\n

    The receiving application responds with a REPLY message containing the latest value of the requested resource.

    The resource_id is application specific and may be a hierarchical topic. It is recommended to design resource identifiers following the best practices established for REST API.

    For example, if an application is managing a set of sensors, a read request issued to the topic \"$EDC/account_name/client_id/app_id/GET/sensors\" will reply with the latest values for all sensors.

    Similarly, a read request issued to the topic \"$EDC/account_name/client_id/app_id/GET/sensors/temp\" will reply with the latest value for only a temperature sensor that is being managed by the application.

    "},{"location":"references/mqtt-namespace/#create-or-update-resources","title":"Create or Update Resources","text":"

    An MQTT message published on the following topic is a create or update request for the resource identified by the resource_id:

    $EDC/account_name/client_id/app_id/PUT/resource_id\n

    The receiving application creates the specified resource (or updates it if it already exists) with the value supplied in the message payload and responds with a REPLY message.

    As in the read operations, the resource_id is application specific and may be a hierarchical topic. It is recommended to design resource identifiers following the best practices established for REST API. For example, to set the value for an actuator, a message can be published to the topic \"$EDC/account_name/client_id/app_id/PUT/actuator/1\" with the new value suplliied in the message payload.

    "},{"location":"references/mqtt-namespace/#delete-resources","title":"Delete Resources","text":"

    An MQTT message published on the following topic is a delete request for the resource identified by the resource_id:

    $EDC/account_name/client_id/app_id/DEL/resource_id\n

    The receiving application deletes the specified resource, if it exists, and responds with a REPLY message.

    "},{"location":"references/mqtt-namespace/#execute-resources","title":"Execute Resources","text":"

    An MQTT message published on the following topic is an execute request for the resource identified by the resource_id:

    $EDC/account_name/client_id/app_id/EXEC/resource_id\n

    The receiving application executes the specified resource, if it exists, and responds with a REPLY message. The semantics of the execute operation is application specific.

    "},{"location":"references/mqtt-namespace/#other-operations","title":"Other Operations","text":"

    The IoT application may respond to certain commands, such as taking a snapshot of its configuration or executing an OS-level command. The following topic namespace is recommended for command operations:

    $EDC/account_name/client_id/app_id/EXEC/command_name\n

    An MQTT message published with this topic triggers the execution of the associated command. The EXEC message may contain properties in the MQTT payload that can be used to parameterize the command execution.

    "},{"location":"references/mqtt-namespace/#mqtt-unsolicited-events","title":"MQTT Unsolicited Events","text":"

    IoT applications have the ability to send unsolicited messages to a remote server using events to periodically report data readings from their resources, or to report special events and observed conditions.

    Tip

    It is recommended to not use MQTT control topics for unsolicited events, and subsequently, to avoid the $EDC topic prefix.

    Event MQTT topics generally follow the pattern shown below to report unsolicited data observations for a given resource:

    account_name/client_id/app_id/resource_id\n
    "},{"location":"references/mqtt-namespace/#discoverability","title":"Discoverability","text":"

    The MQTT namespace guidelines in this document do not address remote discoverability of a given device\u2019s applications and its resources. The described interaction pattern can be easily adopted to define an application whose only responsibility is reporting the device profile in terms of installed applications and available resources.

    "},{"location":"references/mqtt-namespace/#remote-osgi-management-via-mqtt","title":"Remote OSGi Management via MQTT","text":"

    The concepts previously described have been applied to develop a solution that allows for the remote management of certain aspects of an OSGi container through the MQTT protocol, including:

    • Remote deployment of application bundles

    • Remote start and stop of services

    • Remote read and update of service configurations

    The following sections describe the MQTT topic namespaces and the application payloads used to achieve the remote management of an OSGi container via MQTT.

    Note

    For the scope of this document, some aspects concerning the encoding and compressing of the payload are not included.

    The applicability of the remote management solution, as inspired by the OSGi component model, can be extended beyond OSGi as the contract with the managing server based on MQTT topics and XML payloads.

    "},{"location":"references/mqtt-namespace/#remote-osgi-configurationadmin-interactions-via-mqtt","title":"Remote OSGi ConfigurationAdmin Interactions via MQTT","text":"

    An application bundle is installed in the gateway to allow for remote management of the configuration properties of the services running in the OSGi container.

    For information about the OSGi Configuration Admin Service and the OSGi Meta Type Service, please refer to the OSGi Service Platform Service R7 Specifications.

    The app_id for the remote configuration service of an MQTT application is \u201cCONF-V1\u201d. The resources it manages are the configuration properties of the OSGi services. Service configurations are represented in XML format.

    The following service configuration XML message is an example of a watchdog service:

    <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<ns2:configuration xmlns:ns2=<http://eurotech.com/esf/2.0\n  xmlns=<http://www.osgi.org/xmlns/metatype/v1.2.0>\n  pid=\"org.eclipse.kura.watchdog.WatchdogService\">\n\n  <OCD id=\"org.eclipse.kura.watchdog.WatchdogService\"\n    name=\"WatchdogService\"\n    description=\"WatchdogService Configuration\">\n\n    <Icon resource=\"WatchdogService\"/>\n\n    <AD id=\"watchdog.timeout\"\n      name=\"watchdog.timeout\"\n      required=\"true\"\n      default=\"10000\"\n      cardinality=\"0\"\n      type=\"Integer\"\n      description=\"\"/>\n  </OCD>\n\n  <ns2:properties>\n    <ns2:property type=\"Integer\" array=\"false\" name=\"watchdog.timeout\">\n      <ns2:value>10000</ns2:value>\n    </ns2:property>\n  </ns2:properties>\n</ns2:configuration>\n

    The service configuration XML message is comprised of the following parts:

    • The Object Class Definition (OCD), which describes the service attributes that may be configured. (The syntax of the OCD element is described in the OSGi Service Platform Service R7 Specifications)

    • The properties element, which contains one or more properties with their associated type and values. The type name must match the name provided in the corresponding attribute definition identifier (AD id) contained in the OCD.

    The \u201cCONF-V1\u201d application supports the read and update resource operations as described in the following sections.

    "},{"location":"references/mqtt-namespace/#read-all-configurations","title":"Read All Configurations","text":"

    This operation provides all service configurations for which remote administration is supported.

    • Request Topic:

      • $EDC/account_name/client_id/CONF-V1/GET/configurations
    • Request Payload:

      • Nothing application-specific beyond the request ID and requester client ID
    • Response Payload:

      • Configurations of all the registered services serialized in XML format
    "},{"location":"references/mqtt-namespace/#read-configuration-for-a-given-service","title":"Read Configuration for a Given Service","text":"

    This operation provides configurations for a specific service that is identified by an OSGi service persistent identifier pid.

    • Request Topic:

      • $EDC/account_name/client_id/CONF-V1/GET/configurations/pid
    • Request Payload:

      • Nothing application-specific beyond the request ID and requester client ID
    • Response Payload:

      • Configurations of the registered service identified by a pid serialized in XML format
    "},{"location":"references/mqtt-namespace/#update-all-configurations","title":"Update All Configurations","text":"

    This operation remotely updates the configuration of a set of services.

    • Request Topic:

      • $EDC/account_name/client_id/CONF-V1/PUT/configurations
    • Request Payload:

      • Service configurations serialized in XML format
    • Response Payload:

      • Nothing application-specific beyond the response code
    "},{"location":"references/mqtt-namespace/#update-the-configuration-of-a-given-service","title":"Update the Configuration of a Given Service","text":"

    This operation remotely updates the configuration of the service identified by a pid.

    • Request Topic:

      • $EDC/account_name/client_id/CONF-V1/PUT/configurations/pid
    • Request Payload:

      • Service configurations serialized in XML format
    • Response Payload:

      • Nothing application-specific
    "},{"location":"references/mqtt-namespace/#example-management-web-application","title":"Example Management Web Application","text":"

    The previously described read and update resource operations can be leveraged to develop a web application that allows for remote OSGi service configuration updates via MQTT though a web user-interface.

    The screen capture that follows shows an example administration application where, for a given IoT gateway, a list of all configurable services is presented to the administrator.

    When one such service is selected, a form is dynamically generated based on the metadata provided in the service OCD. This form includes logic to handle different attribute types, validate acceptable value ranges, and render optional values as drop-downs. When the form is submitted, the new values are communicated to the device through an MQTT resource update message.

    "},{"location":"references/mqtt-namespace/#remote-osgi-deploymentadmin-interactions-via-mqtt","title":"Remote OSGi DeploymentAdmin Interactions via MQTT","text":"

    An application is installed in the gateway to allow for the remote management of the deployment packages installed in the OSGi container.

    For information about the OSGi Deployment Admin Service, please refer to the OSGi Service Platform Service Compendium 4.3 Specifications.

    The app_id for the remote deployment service of an MQTT application is \u201cDEPLOY-V2\u201d. It allows to perform the following operations:

    • Download, install and uninstall OSGi Deployment Packages
    • Download and execute system updates based on shell scripts
    • Get the list of bundles currently in the runtime
    • Start and stop bundles
    "},{"location":"references/mqtt-namespace/#deploy-v2","title":"DEPLOY-V2","text":""},{"location":"references/mqtt-namespace/#download-and-install-messages","title":"Download and Install Messages","text":"

    The download request allows to download and optionally install a software package. The installation procedure will be performed after the download completes if the dp.install metric is set to true. If the metric is set to false, the installation step will not be performed.

    The package type must be specified using the dp.install.system.update request metric, the supported types are the following:

    • OSGi Deployment Package: An OSGi deployment package. Selected with dp.install.system.update = false.
    • Executable Shell Script: A shell script that applies a system level update. Selected with dp.install.system.update = true.

    The device will report the download progress and result of the installation to the cloud platform by sending asynchronous download notification messages and install notification messages.

    The completion notification logic differs depending on the package type.

    • OSGi Deployment Package: The completion notification will be sent immediately after that the Deployment Package is installed on the system.

    • Executable Shell Script: The completion notification will not be sent immediately after that the shell script is executed, but is determined by the execution of an additional verifier script. The verifier script will be executed at next framework startup. A install notification message will be sent afterwards, with dp.install.status = COMPLETED if the exit status is 0 or dp.install.status = FAILED otherwise. This is based on the assumption that a system level update will typically require a device restart.

    The verifier script can be provided in the following ways:

    • By specifying a download URL as the value of the dp.install.verifier.uri request metric. In this case the framework will download the verifier script from the provided URL.

    • By installing it during the execution of the main shell script. In this case the file must be placed in the /opt/eclipse/kura/data/persistance/verification directory. The installed verifier file name must have the ${name}-${version}.sh_verifier.sh structure where ${name} and ${version} must be replaced with the values of the dp.name and dp.version request metrics.

    Warning

    As said above, in case of Executable Shell Script, the completion notification will not be sent if the verifier script is not provided and/or the framework is not restarted.

    Request:

    • Request Topic:

      • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/download
    • Payload:

      • metrics:
        • dp.job.id (Long). Mandatory. Represents a unique Job ID for the download.
        • dp.uri (String). Mandatory. Represents the URI of the deployment package.
        • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
        • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The file will be saved in the temporary directory as -.jar possibly overwriting an existing file.
        • dp.download.protocol (String) Mandatory. Specifies the protocol to be used to download the bundles/shell scripts. Must be set to HTTP or HTTPS.
        • dp.download.block.size (Integer). Optional (if not specified by the cloud platform, it is estimated by the device, depending on the total file size. In this case it is fixed at 1% of the total file size). The size in kBi of the blocks used to download the DP.
        • dp.download.block.delay (Integer). Optional (default: 0). Delay in ms between block transfers.
        • dp.download.notify.block.size (Integer). Optional (if not specified by the cloud platform, it is estimated by the device, depending on the total file size. In this case it is fixed at 5% of the total file size). The size in kBi between the notification messages sent to the cloud platform.
        • dp.download.timeout (Integer). Optional (default: 60000). The timeout in seconds for each block that has to be downloaded.
        • dp.download.resume (Bool). Optional (default: true). Resume download transfer if supported by the server.
        • dp.download.force (Bool). Optional (default: true). Specifies if the download forces to download again the file, if already exists on target device.
        • dp.download.username (String). Optional. Username for password protected download. No authentication will be tried if username is not present.
        • dp.download.password (String). Optional. Password for password protected download. No authentication will be tried if password is not present
        • dp.download.hash (String). Optional. The algorithm and value of the hash of the file used to verify the integrity of the download. The format is of this property is: {algorithm}:{hash value}
        • dp.install (Bool). Optional (default: true). Whether the package should be immediately installed after being downloaded.
        • dp.install.system.update (Bool). Optional (default: false). Sets whether or not this is a system update, rather than a bundle/package update.
        • dp.install.verifier.uri (String). Optional. The verifier script URI to run after the installation of the system update.
        • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package installation process. This property is ignored if dp.install.system.update is true. In such case the reboot must be implemented as part of the script.
        • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if\u00a0dp.reboot is true and dp.install.system.update is false.
        • Response:

          The client will reply immediately with an appropriate response.code. If the platform retries the request but the download is in progress, the client will reply that the request is already in progress with a 500 error code. If the DP has already been downloaded, the client will reply that the request has been accepted. If the dp.download.force flag is set to true, the client will start the download from the beginning, if false the device will proceed with the installation.

          Payload: no application-specific metrics or body.

          Request:

          • Request Topic:
            • $EDC/[account_name]/[client_id]/DEPLOY-V2/GET/download
          • Payload: no application-specific metrics or body.

          Response:

          • Payload:
            • metrics:
              • dp.http.transfer.size (Integer). The size in kBi of the DP being downloaded
              • dp.http.transfer.progress (Integer). The estimated progress of the download in percentage (0-100%). Do not rely on this indicator to detect the download completion.
              • dp.http.transfer.status (String). An enum specifying the download status (IN_PROGRESS, COMPLETED, FAILED...).
              • job.id (Long) Optional. The ID of the job to notify status

          Request:

          • Request Topic:
            • $EDC/[account_name]/[client_id]/DEPLOY-V2/DEL/download
          • Payload: no application-specific metrics or body.

          Response:

          • Response Payload:
            • Nothing application-specific beyond the response code. Unsolicited messages will report the status of the cancel operation.
          "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-download-progress","title":"Unsolicited messages for download progress","text":"

          The client will start downloading the DP and will compute the size of the transfer from the HTTP header. This size will be used to estimate the download progress using the request parameter dp.download.block.size. Next, the client will report the download progress to the platform by publishing, with QoS==2, one or more unsolicited messages. If HTTP header is not available, the device will report 50% as dp.download.progress for all the download processes. The value of requester.client.id is one of the last downloads or install request received.

          Download Notification:

          • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/download
          • Payload:
            • metrics:
              • job.id (Long). The ID of the job to notify status
              • dp.download.size (Integer). The size in kBi of the DP being downloaded
              • dp.download.progress (Integer). The estimated progress of the download in percentage (0-100%). Do not rely on this indicator to detect the download completion.
              • dp.download.status (String). An enum specifying the download status (IN_PROGRESS, COMPLETED, FAILED, CANCELLED...).
              • dp.download.error.message (String). In case of FAILED status, this metric will contain information about the error.
              • dp.download.index (Integer). The index of the file that is currently downloaded. This is supposed to support multiple file downloads.
          "},{"location":"references/mqtt-namespace/#install-messages","title":"Install Messages","text":"

          Request:

          • Request Topic:
            • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/install
          • Payload:
            • metrics:
              • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
              • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The basename of the DP file will to install is derived as -.jar. The file is assumed to reside in the temporary directory.
              • dp.install.system.update (Bool). Mandatory. Specifies if the specified resource is a system update or not. It can be applied to the system immediately or after a system reboot.
              • dp.install.verifier.uri (String). Optional. The verifier script URI to run after the installation of the system update.
              • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package installation process. There might be DPs requiring a post-installation (from the standpoint of the OSGi Deployment Admin) step requiring a system reboot. Note that the post-install phase is not handled by the Deployment Admin. The installation in this case is complete (and can fail) after the reboot. This property is ignored if dp.install.system.update is true. In such case the reboot must be implemented as part of the script.
              • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if\u00a0dp.reboot is true and dp.install.system.update is false.
              • Note

                This operation can be retried. Anyway, if it fails once it's likely to fail again.

                Response:

                • Payload: Nothing application-specific beyond the response code. Unsolicited messages will report the status of the install operation.

                Request:

                • Request Topic:
                  • $EDC/[account_name]/[client_id]/DEPLOY-V2/GET/install
                • Payload: no application-specific metrics or body.

                Response:

                • Payload:
                  • metrics:
                    • dp.install.status (String). An enum specifying the install status
                      • IDLE
                      • INSTALLING BUNDLE
                    • dp.name (String). Optional. If installing: the value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                    • dp.version (String). Optional. If installing: the value of the header DeploymentPackage-Version in the DP MANIFEST.
                "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-install-progress","title":"Unsolicited messages for install progress","text":"

                If the value of dp.install in the original download request is true the client will start installing the DP. Due to the limitations of the OSGi DeploymentAdmin, it's not possible to have feedback on the install progress. However, these operations should normally complete in a few seconds, even for an upgrade. Otherwise (dp.install==false), the platform can request the installation of an already downloaded package through the following message:

                Install notification:

                • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/install
                • Payload:
                  • metrics:
                    • job.id (Long). The ID of the job to notify status
                    • dp.name (String). The name of the package that is installing.
                    • dp.install.progress (Integer). The estimated progress of the install in percentage (0-100%). Due to limitations of the OSGi DeploymentAdmin, it's not possible to have a linear progress. It will go from 0% to 100% in one step. Do not rely on this indicator to detect the download completion.
                    • dp.install.status (String). An enum specifying the install status (IN_PROGRESS, COMPLETED, FAILED...).
                    • dp.install.error.message (String) Optional. In case of FAILED status, this metric will contain information about the error.
                "},{"location":"references/mqtt-namespace/#uninstall-messages","title":"Uninstall Messages","text":"

                Request:

                • Request Topic:
                  • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/uninstall
                • Payload:
                  • metrics:
                    • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                    • job.id (Long) Mandatory. The ID of the job to notify status
                    • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The basename of the DP file will to install is derived as -.jar. The file is assumed to reside in the temporary directory.
                    • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package uninstall process.
                    • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if dp.reboot==true.
                    • Response: The client will reply immediately with an appropriate response.code. If the platform retries the request but the uninstall operation is in progress, the client will reply that the request is already in progress with a 500 error code. At the end of the uninstall operation, an unsolicited message is sent to the cloud platform to report the operation status. If a reboot was requested in the received uninstall request, it will be executed with the specified delay.

                      "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-uninstall-progress","title":"Unsolicited messages for uninstall progress","text":"

                      Uninstall notification:

                      • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/uninstall
                      • Payload:
                        • metrics:
                          • job.id (Long). The ID of the job to notify status
                          • dp.name (String). The name of the package that is uninstalling.
                          • dp.uninstall.progress (Integer). The estimated progress of the install in percentage (0-100%). Due to limitations of the OSGi DeploymentAdmin, it's not possible to have a linear progress. It will go from 0% to 100% in one step. Do not rely on this indicator to detect the download completion.
                          • dp.uninstall.status (String). An enum specifying the uninstall status (IN_PROGRESS, COMPLETED, FAILED...).
                          • dp.uninstall.error.message (String) Optional. In case of FAILED status, this metric will contain information about the error.
                      "},{"location":"references/mqtt-namespace/#read-all-bundles","title":"Read All Bundles","text":"

                      This operation provides all the bundles installed in the OSGi framework.

                      • Request Topic:
                        • $EDC/account_name/client_id/DEPLOY-V2/GET/bundles
                      • Request Payload:
                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:
                        • Installed bundles serialized in XML format

                      The following XML message is an example of a bundle:

                      <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<bundles>\n    <bundle>\n        <name>org.eclipse.osgi</name>\n        <version>3.8.1.v20120830-144521</version>\n        <id>0</id>\n        <state>ACTIVE</state>\n    </bundle>\n    <bundle>\n        <name>org.eclipse.equinox.cm</name>\n        <version>1.0.400.v20120522-1841</version>\n        <id>1</id>\n        <state>ACTIVE</state>\n    </bundle>\n</bundles>\n

                      The bundle XML message is comprised of the following bundle elements:

                      • Symbolic name
                      • Version
                      • ID
                      • State
                      "},{"location":"references/mqtt-namespace/#start-a-bundle","title":"Start a Bundle","text":"

                      This operation starts a bundle identified by its ID.

                      • Request Topic:
                        • $EDC/account_name/client_id/DEPLOY-V2/EXEC/start/bundle_id
                      • Request Payload:
                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:
                        • Nothing application-specific beyond the response code
                      "},{"location":"references/mqtt-namespace/#stop-a-bundle","title":"Stop a Bundle","text":"

                      This operation stops a bundle identified by its ID.

                      • Request Topic:
                        • $EDC/account_name/client_id/DEPLOY-V2/EXEC/stop/bundle_id
                      • Request Payload:
                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:
                        • Nothing application-specific beyond the response code
                      "},{"location":"references/mqtt-namespace/#example-management-web-application_1","title":"Example Management Web Application","text":"

                      The previously described read, start/stop, and install/uninstall resources can be used to implement a remote management application. An example of such application is Eclipse Kapua. In particular it is possible to use the download and install resources from the following sections in Eclipse Kapua console:

                      • Devices section:

                      Selecting the Devices section, a target device, and then clicking on the Install button in the Packages tab will allow to send download and install requests.

                      • Batch Jobs section

                      It is possible to create a batch job with the Package Download / Install definition to perform a download / install request on a set of target devices.

                      "},{"location":"references/mqtt-namespace/#remote-gateway-inventory-via-mqtt","title":"Remote Gateway Inventory via MQTT","text":"

                      An application is installed in the gateway to allow for the remote query of the resources installed in the OSGi container and the underlying OS.

                      The app_id for the remote inventory service of an MQTT application is \u201cINVENTORY-V1\u201d. The service allows retrieving all the different resources available/installed on the gateway. The service supports the following resources:

                      • BUNDLES : represents a OSGi Bundle
                      • DP : represents a OSGi Deployment Package
                      • DEB : represents a Linux Debian package
                      • RPM : represents a Linux RPM package
                      • APK : represents a Linux Alpine APK package
                      • DOCKER : represents a container
                      • CONTAINER IMAGE : represents a container image

                      The resources are represented in JSON format. The following message is an example of a service deployment:

                      {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"io.netty.transport-native-unix-common\",\n            \"version\":\"4.1.34.Final\",\n            \"type\":\"BUNDLE\"\n        },\n    ]\n}\n

                      The inventory JSON message is comprised of the following package elements:

                      • Name

                      • Version

                      • Type

                      The \u201cINVENTORY-V1\u201d application supports only the read resource operations as described in the following sections.

                      "},{"location":"references/mqtt-namespace/#inventory-bundles","title":"Inventory Bundles","text":""},{"location":"references/mqtt-namespace/#read-all-bundles_1","title":"Read All Bundles","text":"

                      This operation provides all the bundles installed in the OSGi framework.

                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/GET/bundles
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Installed bundles serialized in JSON format

                      The following JSON message is an example of a bundle:

                      {\n    \"bundles\":[\n        {\n            \"name\":\"org.eclipse.osgi\",\n            \"version\":\"3.16.0.v20200828-0759\",\n            \"id\":0,\n            \"state\":\"ACTIVE\",\n            \"signed\":true\n        },\n        {\n            \"name\":\"org.eclipse.equinox.cm\",\n            \"version\":\"1.4.400.v20200422-1833\",\n            \"id\":1,\n            \"state\":\"ACTIVE\",\n            \"signed\":false\n        }\n    ]\n}\n

                      The bundle JSON message is comprised of the following bundle elements:

                      • Symbolic name

                      • Version

                      • ID

                      • State

                      • Signed

                      "},{"location":"references/mqtt-namespace/#start-bundle","title":"Start Bundle","text":"

                      This operation allows to start a bundles installed in the OSGi framework.

                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/EXEC/bundles/_start
                      • Request Payload:

                        • A JSON object that identifies the target bundle must be specified in payload body.
                      • Response Payload:

                        • Nothing application specific
                      "},{"location":"references/mqtt-namespace/#stop-bundle","title":"Stop Bundle","text":"
                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/EXEC/bundles/_stop
                      • Request Payload:

                        • A JSON object that identifies the target bundle must be specified in payload body.
                      • Response Payload:

                        • Nothing application specific
                      "},{"location":"references/mqtt-namespace/#json-identifier-for-start-and-stop-requests","title":"JSON identifier for start and stop requests","text":"

                      The requests for starting and stopping a bundle require the application to include a JSON object in request payload for selecting the target bundle, the defined properties are the following:

                      • name: The symbolic name of the bundle to be started/stopped. This parameter must be of string type and it is mandatory.
                      • version: The version of the bundle to be stopped. This parameter must be of string type and it is optional.

                      If multiple bundles match the selection criteria, only one of them will be stopped/started, which one is not defined.

                      Examples:

                      {\n    \"name\":\"org.eclipse.kura.example.beacon\"\n}\n
                      {\n    \"name\":\"org.eclipse.kura.example.beacon\",\n    \"version\":\"1.0.500\"\n}\n
                      "},{"location":"references/mqtt-namespace/#inventory-deployment-packages","title":"Inventory Deployment Packages","text":""},{"location":"references/mqtt-namespace/#read-all-deployment-packages","title":"Read All Deployment Packages","text":"

                      This operation provides the deployment packages installed in the OSGi framework.

                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/GET/packages
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Installed deployment packages serialized in JSON format

                      The following JSON message is an example of a bundle:

                      {\n    \"deploymentPackages\":[\n        {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"signed\":false,\n            \"bundles\":[\n                {\n                    \"name\":\"org.eclipse.kura.example.beacon\",\n                    \"version\":\"1.0.500\",\n                    \"id\": 171,\n                    \"state\": \"ACTIVE\",\n                    \"signed\": false\n                }\n            ]\n        }\n    ]\n}\n

                      The deployment package JSON message is comprised of the following package elements:

                      • Symbolic name

                      • Version

                      • Signature: true if all the bundles in the deployment package are signed

                      • Bundles that are managed by the deployment package along with their symbolic name and version

                      "},{"location":"references/mqtt-namespace/#inventory-system-packages-debrpmapk","title":"Inventory System Packages (DEB/RPM/APK)","text":""},{"location":"references/mqtt-namespace/#read-all-system-packages","title":"Read All System Packages","text":"

                      This operation provides the Linux packages installed in OS.

                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/GET/systemPackages
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Installed Linux Packages serialized in JSON format

                      The following JSON message is an example of a bundle:

                      {\n    \"systemPackages\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"alsa-utils\",\n            \"version\":\"1.1.8-2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"ansible\",\n            \"version\":\"2.7.7+dfsg-1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apparmor\",\n            \"version\":\"2.13.2-10\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-listchanges\",\n            \"version\":\"3.19\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-transport-https\",\n            \"version\":\"1.8.2.2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-utils\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        }\n    ]\n}\n

                      The bundle JSON message is comprised of the following bundle elements:

                      • Name

                      • Version

                      • Type

                      "},{"location":"references/mqtt-namespace/#inventory-containers","title":"Inventory Containers","text":""},{"location":"references/mqtt-namespace/#list-all-containers","title":"List All Containers","text":"

                      Using the API exposed by Inventory-V1, the user can manage containers via external applications such as Eclipse Kapua. This operation lists all the containers installed in the gateway.

                      • Request Topic:
                        • $EDC/account_name/client_id/INVENTORY-V1/GET/containers
                      • Request Payload:
                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:
                        • Installed containers serialized in JSON format

                      The following JSON message is an example of what this request outputs:

                      {\n  \"containers\":\n  [\n    {\n      \"name\":\"container_1\",\n      \"version\":\"nginx:latest\",\n      \"type\":\"DOCKER\",\n      \"state\":\"active\"\n    }\n  ]\n}\n

                      The container JSON message is comprised of the following elements:

                      • Name: The name of the docker container.

                      • Version: describes both the container's respective image and tag separated by a colon.

                      • Type: denotes the type of inventory payload

                      • State: describes the container's current state

                        • active: Container is running
                        • installed: Container is starting
                        • uninstalled: Container has failed, or is stopped
                        • unknown: Container state can not be determined
                      "},{"location":"references/mqtt-namespace/#start-a-container","title":"Start a Container","text":"

                      This operation allows starting a container installed on the gateway. * Request Topic * $EDC/account_name/client_id/INVENTORY-V1/EXEC/containers/_start * Request Payload * A JSON object that identifies the target container must be specified in the payload body. This payload will be described in the following section * Response Payload * Nothing application-specific

                      "},{"location":"references/mqtt-namespace/#stop-a-container","title":"Stop a Container","text":"
                      • Request Topic
                        • $EDC/account_name/client_id/INVENTORY-V1/EXEC/containers/_stop
                      • Request Payload
                        • A JSON object that identifies the target container must be specified in the payload body. This payload will be described in the following section.
                      • Response Payload
                        • Nothing application-specific
                      "},{"location":"references/mqtt-namespace/#json-identifierpayload-for-container-start-and-stop-requests","title":"JSON identifier/payload for container start and stop requests","text":"

                      The requests for starting and stopping a container require the application to include a JSON object in the request payload for selecting the target container. Docker enforces unique container names on a gateway, and thus they can reliably be used as an identifier.

                      Examples:

                      {\n    \"name\":\"container_1\",\n    \"version\":\"nginx:latest\",\n    \"type\":\"DOCKER\",\n    \"state\":\"active\"\n}\n
                      {\n    \"name\":\"container_1\",\n}\n
                      "},{"location":"references/mqtt-namespace/#inventory-container-images","title":"Inventory Container Images","text":""},{"location":"references/mqtt-namespace/#list-all-images","title":"List All Images","text":"

                      Using the API exposed by Inventory-V1, the user can manage container images via external applications such as Eclipse Kapua. This operation lists all the images in the gateway.

                      • Request Topic:
                        • $EDC/account_name/client_id/INVENTORY-V1/GET/images
                      • Request Payload:
                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:
                        • Installed containers serialized in JSON format

                      The following JSON message is an example of what this request outputs:

                      {\n  \"images\":\n  [\n    {\n        \"name\":\"nginx\",\n        \"version\":\"latest\",\n        \"type\":\"CONTAINER_IMAGE\"\n    }\n  ]\n}\n

                      The container JSON message is comprised of the following elements:

                      • Name: The name of the container image.

                      • Version: describes the container image's version.

                      • Type: denotes the type of inventory payload

                      "},{"location":"references/mqtt-namespace/#delete-a-container-image","title":"Delete a Container Image","text":"

                      This operation allows deleting a container image not in use on the gateway. * Request Topic * $EDC/account_name/client_id/INVENTORY-V1/EXEC/images/_delete * Request Payload * A JSON object that identifies the target image must be specified in the payload body. This payload will be described in the following section * Response Payload * Nothing application-specific

                      "},{"location":"references/mqtt-namespace/#json-identifierpayload-for-container-image-delete-requests","title":"JSON identifier/payload for container image delete requests","text":"

                      The requests for deleting a container image require the application to include a JSON object in the request payload for selecting the target. The JSON requires both name and version fields to be populated.

                      Examples:

                      {\n    \"name\":\"nginx\",\n    \"version\":\"latest\",\n    \"type\":\"CONTAINER_IMAGE\"\n}\n
                      {\n    \"name\":\"nginx\",\n    \"version\":\"latest\",\n}\n
                      "},{"location":"references/mqtt-namespace/#inventory-summary","title":"Inventory Summary","text":""},{"location":"references/mqtt-namespace/#read-all-resources","title":"Read All Resources","text":"

                      This operation provides a list of all the resources installed on the gateway

                      • Request Topic:

                        • $EDC/account_name/client_id/INVENTORY-V1/GET/inventory
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Installed Linux Packages serialized in JSON format

                      The following JSON message is an example of a bundle:

                      {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"com.eclipsesource.jaxrs.provider.gson\",\n            \"version\":\"2.3.0.201602281253\",\n            \"type\":\"BUNDLE\"\n        },\n              {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"type\":\"DP\"\n        }\n    ]\n}\n

                      The bundle JSON message is comprised of the following bundle elements:

                      • Name

                      • Version

                      • Type

                      "},{"location":"references/mqtt-namespace/#remote-certificates-and-keys-management-via-mqtt-keys-v1-and-keys-v2","title":"Remote Certificates and Keys management via MQTT (KEYS-V1 and KEYS-V2)","text":""},{"location":"references/mqtt-namespace/#keys-v1","title":"KEYS-V1","text":"

                      The KEYS-V1 app-id is exposed by the org.eclipse.kura.core.keystore bundle. This request handler allows the remote management platform to get a list of all the KeystoreService instances and corresponding keys managed by the framework in a given device.

                      The request handler allows, also, to install new trusted certificate and to generate new key pairs directly in the device. Finally, the remote platform can request, from a defined key pair, the generation of a CSR that can be countersigned remotely by a trusted CA.

                      "},{"location":"references/mqtt-namespace/#read-all-the-kestoreservices","title":"Read All the KestoreServices","text":"

                      This operation returns the list of all the KeystoreServices instantiated in the framework.

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/GET/keystores
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • List of all the managed KeystoreService instances with number of entries stored

                      The following JSON message is an example of an output provided:

                      [\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"type\": \"jks\",\n        \"size\": 4\n    },\n    {\n        \"id\": \"org.eclipse.kura.crypto.CryptoService\",\n        \"type\": \"jks\",\n        \"size\": 3\n    },\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    },\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.DMKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    }\n]\n
                      Each entry of the array is specified by the following values:

                      • id: the KeystoreService PID
                      • type: the type of keystore managed by the given instance
                      • size: the number of entries in a given KeystoreService instance
                      "},{"location":"references/mqtt-namespace/#read-key-entries","title":"Read Key Entries","text":"

                      This operation returns the list of all the key entries managed by the framework. If a request payload is specified, the list of entries is filtered based on the parameters in the request

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/GET/keystores/entries
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID. In this case the response will contain all the entries in all the managed keystoreService instances.
                        • A JSON object with one of the following:
                        {\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\"\n}\n
                        {\n\"alias\": \"ca-godaddyclass2ca\"\n}\n
                      • Response Payload:

                        • List of all the key entries managed by the framework eventually filtered based on the parameters in the request.

                      The following JSON message is an example of an output provided in the response body:

                      [\n    {\n        \"subjectDN\": \"OU=Go Daddy Class 2 Certification Authority, O=\\\"The Go Daddy Group, Inc.\\\", C=US\",\n        \"issuer\": \"OU=Go Daddy Class 2 Certification Authority,O=The Go Daddy Group\\\\, Inc.,C=US\",\n        \"startDate\": \"Tue, 29 Jun 2004 17:06:20 GMT\",\n        \"expirationDate\": \"Thu, 29 Jun 2034 17:06:20 GMT\",\n        \"algorithm\": \"SHA1withRSA\",\n        \"size\": 2048,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"alias\": \"ca-godaddyclass2ca\",\n        \"type\": \"TRUSTED_CERTIFICATE\"\n    },\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
                      "},{"location":"references/mqtt-namespace/#read-key-details","title":"Read Key Details","text":"

                      This operation returns the details associated to a specified key in a keystore

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/GET/keystores/entries/entry
                      • Request Payload:

                        • A JSON with the keystoreServicePid and the alias of the desired key

                      {\n  \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\"\n  \"alias\": \"localhost\"\n}\n
                      * Response Payload: * List of all the details associated to a key managed by the framework

                      The following JSON message is an example of an output provided:

                      {\n    \"algorithm\": \"RSA\",\n    \"size\": 4096,\n    \"certificateChain\": [\n        \"-----BEGIN CERTIFICATE-----\\nMIIFkTCCA3mgAwIBAgIECtXoiDANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJJ\\nVDELMAkGA1UECBMCVUQxDjAMBgNVBAcTBUFtYXJvMREwDwYDVQQKEwhFdXJvdGVj\\naDEMMAoGA1UECxMDRVNGMQwwCgYDVQQDEwNFU0YwHhcNMjEwNDIyMTUxNTU1WhcN\\nMjQwMTE3MTUxNTU1WjBZMQswCQYDVQQGEwJJVDELMAkGA1UECBMCVUQxDjAMBgNV\\nBAcTBUFtYXJvMREwDwYDVQQKEwhFdXJvdGVjaDEMMAoGA1UECxMDRVNGMQwwCgYD\\nVQQDEwNFU0YwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7iZ3fHUQa\\nTPgnvSxGZK4f6MZYfLclD74yqaCCWAztNxPQoiBoSPGdsBGBLNeFbwY0Yzg3qwXw\\nYvgzLJmoXV9rSix7LgXPzsSYfUGfu7PeYTy5bG9X2UVyw9LloUM5DKnw++5F7Xy7\\nF0KQQi0z6/HbbPkZ2aGyNRtMCTh1iAGy3gDh/mMnjpUYuoq1luoX1x6I77X0C+NP\\nTxldVYrTeQiswItAHZmkK1R8AYedbFBgjDuTrfRODxBwESn4kQSMLJ8yHYDRm8S6\\ngVz5LdkcM48UiV5hhF+bCD3UvYA00ZgZm2oOG1ONchYrE7pJr7eQVCYaXkS1lALB\\nKaVJzn03wiLJJv1FYLmGt5J/MwfqyCtBTLlieEVfwnxFCkymtews6SYK32e9q/uJ\\nfcdpWH7tOoarnAf7j5mE84rRU3HqzghK0bMxntfrSH3t18ZUt1/4Qx78WfiM1Te3\\nJtnWBqUNJtX6lgT8IxTWwyEqD183tyKIo8hPGyeJrzWA5RL5hYF5rCNTWzqz5Upi\\n0b/YI5K09+Rn8XmEzzaWjFq5zu6/WpqwPRA8kc2RAEA2scnOT+3yl9Lof/M7BrfL\\nMdjVOZ4MfXgl/fhFyd16AObXuZRUIeiWowKtEiNaiUn8paLDxG+LNV7p5wEQCZZI\\n+MsXMMp6G8Te4yILLCcGov7OkO2wx4GPWQIDAQABo2EwXzAxBgNVHSUEKjAoBggr\\nBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDCDALBgNVHQ8EBAMC\\nAvwwHQYDVR0OBBYEFKM5PlHoe8qFC6w0quGacazGWE/LMA0GCSqGSIb3DQEBCwUA\\nA4ICAQBvpXmbS9LN8n0A+uq+tM3CNtF3YotWRbQHIGJAFTvdq3003W3CVdmykFc8\\n9Kz8PoY1swBJms7GKjQLkqgTHoq6jU/cIXw+CoLQWmvAugva5C1u/5AHJZqTC06J\\nGZyn1Z9N5Lp0XcgogEyhxdbkHniv7jvcmbCurQijZc9nsd5St7e1pT0Co7KKI6Ff\\nODdVP6kZYBzKo4t20tATdAZJ8t7YHNKNq7ZVs1ej9oYUmmQieNXuE4UoHe5hzVQw\\n567cNHWcTHJoyPve03TSQV91wp5rRUKZm2p0WtFNuv22f5p5sQmttsJltzHCgTwE\\nK0j6qYKnXiq+EQs0A3uF9uiIB/KEDLjxscstqsQGFCFOmjA3GSbmJiKCnss3HkNn\\naknT7XCV6tqgDOfPnNzbWJODjYZ+V0DyNY5uqkG2cyREm/qGbH1kLEXhqdWbKqEs\\nsdW6x8p0ImTaPuRl3XEmXbolavIq+FTtOSz8vW1PsdD3quO6krrwiQMXKv1ZMjup\\nDGIZZ4hUUhN84efjlZyoFRvPRvZ8YvjjrHXLij0vcRxndlicevwl5ezlm0LBOpsT\\nkI2uWrbSbxlue/XdgwFCbN0+mXX88fGj6cjhpvd/xnwHaDHfSG9UoU149LJb6ZIZ\\nru+07QriQQxK8V7AdPr6bhmKPxbbFenvSQmsmgjAY93qtanbNg==\\n-----END CERTIFICATE-----\"\n    ],\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\": \"localhost\",\n    \"type\": \"PRIVATE_KEY\"\n}\n
                      "},{"location":"references/mqtt-namespace/#create-csr","title":"Create CSR","text":"

                      This operation returns the CSR for a specific key pair managed by the framework

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/csr
                      • Request Payload:

                        • A JSON with the all the details necessary to generate the CSR
                        { \n    \"keystoreServicePid\":\"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\":\"localhost\",\n    \"signatureAlgorithm\" : \"SHA256withRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                      • Response Payload:

                        • The generated CSR in the body of the message
                        -----BEGIN CERTIFICATE REQUEST-----\nMIIEgTCCAmkCAQAwPDELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VjbGlwc2UxDDAK\nBgNVBAsTA0lvVDENMAsGA1UEAxMES3VyYTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAICTNbBm2wIV/TvddB3OW2s2WJmhAOBxwDSdpxGpgWDzmFAydCt5\nSfWCIeC0kmQfrJpcvcIB7IoE2I7HWtIOxV9c+E+n6R76NvdBQzB8enFfZu4ahIKy\nul2VXQSj0VtYLZvG3yx6af4j8UFWsf2AuAe5Fd1dSBq9aEoRU/D5/uNQOQJi45Hk\nds1KK0FcTkfPjugUCLf1Uf0xXnK1V7yZGrgDpPDbZAYCrcsGomdziO8zkE88gKaa\noC1madGL44yz5tiHTKvbf+O+fKc31N4iDvnIg8f87IMF0D4afDF+3AJjVfcFtp3Q\nxWP3zpKqzPzpzWagTzsW446YMxamZgkDxLsVLitQtesom4ON3HT8s+jxHQhCO5LR\n83Ge10+6viJtkp20GYCqANO85c3TaD9njOE0y8P/T7Nk8MwnBbVgwa15QEWRqjEd\nHB6dF5jKdxlfZhPe2AVnLWAd/W96tCIBSqYu6TTH8npprp/S4t10tRkpaLGa+24c\nVlsjR6AFUX4KksvE/mbXd9QsvKgw/h3g4Jly4W/Ourt1LAH19tzGwULNCS7Ft9rp\nIXUsbmUUwb0V3B3ptcJUDzPUw8LdbItPnXzaPegxmkHO8IllcrdRBXrpcTwJl1ug\nMTMWKW/UjUwKcNQ0mGIxQ18aS0mHk8x8bVTnYLcCnGq3NeiFWvOiJIJpAgMBAAGg\nADANBgkqhkiG9w0BAQsFAAOCAgEATsHVZAEjkMSpwozWbVvDw4iJOSYaQ7ZJXhGZ\nn81puMy/kcdNVD2hfG2c4ern8KPib6hYd1mbQpyNtsbJ68VOPIYOdiaqFd7+lbtM\nIVNETBA9ezXzzXwPCtiJYpmeDYz6HfIzRRzuoJhZtOrgyw8v5wiM0NkenDbTQs4l\nOd/YPFlHnEDkTNM+B/ZJJxRIg3sPhAAgj5HH0Mj2053z66hLDYAo4Tos98MwUcuA\ndY1pcs3brxg6z7xz4vbNKyj0Lh8Gua92OSbl1AFZYb6KXm/7+Md0la/YD+K/E2n6\nhUAcHkr3ayNuTI6lkQFptCHzb4Zr8rdbu63JRno9PFTnW+fa/0xi35DoHD2SAhwA\nCUGXTR+HQXkzB/9NE9X0TxS8SwyrE8sfw4usZm25tACdZ33xziqJXOmbChETyL2b\nJ1IcbsHaeN2Shjnj7UQj+hQFnjVwRLTd0zWMN/l7mPj6TiW9ehubE8ce5siHW7NO\nmqJU1bklxTefefSNHTXrvTInuDXT81gLBRE3x+6uqU2kkJnL8jkrkebDDBhYF+qO\n6dB4W5WGbEHxorX2qfjImvy2Ohsl3rL/DqJgqECZaubTz1Xcj/kl9bdxs0pfa6IY\nInre5iom9bGcA6W6U34jRsrE2pobi6c9Yimrbr/R2O/8Oy2k94FQta8tg8jbAxBi\nZ0Vd1nM=\n-----END CERTIFICATE REQUEST-----\n
                      "},{"location":"references/mqtt-namespace/#store-trusted-certificate","title":"Store Trusted Certificate","text":"

                      This operation stores the provided certificate as a Trusted Certificate Entry managed by the framework

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/certificate
                      • Request Payload:

                        • A JSON with the all the details necessary to generate create the new entry
                        {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"myCertTest99\",\n    \"certificate\":\"-----BEGIN CERTIFICATE-----\n        MIIDdzCCAl+gAwIBAgIEQsO0gDANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdV\n        bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n        VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du\n        MB4XDTIxMDQxNDA4MDIyOFoXDTIxMDcxMzA4MDIyOFowbDEQMA4GA1UEBhMHVW5r\n        bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE\n        ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC\n        ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJSWJDxu8UNC4JGOgK31WCvz\n        NKy2ONH+jTVKnBY7Ckb1hljJY0sKO55aG1HNDfkev2lJTsPIz0nJjNsqBvB1flvf\n        r6XVCxdN0yxvU5g9SpRxE/iiPX0Qt7463OfzyKW97haJrrhF005RHYNcORMY/Phj\n        hFDnZhtAwpbQLzq2UuIZ7okJsx0IgRbjH71ZZuvYCqG7Ct/bp1D7w3tT7gTbIKYH\n        ppQyG9rJDEh9+cr9Hyk8Gz7aAbPT/wMH+/vXDjH2j/M1Tmed0ajuGCJumaTQ4eHs\n        9xW3B3ugycb6e7Osl/4ESRO5RQL1k2GBONv10OrKDoZ5b66xwSJmC/w3BRWQ1cMC\n        AwEAAaMhMB8wHQYDVR0OBBYEFPospETb5HNeD/DmS9mwt+v/AYq/MA0GCSqGSIb3\n        DQEBCwUAA4IBAQBxMe1xQVQKt36A5qVlEZyxI9eb6eQRlYzorOgP2tFaOsvDPpRI\n        CALhPmxgQl/5QvKFfCXKoxWj1Spg4sF6fJp6jhSjLpmChS9lf5fRaWS20/pxIddM\n        10diq3r6HxLKSxCYK7Pf5scOeZquvwfo8Kxye01bvCMFf1s1K3ZEZszk5Oo2MnWU\n        U22YnXfZm1C0h2WMUcou35A7CeVAHPWI0Rvefojv1qYlQScJOkCN5lO6C/1qvRhq\n        nDQdQN/m1HQbpfh2DD6F33nBjkyLQyMRF8uMnspLrLLj8lecSTJZO4fGJOaIXh3O\n        44da9A02FAf5nRRQpwP2x/4IZ5RTRBzrqbqD\n        -----END CERTIFICATE-----\"\n}\n
                      • Response Payload:

                        • Nothing
                      "},{"location":"references/mqtt-namespace/#generate-keypair","title":"Generate KeyPair","text":"

                      This operation will generate a new key pair directly in the device, based on the parameters received from the request

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/keypair
                      • Request Payload:

                        • A JSON with the all the details necessary to create the new key pair
                        {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"algorithm\" : \"RSA\",\n    \"size\": 1024,\n    \"signatureAlgorithm\" : \"SHA256WithRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                      • Response Payload:

                        • Nothing
                      "},{"location":"references/mqtt-namespace/#delete-entry","title":"Delete Entry","text":"

                      This operation will delete the specified entry from the framework managed keystores

                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V1/DEL/keystores/entries
                      • Request Payload:

                        • A JSON with the all the details necessary to identify the entry to be deleted
                        {\n    \"keystoreServicePid\" : \"MyKeystore\",\n    \"alias\" : \"mycerttestec\"\n}\n
                      • Response Payload:

                        • Nothing
                      "},{"location":"references/mqtt-namespace/#keys-v2","title":"KEYS-V2","text":"

                      Starting from Kura 5.4.0, the KEYS-V2 request handler is also available, it supports all of the request endpoints of KEYS-V1 plus an additional endpoint that allows to upload and modify private key entries:

                      "},{"location":"references/mqtt-namespace/#uploading-a-private-key-entry","title":"Uploading a Private Key Entry","text":"
                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V2/POST/keystores/entries/privatekey
                      • Request Payload:

                        The request should include the private key in unencrypted PEM format and the certificate chain in PEM format, the first certificate in the certificateChain list must use the public key associated with the private key supplied as the privateKey parameter.

                        The device will overwrite the entry with the provided alias if it already exists.

                        {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"privateKey\":\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                      • Response Payload:

                        • Nothing
                      "},{"location":"references/mqtt-namespace/#updating-a-private-key-entry","title":"Updating a Private Key Entry","text":"
                      • Request Topic:

                        • $EDC/account_name/client_id/KEYS-V2/POST/keystores/entries/privatekey
                      • Request Payload:

                        In order to update the certificate chain associated to a specific private key entry it is possible to use the same format as previous request and omit the privateKey parameter.

                        In this case the certificate chain of the existing entry will be replaced with the one specified in the request and the existing private key will be retained.

                        This request can be useful for example to create a CSR on the device, sign it externally and then updating the corresponding entry with the resulting certificate.

                        {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                      • Response Payload:

                        • Nothing
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/","title":"Rest CloudConnection v1 API","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: CLD-V1.

                      The CloudConnectionRestService APIs provides methods to manage cloud connection related components like CloudEndpoint, CloudPublisher and CloudSubscriber instances.

                      Identities with rest.cloudconnection permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#find-cloud-component-instances","title":"Find Cloud Component Instances","text":"
                      • Description: This method returns all the Cloud Component instances, including CloudEndpoint, CloudPublisher and CloudSubscriber instances.
                      • Method: GET
                      • API PATH: services/cloudconnection/v1/instances
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses","title":"Responses","text":"
                      • 200 Ok Status
                        {\n    \"cloudEndpointInstances\": [\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-todelete\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-2\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-3\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-test\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        }\n    ],\n    \"pubsubInstances\": [\n        {\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"pid\": \"testPub\",\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"type\": \"PUBLISHER\"\n        },\n        {\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"pid\": \"testPub\",\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"type\": \"SUBSCRIBER\"\n        }\n    ]\n}\n
                      • cloudEndpointType: The possible values are:

                        • CLOUD_CONNECTION_MANAGER if the component implements CloudConnectionManager

                        • CLOUD_ENDPOINT otherwise.

                      If cloudEndpointType is CLOUD_CONNECTION_MANAGER, it is possible to use the cloudEndpoint/connect, cloudEndpoint/disconnect and cloudEndpoint/isConnected methods to manage the connection state.

                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-cloudcomponent-factories","title":"Get CloudComponent Factories","text":"
                      • Description: This method returns all the Factories able to create Component for CloudEndpoint, including CloudConnectionFactory, CloudPublisher, CloudSubscriber
                      • Method: GET
                      • API PATH: services/cloudconnection/v1/factories
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_1","title":"Responses","text":"
                      • 200 Ok Status
                        {\n    \"cloudConnectionFactories\": [\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-2\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloud.CloudService\\\\-[a-zA-Z0-9]+$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.CloudEndpoint\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloudconnection.raw.mqtt.CloudEndpoint(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.camel.cloud.factory.CamelFactory\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.camel.cloud.factory.CamelFactory\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.camel.cloud.factory.CamelFactory(\\\\-[a-zA-Z0-9]+)?$\"\n        }\n    ],\n    \"pubSubFactories\": [\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.subscriber.RawMqttSubscriber\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"defaultPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\",\n            \"defaultPidRegex\": \"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloud.subscriber.CloudSubscriber\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.publisher.RawMqttPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.event.publisher.EventPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        }\n    ]\n}\n

                      For cloudConnectionFactories elements:

                      • cloudConnectionFactoryPid: the PID of the cloud connection factory
                      • defaultCloudEndpointPid: If set, it represents the default PID for an instance of a new component suggested by the factory. This can be used by an user interface as a suggestion/placeholder for the name of a new cloud endpoint.
                      • cloudEndpointPidRegex: If set, its value represents a regular expression that the PID of a new component must match.

                      For pubSubFactories elements:

                      • factoryPid: The factory PID of the publisher/subscripter component. It identifies the component type. It can be used for example to create a new component using the pubSub POST method.
                      • cloudConnectionFactoryPid: Specifies the cloudConnectionFactoryPid of the CloudConnectionFactory associated with this component. Each publisher/subscriber component is only compatible with CloudEndpoint instances created by the factory having this cloudConnectionFactoryPid.
                      • defaultPid: If set, it represents the default PID for an instance of a new component suggested by the factory. This can be used by an user interface as a suggestion/placeholder for the name of a new publisher/subscriber.
                      • defaultPidRegex: If set, its value represents a regular expression that the PID of a new component must match.

                      • 500 Internal Server error

                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#get-stackcomponents-pids","title":"Get StackComponents Pids","text":"
                      • Description: This method retrieves all all PIDs of Component instances that make up the stack for a specific CloudEndpoint. Examples of such components can be CloudService, DataService, DataTransportService.
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/cloudEndpoint/stackComponentPids
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request","title":"Request","text":"
                      {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_2","title":"Responses","text":"
                      • 200 Ok Status
                        {\n    \"pids\": [\n        \"org.eclipse.kura.cloud.CloudService\",\n        \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n        \"org.eclipse.kura.data.DataService\"\n    ]\n}\n
                      • 404 if cloudConnectionFactoryPid or cloudEndpointPid are not found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#create-cloud-endpoint","title":"Create Cloud Endpoint","text":"
                      • Description: This method create a new CloudEndpoint. The CloudConnectionFactory will always create a CloudEndpoint instance with the given cloudEndpointPid and optionally other associated stack components, for example CloudService, DataService, DataTransportService.
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/cloudEndpoint
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_1","title":"Request","text":"
                      {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService-1\"\n}\n
                      • cloudEndpointPid: The cloudEndpointPid of the new instance that will be created. If the associated factory specifies the cloudEndpointPidRegex property, this parameter must match the provided regex.
                      • cloudConnectionFactoryPid: The cloudConnectionFactoryPid of the factory that should be used to create the new component.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_3","title":"Responses","text":"
                      • 204 Ok Status
                      • 400 malformed cloudConnectionFactoryPid or cloudEndpointPid.
                      • 404 Wrong cloudConnectionFactoryPid
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#create-publishersubscriber-instance","title":"Create Publisher/Subscriber instance","text":"
                      • Description: his method create a new CloudPublisher or a CloudSubscriber instance for a specific CloudEndpoint CloudEndpoint. The type of the instance depends from the type of the specified factory.
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/pubSub
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_2","title":"Request","text":"
                      {\n    \"pid\" : \"testPub\",\n    \"factoryPid\" : \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                      • pid: The PID of the new publisher/subscriber component. If the publisher/subscriber factory specifies the defaultPidRegex property, this parameter must match the provided regex.
                      • factoryPid: The factoryPid of the publisher/subscriber factory.
                      • cloudEndpointPid: The PID of the CloudEndpoint that the new publisher/subscriber component will be associated with. The associated CloudEndpoint must have been created by the CloudConnectionFactory with the cloudConnectionFactoryPid specified by the publisher/subscriber factory.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_4","title":"Responses","text":"
                      • 204 Ok Status
                      • 400 malformed pid, factoryPid or cloudEndpointPid
                      • 404 If factoryPid or cloudEndpointPid are wrong.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-configurations","title":"Get Configurations","text":"
                      • Description: This method retrieves the complete configuration, including the metatype description, of a cloud component instance. This method will return the configuration of CloudPublisher instances, CloudSubscriber instances, or the configuration of components that are part of a cloud stack, whose pids are returned by the cloudEndpoint/stackComponentPids method.
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/configurations
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_3","title":"Request","text":"
                      {\n    \"pids\" : [\"testPub\", \"org.eclipse.kura.cloud.CloudService\"]\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_5","title":"Responses","text":"

                      {\n    \"configs\": [\n        {\n            \"pid\": \"testPub\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"Application Id\",\n                        \"description\": \"The application id used to publish messages.\",\n                        \"id\": \"appId\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"W1\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Application Topic\",\n                        \"description\": \"Follows the application Id and specifies the rest of the publishing topic. Wildcards can be defined in the topic by specifing a $value in the field. The publisher will try to match \\\"value\\\" with a corresponding property in the received KuraMessage. If possible, the $value placeholder will be substituted with the real value specified in the KuraMessage received from the user application.\",\n                        \"id\": \"app.topic\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"A1/$assetName\",\n                        \"isRequired\": false\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"0\",\n                                \"value\": \"0\"\n                            },\n                            {\n                                \"label\": \"1\",\n                                \"value\": \"1\"\n                            }\n                        ],\n                        \"name\": \"Qos\",\n                        \"description\": \"The desired quality of service for the messages that have to be published. If Qos is 0, the message is delivered at most once, or it is not delivered at all. If Qos is set to 1, the message is always delivered at least once.\",\n                        \"id\": \"qos\",\n                        \"type\": \"INTEGER\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"0\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Retain\",\n                        \"description\": \"Default retaing flag for the published messages.\",\n                        \"id\": \"retain\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Data\",\n                                \"value\": \"data\"\n                            },\n                            {\n                                \"label\": \"Control\",\n                                \"value\": \"control\"\n                            }\n                        ],\n                        \"name\": \"Kind of Message\",\n                        \"description\": \"Type of message to be published.\",\n                        \"id\": \"message.type\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"data\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Priority\",\n                        \"description\": \"Message priority. Priority level 0 (highest) should be used sparingly and reserved for messages that should be sent with the minimum latency. Default is set to 7.\",\n                        \"id\": \"priority\",\n                        \"type\": \"INTEGER\",\n                        \"cardinality\": 0,\n                        \"min\": \"0\",\n                        \"defaultValue\": \"7\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"CloudPublisher\",\n                \"description\": \"The CloudPublisher allows to define publishing parameters and provide a simple endpoint where the applications can attach to publish their messages.\",\n                \"id\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\"\n            },\n            \"properties\": {\n                \"app.topic\": {\n                    \"value\": \"A1/$assetName\",\n                    \"type\": \"STRING\"\n                },\n                \"message.type\": {\n                    \"value\": \"data\",\n                    \"type\": \"STRING\"\n                },\n                \"qos\": {\n                    \"value\": 0,\n                    \"type\": \"INTEGER\"\n                },\n                \"appId\": {\n                    \"value\": \"W1\",\n                    \"type\": \"STRING\"\n                },\n                \"retain\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"priority\": {\n                    \"value\": 7,\n                    \"type\": \"INTEGER\"\n                },\n                \"service.factoryPid\": {\n                    \"value\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n                    \"type\": \"STRING\"\n                },\n                \"cloud.endpoint.service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"testPub\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.publisher.CloudPublisher-1699623980455-20\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Set display name as device name\",\n                                \"value\": \"device-name\"\n                            },\n                            {\n                                \"label\": \"Set display name from hostname\",\n                                \"value\": \"hostname\"\n                            },\n                            {\n                                \"label\": \"Custom\",\n                                \"value\": \"custom\"\n                            },\n                            {\n                                \"label\": \"Server defined\",\n                                \"value\": \"server\"\n                            }\n                        ],\n                        \"name\": \"Device Display-Name\",\n                        \"description\": \"Friendly name of the device. Device name is the common name of the device (eg: Reliagate 20-25, Raspberry Pi, etc.). Hostname will use the linux hostname utility.                  Custom allows for defining a unique string. Server defined relies on the remote management server to define a name.\",\n                        \"id\": \"device.display-name\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"device-name\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Device Custom-Name\",\n                        \"description\": \"Custom name for the device. This value is applied ONLY if device.display-name is set to \\\"Custom\\\"\",\n                        \"id\": \"device.custom-name\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"isRequired\": false\n                    },\n                    {\n                        \"name\": \"Topic Control-Prefix\",\n                        \"description\": \"Topic prefix for system and device management messages.\",\n                        \"id\": \"topic.control-prefix\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"$EDC\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Encode gzip\",\n                        \"description\": \"Compress message payloads before sending them to the remote server to reduce the network traffic.\",\n                        \"id\": \"encode.gzip\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": false\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Gps Lock\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on GPS lock event\",\n                        \"id\": \"republish.mqtt.birth.cert.on.gps.lock\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Modem Detect\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on modem detection event\",\n                        \"id\": \"republish.mqtt.birth.cert.on.modem.detect\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Tamper Event\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on a tamper event. This has effect only if a TamperDetectionService is available in the framework.\",\n                        \"id\": \"republish.mqtt.birth.cert.on.tamper.event\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Enable Default Subscriptions\",\n                        \"description\": \"Manages the default subscriptions to the gateway management MQTT topics. When disabled, the gateway will not be remotely manageable.\",\n                        \"id\": \"enable.default.subscriptions\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Kura Protobuf\",\n                                \"value\": \"kura-protobuf\"\n                            },\n                            {\n                                \"label\": \"Simple JSON\",\n                                \"value\": \"simple-json\"\n                            }\n                        ],\n                        \"name\": \"Payload Encoding\",\n                        \"description\": \"Specify the message payload encoding.\",\n                        \"id\": \"payload.encoding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"kura-protobuf\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"icon\": [\n                    {\n                        \"resource\": \"CloudService\",\n                        \"size\": 32\n                    }\n                ],\n                \"name\": \"CloudService\",\n                \"description\": \"The CloudService allows for setting a user friendly name for the current device. It also provides the option to compress message payloads to reduce network traffic.\",\n                \"id\": \"org.eclipse.kura.cloud.CloudService\"\n            },\n            \"properties\": {\n                \"topic.control-prefix\": {\n                    \"value\": \"$EDC\",\n                    \"type\": \"STRING\"\n                },\n                \"republish.mqtt.birth.cert.on.tamper.event\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"device.custom-name\": {\n                    \"value\": \"Intel UP\u00b2\",\n                    \"type\": \"STRING\"\n                },\n                \"device.display-name\": {\n                    \"value\": \"device-name\",\n                    \"type\": \"STRING\"\n                },\n                \"payload.encoding\": {\n                    \"value\": \"kura-protobuf\",\n                    \"type\": \"STRING\"\n                },\n                \"republish.mqtt.birth.cert.on.modem.detect\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"service.factoryPid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService-1699623980404-13\",\n                    \"type\": \"STRING\"\n                },\n                \"enable.default.subscriptions\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"republish.mqtt.birth.cert.on.gps.lock\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"encode.gzip\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"DataService.target\": {\n                    \"value\": \"(kura.service.pid=org.eclipse.kura.data.DataService)\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.cloud.service.factory.pid\": {\n                    \"value\": \"org.eclipse.kura.core.cloud.factory.DefaultCloudServiceFactory\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ]\n}\n
                      - 200 Status OK - 500 Internal Error

                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#connect-cloudendpoint","title":"Connect CloudEndpoint","text":"
                      • Description: This method allows to trigger the connection of the specified CloudEndpoint
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/cloudEndpoint/connect
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_4","title":"Request","text":"
                      {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_6","title":"Responses","text":"
                      • 204 Status OK
                      • 404 Wrong cloudEndpointPid
                      • 500 Internal Server Error in case of connection problems.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#disconnect-cloudendpoint","title":"Disconnect CloudEndpoint","text":"
                      • Description: This method allows to trigger the disconnection of the specified CloudEndpoint
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/cloudEndpoint/disconnect
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_5","title":"Request","text":"
                      {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_7","title":"Responses","text":"
                      • 204 Status OK
                      • 404 Wrong cloudEndpointPid
                      • 500 Internal Server Error in case of connection problems.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#check-cloudendpoint-connection-status","title":"Check CloudEndpoint connection status","text":"
                      • Description: This method allows to check the status of the connection of the specified CloudEndpoint
                      • Method: POST
                      • API PATH: services/cloudconnection/v1/cloudEndpoint/isConnected
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_6","title":"Request","text":"
                      {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_8","title":"Responses","text":"

                      {\n    \"connected\": false\n}\n
                      - 200 Status OK - 404 Wrong cloudEndpointPid - 500 Internal Server Error

                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#update-cloudendpoint-stack-component-configurations","title":"Update CloudEndpoint stack component configurations","text":"
                      • Description: This method allows to update the configuration of a cloud component instance. This method can be used to update the configuration of CloudPublisher instances, CloudSubscriber instances, or the configuration of components that are part of a cloud stack, whose pids are returned by the cloudEndpoint/stackComponentPids method.
                      • Method: PUT
                      • API PATH: services/cloudconnection/v1/configurations
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_7","title":"Request","text":"
                      • takeSnapshot set to true o false to specify if the ConfigurationService must save a snapshot with the updated configuration.
                      {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n            \"properties\": {\n                \"broker-url\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"mqtt://mqtt.eclipseprojects.io:1883\"\n                },\n                \"topic.context.account-name\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"account-name-testX2\"\n                },\n                \"username\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"username\"\n                },\n                \"password\": {\n                    \"type\": \"PASSWORD\",\n                    \"value\": \"Placeholder\"\n                },\n                \"client-id\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"\"\n                },\n                \"keep-alive\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 30\n                },\n                \"timeout\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 20\n                },\n                \"clean-session\": {\n                    \"type\": \"BOOLEAN\",\n                    \"value\": true\n                },\n                \"lwt.topic\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"$EDC/#account-name/#client-id/MQTT/LWT\"\n                },\n                \"lwt.payload\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"\"\n                },\n                \"lwt.qos\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 0\n                },\n                \"lwt.retain\": {\n                    \"type\": \"BOOLEAN\",\n                    \"value\": false\n                },\n                \"in-flight.persistence\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"memory\"\n                },\n                \"protocol-version\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 4\n                },\n                \"SslManagerService.target\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\"\n                }\n            }\n        }\n    ],\n    \"takeSnapshot\" : true\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_9","title":"Responses","text":"
                      • 204 Status OK
                      • 400 Bad request if pid is wrong or non existent.
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-cloudendpoint","title":"Delete CloudEndpoint","text":"
                      • Description: This method allow to delete a CloudEndpoint instance.
                      • Method: DELETE
                      • API PATH: services/cloudconnection/v1/cloudEndpoint
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_8","title":"Request","text":"
                      {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService-1\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_10","title":"Responses","text":"
                      • 204 Status OK
                      • 404 if cloudConnectionFactoryPid or cloudEndpointPid are not found
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-publishersubscriber-instance","title":"Delete Publisher/Subscriber instance","text":"
                      • Description: This method allows to delete a CloudPublisher or CloudSubscriber instance.
                      • Method: DELETE
                      • API PATH: services/cloudconnection/v1/pubSub
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_9","title":"Request","text":"
                      {\n    \"pid\" : \"testPub\"\n}\n
                      "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_11","title":"Responses","text":"
                      • 204 Status OK
                      • 404 Wrong cloudConnectionFactoryPid or cloudEndpointPid.
                      "},{"location":"references/rest-apis/rest-command-api/","title":"Rest Command v1 API","text":""},{"location":"references/rest-apis/rest-command-api/#execute-command","title":"Execute Command","text":"
                      • Method: POST
                      • API PATH: /services/command/v1/command/
                      "},{"location":"references/rest-apis/rest-command-api/#request-body","title":"Request Body","text":"
                      {\n    //Command to be executed on gateway\n    \"command\":\"printenv TextEnvVarName1\",\n\n    //Service Password for command Service\n    \"password\":\"s3curePassw0rd\",\n\n    //String base64 encoding of a zip file to transfer to gateway\n    \"zipBytes\": \"UEsDBAoACAAAAIyD1lYAA AAAAAAAAAAAAAAJACAAdGVzdGZpbGUxVVQNAAfprpRk6a6UZOmulGR1eAsAAQT1AQAABBQAAABQSwcIAAAAAAAAAAAAAAAAUEsBAgoDCgAIAAAAjIPWVgAAAAAAAAAAAAAAAAkAIAAAAAAAAAAAAKSBAAAAAHRlc3RmaWxlMVVUDQAH6a6UZOmulGTprpRkdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEAVwAAAFcAAAAAAA==\",\n\n    //Command argument String array\n    \"arguments\":[\"arg 1\"],\n\n    //Shell environment Pairs Map\n    \"environmentPairs\": \n    {\n        \"TextEnvVarName1\":\"TextEnvVarValue1\",\n        \"TextEnvVarName2\":\"TextEnvVarValue2\"\n    },\n    //Working directory of command to be executed\n    \"workingDirectory\":\"/tmp\",\n\n}\n
                      "},{"location":"references/rest-apis/rest-command-api/#responses","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"stdout\": \"Command error output is displayed in this field\",\n    \"stderr\": \"Command output is displayed in this field\",\n    \"exitCode\": 0,\n    \"isTimeOut\": false\n}\n
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-command-api/#execute-asynchronous-command","title":"Execute Asynchronous Command","text":"
                      • Method: POST
                      • API PATH: /services/command/v1/command/async
                      "},{"location":"references/rest-apis/rest-command-api/#request-body_1","title":"Request Body","text":"
                      {\n    //Command to be executed on gateway\n    \"command\":\"printenv TextEnvVarName1\",\n\n    //Service Password for command Service\n    \"password\":\"s3curePassw0rd\",\n\n    //String base64 encoding of a zip file to transfer to gateway\n    \"zipBytes\": \"UEsDBAoACAAAAIyD1lYAA AAAAAAAAAAAAAAJACAAdGVzdGZpbGUxVVQNAAfprpRk6a6UZOmulGR1eAsAAQT1AQAABBQAAABQSwcIAAAAAAAAAAAAAAAAUEsBAgoDCgAIAAAAjIPWVgAAAAAAAAAAAAAAAAkAIAAAAAAAAAAAAKSBAAAAAHRlc3RmaWxlMVVUDQAH6a6UZOmulGTprpRkdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEAVwAAAFcAAAAAAA==\",\n\n    //Command argument String array\n    \"arguments\":[\"arg 1\"],\n\n    //Shell environment Pairs Map\n    \"environmentPairs\": \n    {\n        \"TextEnvVarName1\":\"TextEnvVarValue1\",\n        \"TextEnvVarName2\":\"TextEnvVarValue2\"\n    },\n    //Working directory of command to be executed\n    \"workingDirectory\":\"/tmp\",\n\n}\n
                      "},{"location":"references/rest-apis/rest-command-api/#responses_1","title":"Responses","text":"
                      • 202 Accepted
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error

                      Note

                      Use the following command to retrieve the base64 representation of a zip file. base64 -i <filename.zip>

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/","title":"Configuration V1 REST APIs","text":"

                      This page describes the configuration V1 rest APIs.

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#rest-apis","title":"REST APIs","text":"

                      The Configuration Service REST APIs are exposed by the org.eclipse.kura.rest.configuration bundle, providing the following REST APIs under the /configuration/v1 path.

                      Method Path Allowed roles Encoding Request parameters Description GET /factoryComponents configuration JSON None The method lists all the FactoryComponents Pids tracked by the ConfigurationService POST /factoryComponents configuration JSON FactoryComponentConfiguration object Creates a new ConfigurableComponent instance by creating a new configuration from a Configuration Admin factory. The FactoryComponentConfiguration object passed as request parameter will provide all the information needed to generate the instance. It links the factory Pid to be used, the target instance Pid, the properties to be used when creating the instance, and if the request should be persisted with a snapshot DELETE /factoryComponents/{pid}?takeSnapshot={takeSnapshot} configuration JSON pid = A String representing the pid of the instance generated by a Factory Component that needs to deleted; takeSnapshot = an optional (default false) boolean to specify if a new snapshot needs to be created after the delete operation For the specified Pid and optional takeSnapshot query parameter, the ConfigurationService instance will delete the corresponding ConfigurableComponent instance GET /configurableComponents configuration JSON None Lists the tracked configurable component Pids GET /configurableComponents/configurations configuration JSON None Lists all the component configurations of all the ConfigurableComponents tracked by the ConfigurationService GET /configurableComponents/configurations/byFilter/{filter} configuration JSON filter = A String representing an OSGi filter. Lists the component configurations of all the ConfigurableComponents tracked by the ConfigurationService that match the filter specified GET /configurableComponents/configurations/byPid/{pid} configuration JSON pid = A String representing the pid of a configurable component instance Provides the ComponentConfiguration of the ConfigurableComponent matching the specified Pid GET /configurableComponents/configurations/byPid/{pid}/_default configuration JSON pid = A String representing the pid of a configurable component instance Provides the default Component Configuration for the component identified by the specified Pid POST /configurableComponents/configurations/byPid/{pid}/_update configuration JSON pid = A String representing the pid of a configurable component instance; ComponentConfigurationUpdateRequest = the updated configuration provided in the request body Allows to update the component configuration identified by the provided PID POST /configurableComponents/configurations/_update configuration JSON componentConfigurations = the list of updated configurations provided in the request body Allows to update the configuration of multiple configurable components GET /snapshots configuration JSON None Lists all the available snapshot IDs managed by the framework GET /snapshots/{id} configuration JSON id = the snapshot Id Returns the content of a given snapshot tracked by the framework POST /snapshots/_write configuration JSON None Triggers the framework to take and persist a snapshot POST /snapshots/_rollback configuration JSON None Rollbacks the framework to the last saved snapshot if available. POST /snapshots/{id}/_rollback configuration JSON id = the snapshot Id Rollbacks the framework to the snapshot identified by the provided ID POST /snapshots/_upload configuration Consumes: XML Framework snapshot in XML form provided in the request body Uploads a snapshot. The framework will update the component(s) configuration accordingly to the configurations received"},{"location":"references/rest-apis/rest-configuration-service-v1/#get-all-the-factory-components-pids","title":"Get all the factory components pids","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents

                      Response:

                      [\n    \"org.eclipse.kura.wire.Conditional\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.publisher.RawMqttPublisher\",\n    \"org.eclipse.kura.misc.cloudcat.CloudCat\",\n    \"org.eclipse.kura.core.db.H2DbServer\",\n    \"org.eclipse.kura.wire.Fifo\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\",\n    \"org.eclipse.kura.core.keystore.FilesystemKeystoreServiceImpl\",\n    \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n    \"org.eclipse.kura.core.db.H2DbService\",\n    \"org.eclipse.kura.wire.CloudSubscriber\",\n    \"org.eclipse.kura.wire.RegexFilter\",\n    \"org.eclipse.kura.wire.Logger\",\n    \"org.eclipse.kura.wire.Timer\",\n    \"com.eurotech.framework.log.manager.LogManager\",\n    \"com.eurotech.framework.log.journald.wire.JournaldWireComponent\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.subscriber.RawMqttSubscriber\",\n    \"org.eclipse.kura.cloud.subscriber.CloudSubscriber\",\n    \"org.eclipse.kura.ssl.SslManagerService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"com.eurotech.framework.log.journald.JournaldLogReader\",\n    \"org.eclipse.kura.provisioning.ProvisioningService\",\n    \"org.eclipse.kura.wire.CloudPublisher\",\n    \"org.eclipse.kura.wire.H2DbWireRecordFilter\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"org.eclipse.kura.data.DataService\",\n    \"org.eclipse.kura.wire.H2DbWireRecordStore\",\n    \"org.eclipse.kura.wire.Join\",\n    \"com.eurotech.framework.log.publisher.LogPublisher\"\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#create-component-from-factory","title":"Create component from factory","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents

                      Request body:

                      {\n    \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n    \"pid\": \"myH2DbServer\",\n    \"properties\" : [],\n    \"takeSnapshot\" : false\n}\n

                      The request must be provided with the following elements:

                      • factoryPid: the factory used to generate the new instance
                      • pid: the new instance process id
                      • properties: eventual properties that will be used to instantiate the new instance and will override the defaults. A list of string, object pair is needed and can be optionally empty.
                      • takeSnapshot: specifies if after the creation of a new component a snapshot needs to be taken.
                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#delete-a-component-and-optionally-take-a-snapshot","title":"Delete a component and optionally take a snapshot","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents/{pid}?takeSnapshot={takeSnapshot}

                      • takeSnapshot: can be either true or false. If set to true, after the deletion, the framework will take a snapshot.
                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-the-tracked-configurable-component-pids","title":"List the tracked configurable component Pids","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents

                      Response:

                      [\n    \"org.eclipse.kura.clock.ClockService\",\n    \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n    \"org.eclipse.kura.position.PositionService\",\n    \"com.eurotech.framework.internal.ansible.provider.AnsibleServiceImpl\",\n    \"org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl\",\n    \"com.eurotech.framework.internal.ansible.cloud.AnsibleActivityHandler\",\n    \"default.diagnostic.publisher\",\n    \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n    \"com.eurotech.framework.internal.fail2ban.Fail2BanConfigurator\",\n    \"default.log.publisher\",\n    \"org.eclipse.kura.wire.graph.WireGraphService\",\n    \"com.eurotech.framework.internal.floodingprotection.FloodingProtectionConfigurator\",\n    \"org.eclipse.kura.ssl.SslManagerService\",\n    \"org.eclipse.kura.http.server.manager.HttpService\",\n    \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n    \"default.ping.publisher\",\n    \"org.eclipse.kura.db.H2DbService\",\n    \"com.eurotech.framework.diagnostics.DiagnosticsService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"org.eclipse.kura.deployment.agent\",\n    \"DMKeystore\",\n    \"LogReaderJournald\",\n    \"default.alert.publisher\",\n    \"org.eclipse.kura.core.deployment.CloudDeploymentHandlerV2\",\n    \"HttpsKeystore\",\n    \"com.eurotech.framework.security.aide.AideTamperDetectionServiceConfigurator\",\n    \"org.eclipse.kura.provisioning.ProvisioningService\",\n    \"LogManagerAuth\",\n    \"com.eurotech.framework.security.journald.fss.FssTamperDetectionServiceConfigurator\",\n    \"org.eclipse.kura.watchdog.WatchdogService\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"LogManagerActivity\",\n    \"org.eclipse.kura.data.DataService\",\n    \"LogManagerDefault\",\n    \"org.eclipse.kura.web.Console\",\n    \"SSLKeystore\",\n    \"com.eurotech.framework.net.vpn.client.VpnClient\",\n    \"org.eclipse.kura.internal.rest.provider.RestService\",\n    \"com.eurotech.framework.reboot.RebootService\"\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#lists-all-the-component-configurations-of-all-the-configurablecomponents","title":"Lists all the component configurations of all the ConfigurableComponents","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations

                      Response:

                      [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"definition\": {\n            \"ad\": [\n                {\n                    \"option\": [],\n                    \"name\": \"enabled\",\n                    \"description\": \"Whether or not to enable the ClockService\",\n                    \"id\": \"enabled\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.set.hwclock\",\n                    \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                    \"id\": \"clock.set.hwclock\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [\n                        {\n                            \"label\": \"java-ntp\",\n                            \"value\": \"java-ntp\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"ntpd\",\n                            \"value\": \"ntpd\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"chrony-advanced\",\n                            \"value\": \"chrony-advanced\",\n                            \"otherAttributes\": {}\n                        }\n                    ],\n                    \"name\": \"clock.provider\",\n                    \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                    \"id\": \"clock.provider\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"java-ntp\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.host\",\n                    \"description\": \"The hostname that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.host\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"0.pool.ntp.org\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.port\",\n                    \"description\": \"The port number that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.port\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"max\": \"65535\",\n                    \"_default\": \"123\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.timeout\",\n                    \"description\": \"The NTP timeout in milliseconds\",\n                    \"id\": \"clock.ntp.timeout\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1000\",\n                    \"_default\": \"10000\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.max-retry\",\n                    \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                    \"id\": \"clock.ntp.max-retry\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"0\",\n                    \"_default\": \"0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.retry.interval\",\n                    \"description\": \"When sync fails, interval in seconds between each retry.\",\n                    \"id\": \"clock.ntp.retry.interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"_default\": \"5\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.refresh-interval\",\n                    \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                    \"id\": \"clock.ntp.refresh-interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"_default\": \"3600\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"RTC File Name\",\n                    \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                    \"id\": \"rtc.filename\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"/dev/rtc0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"Chrony Configuration\",\n                    \"description\": \"Chrony configuration file.|TextArea\",\n                    \"id\": \"chrony.advanced.config\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"required\": false,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"icon\": [\n                {\n                    \"resource\": \"ClockService\",\n                    \"size\": 32,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"name\": \"ClockService\",\n            \"description\": \"ClockService Configuration\",\n            \"id\": \"org.eclipse.kura.clock.ClockService\",\n            \"otherAttributes\": {}\n        },\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": false\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"chrony.advanced.config\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"\"\n            }\n        }\n    },\n  ...\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-the-configurations-of-all-the-configurablecomponents-that-match-a-filter","title":"List the configurations of all the ConfigurableComponents that match a filter","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byFilter/(service.pid=org.eclipse.kura.clock.ClockService)

                      Response:

                      [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"definition\": {\n            \"ad\": [\n                {\n                    \"option\": [],\n                    \"name\": \"enabled\",\n                    \"description\": \"Whether or not to enable the ClockService\",\n                    \"id\": \"enabled\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.set.hwclock\",\n                    \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                    \"id\": \"clock.set.hwclock\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [\n                        {\n                            \"label\": \"java-ntp\",\n                            \"value\": \"java-ntp\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"ntpd\",\n                            \"value\": \"ntpd\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"chrony-advanced\",\n                            \"value\": \"chrony-advanced\",\n                            \"otherAttributes\": {}\n                        }\n                    ],\n                    \"name\": \"clock.provider\",\n                    \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                    \"id\": \"clock.provider\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"java-ntp\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.host\",\n                    \"description\": \"The hostname that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.host\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"0.pool.ntp.org\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.port\",\n                    \"description\": \"The port number that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.port\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"max\": \"65535\",\n                    \"_default\": \"123\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.timeout\",\n                    \"description\": \"The NTP timeout in milliseconds\",\n                    \"id\": \"clock.ntp.timeout\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1000\",\n                    \"_default\": \"10000\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.max-retry\",\n                    \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                    \"id\": \"clock.ntp.max-retry\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"0\",\n                    \"_default\": \"0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.retry.interval\",\n                    \"description\": \"When sync fails, interval in seconds between each retry.\",\n                    \"id\": \"clock.ntp.retry.interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"_default\": \"5\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.refresh-interval\",\n                    \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                    \"id\": \"clock.ntp.refresh-interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"_default\": \"3600\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"RTC File Name\",\n                    \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                    \"id\": \"rtc.filename\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"/dev/rtc0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"Chrony Configuration\",\n                    \"description\": \"Chrony configuration file.|TextArea\",\n                    \"id\": \"chrony.advanced.config\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"required\": false,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"icon\": [\n                {\n                    \"resource\": \"ClockService\",\n                    \"size\": 32,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"name\": \"ClockService\",\n            \"description\": \"ClockService Configuration\",\n            \"id\": \"org.eclipse.kura.clock.ClockService\",\n            \"otherAttributes\": {}\n        },\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": false\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"chrony.advanced.config\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"\"\n            }\n        }\n    }\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-configuration-of-the-configurablecomponent-matching-a-pid","title":"Get the configuration of the ConfigurableComponent matching a pid","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService

                      Response:

                      {\n    \"pid\": \"org.eclipse.kura.clock.ClockService\",\n    \"definition\": {\n        \"ad\": [\n            {\n                \"option\": [],\n                \"name\": \"enabled\",\n                \"description\": \"Whether or not to enable the ClockService\",\n                \"id\": \"enabled\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.set.hwclock\",\n                \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                \"id\": \"clock.set.hwclock\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [\n                    {\n                        \"label\": \"java-ntp\",\n                        \"value\": \"java-ntp\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"ntpd\",\n                        \"value\": \"ntpd\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"chrony-advanced\",\n                        \"value\": \"chrony-advanced\",\n                        \"otherAttributes\": {}\n                    }\n                ],\n                \"name\": \"clock.provider\",\n                \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                \"id\": \"clock.provider\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"java-ntp\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.host\",\n                \"description\": \"The hostname that provides the system time via NTP\",\n                \"id\": \"clock.ntp.host\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"0.pool.ntp.org\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.port\",\n                \"description\": \"The port number that provides the system time via NTP\",\n                \"id\": \"clock.ntp.port\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"max\": \"65535\",\n                \"_default\": \"123\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.timeout\",\n                \"description\": \"The NTP timeout in milliseconds\",\n                \"id\": \"clock.ntp.timeout\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1000\",\n                \"_default\": \"10000\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.max-retry\",\n                \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                \"id\": \"clock.ntp.max-retry\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"0\",\n                \"_default\": \"0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.retry.interval\",\n                \"description\": \"When sync fails, interval in seconds between each retry.\",\n                \"id\": \"clock.ntp.retry.interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"_default\": \"5\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.refresh-interval\",\n                \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                \"id\": \"clock.ntp.refresh-interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"_default\": \"3600\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"RTC File Name\",\n                \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                \"id\": \"rtc.filename\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"/dev/rtc0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"Chrony Configuration\",\n                \"description\": \"Chrony configuration file.|TextArea\",\n                \"id\": \"chrony.advanced.config\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"required\": false,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"icon\": [\n            {\n                \"resource\": \"ClockService\",\n                \"size\": 32,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"name\": \"ClockService\",\n        \"description\": \"ClockService Configuration\",\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"otherAttributes\": {}\n    },\n    \"properties\": {\n        \"clock.ntp.host\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.ntp.max-retry\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 0\n        },\n        \"clock.set.hwclock\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"clock.ntp.timeout\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 10000\n        },\n        \"enabled\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": false\n        },\n        \"clock.ntp.retry.interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 5\n        },\n        \"kura.service.pid\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"service.pid\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"clock.ntp.port\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 123\n        },\n        \"clock.provider\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"java-ntp\"\n        },\n        \"clock.ntp.refresh-interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 3600\n        },\n        \"rtc.filename\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"/dev/rtc1\"\n        },\n        \"chrony.advanced.config\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"\"\n        }\n    }\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-default-configuration-of-the-configurablecomponent-matching-a-pid","title":"Get the default configuration of the ConfigurableComponent matching a pid","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService/_default

                      Response:

                      {\n    \"pid\": \"org.eclipse.kura.clock.ClockService\",\n    \"definition\": {\n        \"ad\": [\n            {\n                \"option\": [],\n                \"name\": \"enabled\",\n                \"description\": \"Whether or not to enable the ClockService\",\n                \"id\": \"enabled\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.set.hwclock\",\n                \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                \"id\": \"clock.set.hwclock\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [\n                    {\n                        \"label\": \"java-ntp\",\n                        \"value\": \"java-ntp\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"ntpd\",\n                        \"value\": \"ntpd\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"chrony-advanced\",\n                        \"value\": \"chrony-advanced\",\n                        \"otherAttributes\": {}\n                    }\n                ],\n                \"name\": \"clock.provider\",\n                \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                \"id\": \"clock.provider\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"java-ntp\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.host\",\n                \"description\": \"The hostname that provides the system time via NTP\",\n                \"id\": \"clock.ntp.host\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"0.pool.ntp.org\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.port\",\n                \"description\": \"The port number that provides the system time via NTP\",\n                \"id\": \"clock.ntp.port\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"max\": \"65535\",\n                \"_default\": \"123\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.timeout\",\n                \"description\": \"The NTP timeout in milliseconds\",\n                \"id\": \"clock.ntp.timeout\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1000\",\n                \"_default\": \"10000\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.max-retry\",\n                \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                \"id\": \"clock.ntp.max-retry\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"0\",\n                \"_default\": \"0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.retry.interval\",\n                \"description\": \"When sync fails, interval in seconds between each retry.\",\n                \"id\": \"clock.ntp.retry.interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"_default\": \"5\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.refresh-interval\",\n                \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                \"id\": \"clock.ntp.refresh-interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"_default\": \"3600\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"RTC File Name\",\n                \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                \"id\": \"rtc.filename\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"/dev/rtc0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"Chrony Configuration\",\n                \"description\": \"Chrony configuration file.|TextArea\",\n                \"id\": \"chrony.advanced.config\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"required\": false,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"icon\": [\n            {\n                \"resource\": \"ClockService\",\n                \"size\": 32,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"name\": \"ClockService\",\n        \"description\": \"ClockService Configuration\",\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"otherAttributes\": {}\n    },\n    \"properties\": {\n        \"clock.ntp.host\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.provider\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"java-ntp\"\n        },\n        \"clock.ntp.port\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 123\n        },\n        \"clock.ntp.max-retry\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 0\n        },\n        \"clock.ntp.refresh-interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 3600\n        },\n        \"rtc.filename\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"/dev/rtc0\"\n        },\n        \"clock.set.hwclock\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"enabled\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"clock.ntp.timeout\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 10000\n        },\n        \"clock.ntp.retry.interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 5\n        }\n    }\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#update-the-component-configuration-identified-matching-a-pid","title":"Update the component configuration identified matching a pid","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService/_update

                      Request body:

                      {\n    \"takeSnapshot\":true,\n    \"componentConfigurationRequest\": {\n        \"properties\": {\n            \"enabled\": {\n                \"type\": \"boolean\",\n                \"value\": false,\n                \"array\": false\n            }\n        }\n    }\n}\n

                      Warning

                      Every service may need a different set of parameters and combination of them to reach the desired result. For the NetworkAdminService, for example, the following configuration is required to disable a specific network interface (enp2s0) and prevent re-enabling at reboot. Please verify offline the REST APIs executed and the different deploying scenarios before distributing updates to the fleet of devices.

                      {\n    \"takeSnapshot\":true,\n    \"componentConfigurationRequest\": {\n        \"properties\": {\n            \"net.interface.enp2s0.config.ip4.status\": {\n                \"type\": \"string\",\n                \"value\": \"netIPv4StatusDisabled\",\n                \"array\": false\n            },\n            \"net.interface.enp2s0.config.autoconnect\": {\n                \"type\": \"boolean\",\n                \"value\": false,\n                \"array\": false\n            }\n        }\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#update-the-configuration-of-multiple-configurable-components","title":"Update the configuration of multiple configurable components","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/_update

                      Request body:

                      {\n    \"takeSnapshot\": true,\n    \"componentConfigurations\": [\n        {\n            \"pid\": \"org.eclipse.kura.clock.ClockService\",\n            \"properties\": {\n                \"enabled\": {\n                    \"type\": \"boolean\",\n                    \"value\": false,\n                    \"array\": false\n                }\n            }\n        }\n    ]\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-all-the-available-snapshot-ids","title":"List all the available snapshot IDs","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots

                      Response:

                      [\n    0,\n    1630930775789,\n    1630930776355,\n    1630930776839,\n    1630930797402,\n    1630930805305\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-content-of-a-given-snapshot","title":"Get the content of a given snapshot","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/{id}

                      Response:

                      [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            }\n        }\n    },\n  ...\n]\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#trigger-the-framework-to-take-and-persist-a-snapshot","title":"Trigger the framework to take and persist a snapshot","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_write

                      Response:

                      1631095409516\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#rollbacks-to-the-snapshot-identified-by-the-provided-id","title":"Rollbacks to the snapshot identified by the provided ID","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_rollback Response:

                      1631093011618\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v1/#upload-a-snapshot-as-xml","title":"Upload a snapshot as XML","text":"

                      Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_upload

                      Request body:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?><esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.clock.ClockService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.host\" type=\"String\">\n                <esf:value>0.pool.ntp.org</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.port\" type=\"Integer\">\n                <esf:value>123</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.provider\" type=\"String\">\n                <esf:value>java-ntp</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.max-retry\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.refresh-interval\" type=\"Integer\">\n                <esf:value>3600</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"rtc.filename\" type=\"String\">\n                <esf:value>/dev/rtc1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.set.hwclock\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.timeout\" type=\"Integer\">\n                <esf:value>10000</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.retry.interval\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"kura.service.pid\" type=\"String\">\n                <esf:value>org.eclipse.kura.clock.ClockService</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"service.pid\" type=\"String\">\n                <esf:value>org.eclipse.kura.clock.ClockService</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v2/","title":"Configuration V2 REST APIs and CONF-V2 Request Handler","text":"

                      This page describes the CONF-V2 request handler and configuration/v2 rest APIs. Accessing the REST APIs requires to use an identity with the rest.configuration permission assigned.

                      • Request definitions
                        • GET/snapshots
                        • GET/factoryComponents
                        • POST/factoryComponents
                        • DEL/factoryComponents/byPid
                        • GET/factoryComponents/ocd
                        • POST/factoryComponents/ocd/byFactoryPid
                        • GET/configurableComponents
                        • GET/configurableComponents/pidsWithFactory
                        • GET/configurableComponents/configurations
                        • POST/configurableComponents/configurations/byPid
                        • POST/configurableComponents/configurations/byPid/_default
                        • PUT/configurableComponents/configurations/_update
                        • EXEC/snapshots/_write
                        • EXEC/snapshots/_rollback
                        • EXEC/snapshots/byId/_rollback
                      • JSON definitions
                        • PidAndFactoryPidSet
                        • SnapshotIdSet
                        • PropertyType
                        • ConfigurationProperty
                        • ConfigurationProperties
                        • Option
                        • AttributeDefinition
                        • ObjectClassDefinition
                        • ComponentConfiguration
                        • ComponentConfigurationList
                        • CreateFactoryComponentConfigurationsRequest
                        • UpdateComponentConfigurationRequest
                        • DeleteFactoryComponentConfigurationsRequest
                        • PidSet
                        • SnaphsotId
                        • GenericFailureReport
                        • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-configuration-service-v2/#getsnapshots","title":"GET/snapshots","text":"
                      • REST API path : /services/configuration/v2/snapshots
                      • description : Returns the ids of the snapshots currently stored on the device.
                      • responses :
                        • 200
                        • description : The snapshot id set.
                        • response body :
                          • SnapshotIdSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#getfactorycomponents","title":"GET/factoryComponents","text":"
                      • REST API path : /services/configuration/v2/factoryComponents
                      • description : Returns the ids of the component factories available on the device.
                      • responses :
                        • 200
                        • description : The factory pid set.
                        • response body :
                          • PidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#postfactorycomponents","title":"POST/factoryComponents","text":"
                      • REST API path : /services/configuration/v2/factoryComponents
                      • description : This is a batch request that allows to create one or more factory component instances and optionally create a new snapshot.
                      • request body :
                        • CreateFactoryComponentConfigurationsRequest
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: create:$pid for component creation operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#delfactorycomponentsbypid","title":"DEL/factoryComponents/byPid","text":"
                      • REST API path : /services/configuration/v2/factoryComponents/byPid
                      • description : This is a batch request that allows to delete one or more factory component instances and optionally create a new snapshot.
                      • request body :
                        • DeleteFactoryComponentConfigurationsRequest
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for component delete operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#getfactorycomponentsocd","title":"GET/factoryComponents/ocd","text":"
                      • REST API path : /services/configuration/v2/factoryComponents/ocd
                      • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to all available factories.
                      • responses :
                        • 200
                        • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present.
                        • response body :
                          • ComponentConfigurationList
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#postfactorycomponentsocdbyfactorypid","title":"POST/factoryComponents/ocd/byFactoryPid","text":"
                      • REST API path : /services/configuration/v2/factoryComponents/ocd/byFactoryPid
                      • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to a user selected set of factories.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present. If the OCD for a given factory pid cannot be found, it will not be included in the result.
                        • response body :
                          • ComponentConfigurationList
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponents","title":"GET/configurableComponents","text":"
                      • REST API path : /services/configuration/v2/configurableComponents
                      • description : Returns the list of the pids available on the system.
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • response body :
                          • PidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponentspidswithfactory","title":"GET/configurableComponents/pidsWithFactory","text":"
                      • REST API path : /services/configuration/v2/configurableComponents/pidsWithFactory
                      • description : Returns the list of the pids available on the system, reporting also the factory pid where applicable.
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • response body :
                          • PidAndFactoryPidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponentsconfigurations","title":"GET/configurableComponents/configurations","text":"
                      • REST API path : /services/configuration/v2/configurableComponents/configurations
                      • description : Returns all of component configurations available on the system. This request will return the pid, ocd and properties.
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • response body :
                          • ComponentConfigurationList
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#postconfigurablecomponentsconfigurationsbypid","title":"POST/configurableComponents/configurations/byPid","text":"
                      • REST API path : /services/configuration/v2/configurableComponents/configurations/byPid
                      • description : Returns a user selected set of configurations. This request will return the pid, ocd and properties.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The request succeeded. If the configuration for a given pid cannot be found, it will not be included in the result.
                        • response body :
                          • ComponentConfigurationList
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#postconfigurablecomponentsconfigurationsbypid_default","title":"POST/configurableComponents/configurations/byPid/_default","text":"
                      • REST API path : /services/configuration/v2/configurableComponents/configurations/byPid/_default
                      • description : Returns the default configuration for a given set of component pids. The default configurations are generated basing on component definition only, user applied modifications will not be taken into account. This request will return the pid, ocd and properties.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The request succeeded. If the configuration for a given pid cannot be found, it will not be included in the result.
                        • response body :
                          • ComponentConfigurationList
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#putconfigurablecomponentsconfigurations_update","title":"PUT/configurableComponents/configurations/_update","text":"
                      • REST API path : /services/configuration/v2/configurableComponents/configurations/_update
                      • description : Updates a given set of component configurations. This request can be also used to apply a configuration snapshot.
                      • request body :
                        • UpdateComponentConfigurationRequest
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid for component update operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshots_write","title":"EXEC/snapshots/_write","text":"
                      • REST API path : /services/configuration/v2/snapshots/_write
                      • description : Requests the device to create a new snasphot based on the current defice configuration. If this request is used through REST API, the POST method must be used.
                      • responses :
                        • 200
                        • description : The request succeeded. The result is the identifier of the new snsapsot
                        • response body :
                          • SnaphsotId
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshots_rollback","title":"EXEC/snapshots/_rollback","text":"
                      • REST API path : /services/configuration/v2/snapshots/_rollback
                      • description : Rollbacks the framework to the last saved snapshot if available. If this request is used through REST API, the POST method must be used.
                      • responses :
                        • 200
                        • description : The request succeeded. The result contains the id of the snapshot used for rollback.
                        • response body :
                          • SnaphsotId
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshotsbyid_rollback","title":"EXEC/snapshots/byId/_rollback","text":"
                      • REST API path : /services/configuration/v2/snapshots/byId/_rollback
                      • description : Performs a rollback to the snapshot id specified by the user.
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-configuration-service-v2/#pidandfactorypidset","title":"PidAndFactoryPidSet","text":"

                      Represents a set of pids with the corresponding factory pid. Properties:

                      • components: array The set of pids and factory pids

                        • array elements: object The pid and factory pid Properties:

                        • pid: string The component pid.

                        • factoryPid: string
                          • optional Can be missing if the described component is not a factory instance. The factory pid
                      {\n  \"components\": [\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbService\",\n      \"pid\": \"org.eclipse.kura.db.H2DbService\"\n    },\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n      \"pid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\"\n    },\n    {\n      \"pid\": \"org.eclipse.kura.web.Console\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#snapshotidset","title":"SnapshotIdSet","text":"

                      An object decribing a set of configuration snapshot ids Properties:

                      • ids: array The set of snapshot ids.
                        • array elements: number A snapshot id.
                      {\n  \"ids\": [\n    0,\n    1638438049921,\n    1638438146960,\n    1638439710944,\n    1638439717931,\n    1638439734077,\n    1638439767252,\n    1638521986953,\n    1638521993692,\n    1638522572822\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#propertytype","title":"PropertyType","text":"

                      A string that describes the type of a configuration property. * Possible values * STRING * LONG * DOUBLE * FLOAT * INTEGER * BYTE * CHAR * BOOLEAN * SHORT * PASSWORD

                      \"STRING\"\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#configurationproperty","title":"ConfigurationProperty","text":"

                      An object describing a configuration property. Properties:

                      • type: string (enumerated)
                        • PropertyType
                      • value: variant
                      • optional In requests, this field can be omitted or set to null to assign the null value to a non required configuration property. Describes the property value. The value type depends on the type property. Variants:

                        • number If type is LONG, DOUBLE, FLOAT, INTEGER, BYTE or SHORT and the property does not represent an array.
                        • string If type is STRING, PASSWORD or CHAR and the property is not an array. In case of CHAR type, the value must have a length of 1.
                        • bool If type is BOOLEAN and the property is not an array
                        • array If type is LONG, DOUBLE, FLOAT, INTEGER, BYTE or SHORT and the property represents an array.
                          • array elements: number The property values as numbers.
                        • array If type is STRING, PASSWORD or CHAR and the property is an array.
                          • array elements: string The property values as strings. In case of CHAR type, the values must have a length of 1.
                        • array If type is BOOLEAN and the property is an array
                          • array elements: bool The property values as booleans.

                      {\n  \"type\": \"STRING\",\n  \"value\": \"foo\"\n}\n
                      {\n  \"type\": \"STRING\",\n  \"value\": [\n    \"foo\",\n    \"bar\"\n  ]\n}\n
                      {\n  \"type\": \"INTEGER\",\n  \"value\": 12\n}\n
                      {\n  \"type\": \"LONG\",\n  \"value\": [\n    1,\n    2,\n    3,\n    4\n  ]\n}\n
                      {\n  \"type\": \"PASSWORD\",\n  \"value\": \"myPassword\"\n}\n
                      {\n  \"type\": \"PASSWORD\",\n  \"value\": [\n    \"my\",\n    \"password\",\n    \"array\"\n  ]\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#configurationproperties","title":"ConfigurationProperties","text":"

                      An object representing a set of configuration properties. The members of this object represent configuration property, the member names represent the configuration property ids. This object can have a variable number of members. Properties:

                      • propertyName: object
                        • ConfigurationProperty
                      {\n  \"KeystoreService.target\": {\n    \"type\": \"STRING\",\n    \"value\": \"(kura.service.pid=HttpsKeystore)\"\n  },\n  \"https.client.auth.ports\": {\n    \"type\": \"INTEGER\",\n    \"value\": [\n      4443\n    ]\n  },\n  \"https.client.revocation.soft.fail\": {\n    \"type\": \"BOOLEAN\",\n    \"value\": false\n  },\n  \"https.ports\": {\n    \"type\": \"INTEGER\",\n    \"value\": [\n      443\n    ]\n  },\n  \"https.revocation.check.enabled\": {\n    \"type\": \"BOOLEAN\",\n    \"value\": false\n  },\n  \"kura.service.pid\": {\n    \"type\": \"STRING\",\n    \"value\": \"org.eclipse.kura.http.server.manager.HttpService\"\n  },\n  \"service.pid\": {\n    \"type\": \"STRING\",\n    \"value\": \"org.eclipse.kura.http.server.manager.HttpService\"\n  },\n  \"ssl.revocation.mode\": {\n    \"type\": \"STRING\",\n    \"value\": \"PREFER_OCSP\"\n  }\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#option","title":"Option","text":"

                      An object describing an allowed element for a multi choiche field. Properties:

                      • label: string
                      • optional This parameter may not be specified by component configuration. An user friendly label for the option.
                      • value: string The option value encoded as a string.

                      {\n  \"label\": \"Value 1\",\n  \"value\": \"1\"\n}\n
                      {\n  \"value\": \"foo\"\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#attributedefinition","title":"AttributeDefinition","text":"

                      A descriptor of a configuration property. Properties:

                      • id: string The id of the attribute definition. This field corresponds to the configuration property name.
                      • type: string (enumerated)
                        • PropertyType
                      • name: string
                      • optional This parameter may not be specified by component configuration. An user friendly name for the property.
                      • description: string
                      • optional This parameter may not be specified by component configuration. An user friendly description for the property.
                      • cardinality: number An integer describing the property cardinality. If the value is 0, then the property is a singleton value (not an array), if it is > 0, then the configuration property is an array and this property specifies the maximum allowed array length.
                      • min: string
                      • optional This parameter may not be specified by component configuration. If not specified, the property does not have a minimum value. Specifies the minimum value for this property as a string.
                      • max: string
                      • optional This parameter may not be specified by component configuration. If not specified, the property does not have a maximum value. Specifies the maximum value for this property as a string.
                      • isRequired: bool Specifies whether the configuration parameter is required or not.
                      • defaultValue: string
                      • optional This parameter may not be specified by component configuration. Specifies the default value for this property as a string.
                      • option: array
                      • optional If specified, describes a set of allowed values for the configuration property. The allowed values for this configuration properties
                        • array elements: object
                        • Option

                      {\n  \"defaultValue\": \"false\",\n  \"description\": \"Specifies whether the DB server is enabled or not.\",\n  \"id\": \"db.server.enabled\",\n  \"isRequired\": true,\n  \"name\": \"db.server.enabled\",\n  \"type\": \"BOOLEAN\"\n}\n
                      {\n  \"defaultValue\": \"TCP\",\n  \"description\": \"Specifies the server type, see http://www.h2database.com/javadoc/org/h2/tools/Server.html for more details.\",\n  \"id\": \"db.server.type\",\n  \"isRequired\": true,\n  \"name\": \"db.server.type\",\n  \"option\": [\n    {\n      \"label\": \"WEB\",\n      \"value\": \"WEB\"\n    },\n    {\n      \"label\": \"TCP\",\n      \"value\": \"TCP\"\n    },\n    {\n      \"label\": \"PG\",\n      \"value\": \"PG\"\n    }\n  ],\n  \"type\": \"STRING\"\n}\n

                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#objectclassdefinition","title":"ObjectClassDefinition","text":"

                      Provides some metadata information about a component configuration. Properties:

                      • ad: array The metadata about the configuration properties.
                        • array elements: object
                        • AttributeDefinition
                      • icon: array
                      • optional Can be missing if the OCD does not define icons. A list of icons that visually represent the configuration.

                        • array elements: object

                        Properties:

                        • resource: string An identifier of the icon image resource.
                        • size: number The icon width and height in pixels.
                        • name: string A user friendly name for the component configuration.
                        • description: string A user friendly description of the component configuration.
                        • id: string An identifier of the component configuration.
                      {\n  \"ad\": [\n    {\n      \"defaultValue\": \"false\",\n      \"description\": \"The WatchdogService monitors CriticalComponents and reboots the system if one of them hangs. Once enabled the WatchdogService starts refreshing the watchdog device, which will reset the system if WatchdogService hangs.\",\n      \"id\": \"enabled\",\n      \"isRequired\": true,\n      \"name\": \"Watchdog enable\",\n      \"type\": \"BOOLEAN\"\n    },\n    {\n      \"defaultValue\": \"10000\",\n      \"description\": \"WatchdogService's refresh interval in ms of the Watchdog device. The value can be set between 1 and 60 seconds and should not be set to a value greater or equal to the Watchdog device's timeout value\",\n      \"id\": \"pingInterval\",\n      \"isRequired\": true,\n      \"max\": \"60000\",\n      \"name\": \"Watchdog refresh interval\",\n      \"type\": \"INTEGER\"\n    },\n    {\n      \"defaultValue\": \"/dev/watchdog\",\n      \"description\": \"Watchdog device path e.g. /dev/watchdog.\",\n      \"id\": \"watchdogDevice\",\n      \"isRequired\": true,\n      \"name\": \"Watchdog device path\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"defaultValue\": \"/opt/eclipse/kura/data/kura-reboot-cause\",\n      \"description\": \"The path for the file that will contain the reboot cause information.\",\n      \"id\": \"rebootCauseFilePath\",\n      \"isRequired\": true,\n      \"name\": \"Reboot Cause File Path\",\n      \"type\": \"STRING\"\n    }\n  ],\n  \"description\": \"The WatchdogService handles the hardware watchdog of the platform.  The parameter define the ping periodicity of the hardware watchdog to ensure it does not reboot. The WatchdogService will reset the watchdog timeout, can disable it (where supported) with the Magic Character, but cannot set the refresh rate of a watchdog device.\",\n  \"icon\": [\n    {\n      \"resource\": \"WatchdogService\",\n      \"size\": 32\n    }\n  ],\n  \"id\": \"org.eclipse.kura.watchdog.WatchdogService\",\n  \"name\": \"WatchdogService\"\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#componentconfiguration","title":"ComponentConfiguration","text":"

                      Describes a component configuration. Properties:

                      • pid: string The identifier of this configuration.
                      • ocd: object
                      • optional Can be omitted in some requests and responses, see request documentation for more information.
                        • ObjectClassDefinition
                      • properties: object
                      • optional Can be omitted in some requests and responses, see request documentation for more information.
                        • ConfigurationProperties
                      {\n  \"definition\": {\n    \"ad\": [\n      {\n        \"cardinality\": 3,\n        \"description\": \"If set to a non empty list, REST API access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. Please make sure that the allowed ports are open in HttpService and Firewall configuration.\",\n        \"id\": \"allowed.ports\",\n        \"isRequired\": false,\n        \"max\": \"65535\",\n        \"min\": \"1\",\n        \"name\": \"Allowed ports\",\n        \"type\": \"INTEGER\"\n      }\n    ],\n    \"description\": \"This service allows to configure settings related to Kura REST APIs\",\n    \"id\": \"org.eclipse.kura.internal.rest.provider.RestService\",\n    \"name\": \"RestService\"\n  },\n  \"pid\": \"org.eclipse.kura.internal.rest.provider.RestService\",\n  \"properties\": {\n    \"kura.service.pid\": {\n      \"type\": \"STRING\",\n      \"value\": \"org.eclipse.kura.internal.rest.provider.RestService\"\n    },\n    \"service.pid\": {\n      \"type\": \"STRING\",\n      \"value\": \"org.eclipse.kura.internal.rest.provider.RestService\"\n    }\n  }\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#componentconfigurationlist","title":"ComponentConfigurationList","text":"

                      Represents a list of component configurations. Properties:

                      • configs: array The component configurations
                        • array elements: object
                        • ComponentConfiguration
                      {\n  \"configs\": [\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"defaultValue\": \"true\",\n            \"description\": \"Whether or not to enable the ClockService\",\n            \"id\": \"enabled\",\n            \"isRequired\": true,\n            \"name\": \"enabled\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"defaultValue\": \"true\",\n            \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n            \"id\": \"clock.set.hwclock\",\n            \"isRequired\": true,\n            \"name\": \"clock.set.hwclock\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"defaultValue\": \"java-ntp\",\n            \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n            \"id\": \"clock.provider\",\n            \"isRequired\": true,\n            \"name\": \"clock.provider\",\n            \"option\": [\n              {\n                \"label\": \"java-ntp\",\n                \"value\": \"java-ntp\"\n              },\n              {\n                \"label\": \"ntpd\",\n                \"value\": \"ntpd\"\n              },\n              {\n                \"label\": \"chrony-advanced\",\n                \"value\": \"chrony-advanced\"\n              }\n            ],\n            \"type\": \"STRING\"\n          },\n          {\n            \"defaultValue\": \"0.pool.ntp.org\",\n            \"description\": \"The hostname that provides the system time via NTP\",\n            \"id\": \"clock.ntp.host\",\n            \"isRequired\": true,\n            \"name\": \"clock.ntp.host\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"defaultValue\": \"123\",\n            \"description\": \"The port number that provides the system time via NTP\",\n            \"id\": \"clock.ntp.port\",\n            \"isRequired\": true,\n            \"max\": \"65535\",\n            \"min\": \"1\",\n            \"name\": \"clock.ntp.port\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"10000\",\n            \"description\": \"The NTP timeout in milliseconds\",\n            \"id\": \"clock.ntp.timeout\",\n            \"isRequired\": true,\n            \"min\": \"1000\",\n            \"name\": \"clock.ntp.timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"0\",\n            \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n            \"id\": \"clock.ntp.max-retry\",\n            \"isRequired\": true,\n            \"min\": \"0\",\n            \"name\": \"clock.ntp.max-retry\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"5\",\n            \"description\": \"When sync fails, interval in seconds between each retry.\",\n            \"id\": \"clock.ntp.retry.interval\",\n            \"isRequired\": true,\n            \"min\": \"1\",\n            \"name\": \"clock.ntp.retry.interval\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"3600\",\n            \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n            \"id\": \"clock.ntp.refresh-interval\",\n            \"isRequired\": true,\n            \"name\": \"clock.ntp.refresh-interval\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"/dev/rtc0\",\n            \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n            \"id\": \"rtc.filename\",\n            \"isRequired\": true,\n            \"name\": \"RTC File Name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"description\": \"Chrony configuration file.|TextArea\",\n            \"id\": \"chrony.advanced.config\",\n            \"isRequired\": false,\n            \"name\": \"Chrony Configuration\",\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"ClockService Configuration\",\n        \"icon\": [\n          {\n            \"resource\": \"ClockService\",\n            \"size\": 32\n          }\n        ],\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"name\": \"ClockService\"\n      },\n      \"pid\": \"org.eclipse.kura.clock.ClockService\",\n      \"properties\": {\n        \"clock.ntp.host\": {\n          \"type\": \"STRING\",\n          \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.ntp.max-retry\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"clock.ntp.port\": {\n          \"type\": \"INTEGER\",\n          \"value\": 123\n        },\n        \"clock.ntp.refresh-interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 3600\n        },\n        \"clock.ntp.retry.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 5\n        },\n        \"clock.ntp.timeout\": {\n          \"type\": \"INTEGER\",\n          \"value\": 10000\n        },\n        \"clock.provider\": {\n          \"type\": \"STRING\",\n          \"value\": \"java-ntp\"\n        },\n        \"clock.set.hwclock\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"enabled\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"rtc.filename\": {\n          \"type\": \"STRING\",\n          \"value\": \"/dev/rtc0\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.clock.ClockService\"\n        }\n      }\n    },\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"defaultValue\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\",\n            \"description\": \"Specifies, as an OSGi target filter, the pid of the SslManagerService used to create SSL connections for downloading packages.\",\n            \"id\": \"SslManagerService.target\",\n            \"isRequired\": true,\n            \"name\": \"SslManagerService Target Filter\",\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"This service is responsible of managing the deployment packages installed on the system.\",\n        \"id\": \"org.eclipse.kura.deployment.agent\",\n        \"name\": \"DeploymentAgent\"\n      },\n      \"pid\": \"org.eclipse.kura.deployment.agent\",\n      \"properties\": {\n        \"SslManagerService.target\": {\n          \"type\": \"STRING\",\n          \"value\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.deployment.agent\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.deployment.agent\"\n        }\n      }\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#createfactorycomponentconfigurationsrequest","title":"CreateFactoryComponentConfigurationsRequest","text":"

                      An object describing a factory component instance creation request. Properties:

                      • configs: array The set of configurations to be created

                        • array elements: object An object describing a factory component confguration. Properties:

                        • pid: string The component pid.

                        • factoryPid: string The component factory pid
                        • properties: object
                          • optional If omitted, the component innstance will be created with default configuration.
                          • ConfigurationProperties
                        • takeSnapshot: bool
                        • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the factory component configuration instances have been created.
                      {\n  \"configs\": [\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n      \"pid\": \"testComponent\",\n      \"properties\": {\n        \"db.server.type\": {\n          \"type\": \"STRING\",\n          \"value\": \"WEB\"\n        }\n      }\n    },\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n      \"pid\": \"thirdComponent\"\n    }\n  ],\n  \"takeSnapshot\": true\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#updatecomponentconfigurationrequest","title":"UpdateComponentConfigurationRequest","text":"

                      An object that describes a set of configurations that need to be updated. Properties:

                      • configs: array The configurations to be updated. The ocd field can be omitted, it will be ignored if specified.
                        • array elements: object
                        • ComponentConfiguration
                      • takeSnapshot: bool
                      • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the component configurations have been applied.
                      {\n  \"configs\": [\n    {\n      \"pid\": \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n      \"properties\": {\n        \"command.enable\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"command.timeout\": {\n          \"type\": \"INTEGER\",\n          \"value\": 60\n        }\n      }\n    },\n    {\n      \"pid\": \"org.eclipse.kura.position.PositionService\",\n      \"properties\": {\n        \"parity\": {\n          \"type\": \"STRING\",\n          \"value\": 0\n        }\n      }\n    }\n  ],\n  \"takeSnapshot\": true\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#deletefactorycomponentconfigurationsrequest","title":"DeleteFactoryComponentConfigurationsRequest","text":"

                      An object describing a factory component instance delete request. Properties:

                      • pids: array The list of the pids of the factory component instances to be deleted.
                        • array elements: string The component pid.
                      • takeSnapshot: bool
                      • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the factory component configuration instances have been deleted.
                      {\n  \"pids\": [\n    \"testComponent\",\n    \"otherComponent\"\n  ],\n  \"takeSnapshot\": true\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#pidset","title":"PidSet","text":"

                      Represents a set of pids or factory pids. Properties:

                      • pids: array The set of pids
                        • array elements: string The pid
                      {\n  \"pids\": [\n    \"org.eclipse.kura.deployment.agent\",\n    \"org.eclipse.kura.clock.ClockService\"\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#snaphsotid","title":"SnaphsotId","text":"

                      An object describing the identifier of a configuration snapshot. Properties:

                      • id: number The snapshot id
                      {\n  \"id\": 163655959932\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#genericfailurereport","title":"GenericFailureReport","text":"

                      An object reporting a failure message. Properties:

                      • message: string A message describing the failure.
                      {\n  \"message\": \"An unexpected error occurred.\"\n}\n
                      "},{"location":"references/rest-apis/rest-configuration-service-v2/#batchfailurereport","title":"BatchFailureReport","text":"

                      An object that is returned by requests that involve multiple operations, when at least one operation failed. This object report an error message for each of the operations that failed. The operations are identified by an id, see request documentation for more details. If an operation is not listed by this object, then the operation succeeded. Properties:

                      • failures: array The list of operations that failed.

                        • array elements: object An object reporting details about an operation failure. Properties:

                        • id: string An identifier of the failed operation.

                        • message: string A message describing the failure.
                      {\n  \"failures\": [\n    {\n      \"id\": \"create:testComponent\",\n      \"message\": \"Invalid parameter. pid testComponent already exists\"\n    },\n    {\n      \"id\": \"create:otherComponent\",\n      \"message\": \"Invalid parameter. pid otherComponent already exists\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-deploy-api/","title":"Rest Deploy v2 API","text":"

                      The DeploymentRestService APIs provides methods to manage the installed deployment packages. Identities with rest.deploy permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-deploy-api/#get-installed-packages","title":"Get installed packages","text":"
                      • Description: Provides the list of all the deployment packages installed and tracked by the framework.
                      • Method: GET
                      • API PATH: /deploy/v2/
                      "},{"location":"references/rest-apis/rest-deploy-api/#responses","title":"Responses","text":"
                      • 200 OK status
                      [{ \"name\": \"packageName\", \"version\": \"packageVersion\"}]\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#install-package-from-url","title":"Install package from URL","text":"
                      • Description: Installs the deployment package specified in the InstallRequest. If the request was already issued for the same InstallRequest, it returns the status of the installation process.
                      • Method: POST
                      • API PATH: /deploy/v2/_install
                      "},{"location":"references/rest-apis/rest-deploy-api/#request-body","title":"Request Body","text":"
                      {\n  \"url\": \"deploymentPackageUrl\"\n}\n

                      Example:

                      {\n  \"url\": \"http://download.eclipse.org/kura/releases/4.1.0/org.eclipse.kura.demo.heater_1.0.500.dp\"\n}\n

                      Please note that the url can refer to a .dp already in the device filesystem.

                      "},{"location":"references/rest-apis/rest-deploy-api/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad request
                      \"REQUEST_RECEIVED\"\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#install-package-from-upload","title":"Install package from upload","text":"
                      • Description: Upload and install a Deployment Package.
                      • Method: POST
                      • API PATH: /deploy/v2/_upload
                      "},{"location":"references/rest-apis/rest-deploy-api/#request-body_1","title":"Request Body","text":"

                      The POST request body should be encoded in the multipart/form-data enctype, thus allowing for the upload of the Deployment Package file. The uploaded file is expected to be added in the file field of the form.

                      "},{"location":"references/rest-apis/rest-deploy-api/#headers","title":"Headers","text":"
                      • Content-Type: multipart/form-data
                      "},{"location":"references/rest-apis/rest-deploy-api/#body-formdata","title":"Body (formdata)","text":"
                      • file
                      "},{"location":"references/rest-apis/rest-deploy-api/#example","title":"Example","text":"

                      Example using curl:

                      curl -X POST -k -u $USERNAME:$PASSWORD \\\n    --header 'Content-Type: multipart/form-data' \\\n    --form 'file=@\"/path/to/your/file.dp\"' \\\n    https://$ADDRESS/services/deploy/v2/_upload\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#responses_2","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad request
                      \"REQUEST_RECEIVED\"\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#uninstall-a-package","title":"Uninstall a package","text":"
                      • Description: Uninstalls the deployment package identified by the specified name. If the request was already issued, it reports the status of the uninstallation operation.
                      • Method: DELETE
                      • API PATH: /deploy/v2/{name}
                      "},{"location":"references/rest-apis/rest-deploy-api/#responses_3","title":"Responses","text":"
                      • 200 OK status
                      \"REQUEST_RECEIVED\"\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#get-eclipse-marketplace-package-descriptor","title":"Get Eclipse Marketplace Package Descriptor","text":"
                      • Description: Provides the Eclipse Marketplace Package Descriptor information of the deployment package identified by URL passed in the request.
                      • Method: PUT
                      • API PATH: /deploy/v2/_packageDescriptor
                      "},{"location":"references/rest-apis/rest-deploy-api/#request-body_2","title":"Request Body","text":"
                      {\n  \"url\": \"deploymentPackageUrl\"\n}\n

                      Example:

                      {\n  \"url\": \"http://marketplace.eclipse.org/marketplace-client-intro?mpc_install=5514714\"\n}\n
                      "},{"location":"references/rest-apis/rest-deploy-api/#responses_4","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad request
                      {\n   \"nodeId\":\"5514714\",\n   \"url\":\"https://marketplace.eclipse.org/content/ai-wire-component-eclipse-kura-5\",\n   \"dpUrl\":\"https://download.eclipse.org/kura/releases/5.3.0/org.eclipse.kura.wire.ai.component.provider-1.2.0.dp\",\n   \"minKuraVersion\":\"5.1.0\",\n   \"maxKuraVersion\":\"\",\n   \"currentKuraVersion\":\"5.4.0\",\n   \"isCompatible\":true\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v1/","title":"Rest Identity V1 API","text":"

                      Warning

                      This API id deprecated and superseded by the Identity V2 REST APIs.

                      Note

                      This API can also be accessed via the RequestHandler with app-id: IDN-V1.

                      The IdentityRestService APIs provides methods to manage the system identities. Unless otherwise specified, identities with rest.identity permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-identity-api-v1/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#create-user","title":"Create User","text":"
                      • Description: This method allows to create a new user in the system.
                      • Method: POST
                      • API PATH: services/identity/v1/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#request","title":"Request","text":"
                      {\n    \"userName\": \"username\",\n    \"password\": \"password\",\n    \"passwordChangeNeeded\": false,\n    \"passwordAuthEnabled\": true,\n    \"permissions\": [\n        \"rest.identity\"\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad Request (Password strenght requirements not satisfied)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#get-user-by-name","title":"Get User by Name","text":"
                      • Description: This method allows to get data about an user in the system. The only considered field is the userName.
                      • Method: POST
                      • API PATH: services/identity/v1/identities/byName
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#request_1","title":"Request","text":"
                      {\n    \"userName\": \"username\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_1","title":"Responses","text":"
                      {\n    \"userName\": \"kura.user.username\",\n    \"passwordAuthEnabled\": false,\n    \"passwordChangeNeeded\": false,\n    \"permissions\": []\n}\n
                      • 200 OK status
                      • 404 userName does not exist
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#get-defined-permissions","title":"Get defined permissions","text":"
                      • Description: This method allows you to get the list of the permissions defined in the system
                      • Method: GET
                      • API PATH: services/identity/v1/definedPermissions

                      No specific permission is required to access this resource.

                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_2","title":"Responses","text":"
                      {\n    \"permissions\": [\n        \"rest.command\",\n        \"rest.inventory\",\n        \"rest.configuration\",\n        \"rest.tamper.detection\",\n        \"rest.security\",\n        \"kura.cloud.connection.admin\",\n        \"rest.position\",\n        \"kura.packages.admin\",\n        \"kura.device\",\n        \"rest.wires.admin\",\n        \"kura.admin\",\n        \"rest.keystores\",\n        \"rest.assets\",\n        \"rest.system\",\n        \"kura.maintenance\",\n        \"kura.wires.admin\",\n        \"rest.identity\"\n    ]\n}\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#get-users-configuration","title":"Get users configuration","text":"
                      • Description: This method allows you to get the list of the users and their configuration on the system.
                      • Method: GET
                      • API PATH: services/identity/v1/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_3","title":"Responses","text":"
                      {\n    \"userConfig\": [\n        {\n            \"userName\": \"admin\",\n            \"passwordAuthEnabled\": true,\n            \"passwordChangeNeeded\": false,\n            \"permissions\": [\n                \"kura.admin\"\n            ]\n        },\n        {\n            \"userName\": \"appadmin\",\n            \"passwordAuthEnabled\": true,\n            \"passwordChangeNeeded\": true,\n            \"permissions\": [\n                \"kura.cloud.connection.admin\",\n                \"kura.packages.admin\",\n                \"kura.wires.admin\"\n            ]\n        }\n    ]\n}\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#get-password-requirements","title":"Get password requirements","text":"
                      • Description: This method allows you to get the password requirements.
                      • Method: GET
                      • API PATH: services/identity/v1/passwordRequirements

                      No specific permission is required to access this resource.

                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_4","title":"Responses","text":"
                      {\n    \"passwordMinimumLength\": 8,\n    \"passwordRequireDigits\": false,\n    \"passwordRequireSpecialChars\": false,\n    \"passwordRequireBothCases\": false\n}\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#update-user","title":"Update User","text":"
                      • Description: This method allows to update an existing user in the system.
                      • Method: PUT
                      • API PATH: services/identity/v1/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#request_2","title":"Request","text":"
                      {\n    \"userName\": \"username\",\n    \"password\": \"password\",\n    \"passwordChangeNeeded\": false,\n    \"passwordAuthEnabled\": true,\n    \"permissions\": [\n        \"rest.identity\"\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_5","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad Request (Password strenght requirements not satisfied)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#delete-user","title":"Delete User","text":"
                      • Description: This method allows to delete an existing user in the system. The only considered field is the userName.
                      • Method: DELETE
                      • API PATH: services/identity/v1/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#request_3","title":"Request","text":"
                      {\n    \"userName\": \"username\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_6","title":"Responses","text":"
                      • 200 OK status
                      • 404 userName does not exist
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/","title":"Rest Identity V2 API","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: IDN-V2.

                      The IdentityRestService APIs provides methods to manage the system identities. Unless otherwise specified, identities with rest.identity permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-identity-api-v2/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#create-user","title":"Create User","text":"
                      • Description: This method allows to create a new identity in the system. Identity name must respect the requirements enforced by the IdentityService.
                      • Method: POST
                      • API PATH: services/identity/v2/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request","title":"Request","text":"
                      {\n    \"name\": \"username\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses","title":"Responses","text":"
                      • 200 OK status
                      • 400 Missing or mispelled field name (eg: nme)
                      • 409 Conflict (Identity already exists)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#get-user-by-name","title":"Get User by Name","text":"
                      • Description: This method allows to get data about an identity in the system. The request body's identity field is used to get only the name of specific identity. It is also possible to retrieve information about the specific user's component configuration, specifying the type of interest.
                      • Method: POST
                      • API PATH: services/identity/v2/identities/byName
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_1","title":"Request","text":"
                      {\n    \"identity\": {\n        \"name\": \"test\"\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#response","title":"Response","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_2","title":"Request","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#response_1","title":"Response","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": [\n            {\n                \"name\": \"rest.identity\"\n            }\n        ]\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": true\n    },\n    \"additionalConfigurations\": {\n        \"configurations\": []\n    }\n}\n
                      • 200 OK status
                      • 400 Missing or mispelled field name (eg: nme)
                      • 404 Identity does not exist
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#get-user-default-configuration-by-name","title":"Get User Default Configuration by Name","text":"
                      • Description: This method allows to get the default configuration data about an identity in the system. The request body's identity field is used to get only the name of specific identity. It is also possible to retrieve information about the specific user's component default configuration, specifying the type of interest. This method accepts also non-existing user's name as input: in this way it's possible to retrieve which is the default configuration applied when a user is created with the name field only.
                      • Method: POST
                      • API PATH: services/identity/v2/identities/default/byName
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_3","title":"Request","text":"
                      {\n\"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#response_2","title":"Response","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_4","title":"Request","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#response_3","title":"Response","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": []\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": false\n    },\n    \"additionalConfigurations\": {\n        \"configurations\": []\n    }\n}\n
                      • 200 OK status
                      • 400 Missing or mispelled field name (eg: nme)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#create-permission","title":"Create Permission","text":"
                      • Description: This method allows to create a new permission in the system. Permission name must respect the requirements enforced by the IdentityService.
                      • Method: POST
                      • API PATH: services/identity/v2/permissions
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_5","title":"Request","text":"
                      {\n    \"name\": \"permission\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad Request (Permission name not valid)
                      • 409 Conflict (Permission already exists)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#validate-identity-configuration","title":"Validate Identity Configuration","text":"
                      • Description: Validates the provided list of identity configurations without performing any change to the system. It is possible to specify only the identity body field, or also the configurationComponents one.
                      • Method: POST
                      • API PATH: services/identity/v2/identities/validate
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_6","title":"Request","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_2","title":"Responses","text":"
                      • 200 OK status
                      • 400 Missing or mispelled field name (eg: nme)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#get-defined-permissions","title":"Get defined permissions","text":"
                      • Description: This method allows you to get the list of the permissions defined in the system
                      • Method: GET
                      • API PATH: services/identity/v2/definedPermissions

                      No specific permission is required to access this resource.

                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_3","title":"Responses","text":"
                      [\n    {\n        \"name\": \"rest.identity\"\n    },\n    {\n        \"name\": \"rest.wires.admin\"\n    },\n    {\n        \"name\": \"kura.wires.admin\"\n    },\n    {\n        \"name\": \"kura.network.admin\"\n    },\n    {\n        \"name\": \"rest.network.status\"\n    },\n    {\n        \"name\": \"test-permission\"\n    },\n    {\n        \"name\": \"rest.keystores\"\n    },\n    {\n        \"name\": \"rest.assets\"\n    },\n    {\n        \"name\": \"rest.network.configuration\"\n    },\n    {\n        \"name\": \"kura.admin\"\n    },\n    {\n        \"name\": \"rest.cloudconnection\"\n    },\n    {\n        \"name\": \"kura.device\"\n    },\n    {\n        \"name\": \"rest.system\"\n    },\n    {\n        \"name\": \"kura.maintenance\"\n    },\n    {\n        \"name\": \"kura.packages.admin\"\n    },\n    {\n        \"name\": \"rest.tamper.detection\"\n    },\n    {\n        \"name\": \"rest.deploy\"\n    },\n    {\n        \"name\": \"rest.configuration\"\n    },\n    {\n        \"name\": \"kura.cloud.connection.admin\"\n    },\n    {\n        \"name\": \"rest.command\"\n    },\n    {\n        \"name\": \"rest.inventory\"\n    },\n    {\n        \"name\": \"rest.position\"\n    },\n    {\n        \"name\": \"rest.security\"\n    }\n]\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#get-users-configuration","title":"Get users configuration","text":"
                      • Description: This method allows you to get the list of the users and their configuration on the system.
                      • Method: GET
                      • API PATH: services/identity/v2/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_4","title":"Responses","text":"
                      [\n    {\n        \"identity\": {\n            \"name\": \"admin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": false,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    },\n    {\n        \"identity\": {\n            \"name\": \"appadmin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.packages.admin\"\n                },\n                {\n                    \"name\": \"kura.cloud.connection.admin\"\n                },\n                {\n                    \"name\": \"kura.wires.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": true,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    },\n    {\n        \"identity\": {\n            \"name\": \"netadmin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.device\"\n                },\n                {\n                    \"name\": \"kura.network.admin\"\n                },\n                {\n                    \"name\": \"kura.cloud.connection.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": true,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    }\n]\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#get-password-strenght-requirements","title":"Get Password Strenght Requirements","text":"
                      • Description: This method allows you to get the password requirements.
                      • Method: GET
                      • API PATH: services/identity/v2/passwordStrenghtRequirements

                      No specific permission is required to access this resource.

                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_5","title":"Responses","text":"
                      {\n    \"passwordMinimumLength\": 8,\n    \"digitsRequired\": false,\n    \"specialCharactersRequired\": false,\n    \"bothCasesRequired\": false\n}\n
                      • 200 OK status
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#update-identity","title":"Update Identity","text":"
                      • Description: This method allows to update an existing identity in the system. New passwords must respect the requirements enforced by the IdentityService.
                      • Method: PUT
                      • API PATH: services/identity/v2/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_7","title":"Request","text":"
                      {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": [\n            {\n                \"name\": \"rest.identity\"\n            }\n        ]\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": true,\n        \"password\": \"password123\"\n    }\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_6","title":"Responses","text":"
                      • 200 OK status
                      • 400 Missing or mispelled field name (eg: nme)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#delete-user","title":"Delete User","text":"
                      • Description: This method allows to delete an existing user in the system. The only considered field is the name.
                      • Method: DELETE
                      • API PATH: services/identity/v2/identities
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_8","title":"Request","text":"
                      {\n    \"name\": \"username\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_7","title":"Responses","text":"
                      • 200 OK status
                      • 404 username does not exist
                      • 400 Missing or mispelled field name (eg: nme)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#delete-permission","title":"Delete Permission","text":"
                      • Description: This method allows to delete an existing permission in the system. The only considered field is the name.
                      • Method: DELETE
                      • API PATH: services/identity/v2/permissions
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#request_9","title":"Request","text":"
                      {\n    \"name\": \"permission\"\n}\n
                      "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_8","title":"Responses","text":"
                      • 200 OK status
                      • 404 permission does not exist
                      • 400 Missing or mispelled field name (eg: nme)
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/","title":"Rest Inventory v1 API","text":""},{"location":"references/rest-apis/rest-inventory-api/#global","title":"Global","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-inventory-summary","title":"GET Inventory Summary","text":"
                      • Method: GET
                      • Description: returns a list of All Inventory Items
                      • API PATH: /services/inventory/v1/inventory
                      "},{"location":"references/rest-apis/rest-inventory-api/#response","title":"Response","text":"
                      • 200 OK
                        {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"com.eclipsesource.jaxrs.provider.gson\",\n            \"version\":\"2.3.0.201602281253\",\n            \"type\":\"BUNDLE\"\n        },\n              {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"type\":\"DP\"\n        }\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#bundles","title":"Bundles","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-bundles","title":"GET bundles","text":"
                      • Method: GET
                      • Description: returns a list of bundles.
                      • API PATH: /services/inventory/v1/bundles
                      "},{"location":"references/rest-apis/rest-inventory-api/#response_1","title":"Response","text":"
                      • 200 OK
                        {\n    \"bundles\":[\n        {\n            \"name\":\"org.eclipse.osgi\",\n            \"version\":\"3.16.0.v20200828-0759\",\n            \"id\":0,\n            \"state\":\"ACTIVE\",\n            \"signed\":true\n        },\n        {\n            \"name\":\"org.eclipse.equinox.cm\",\n            \"version\":\"1.4.400.v20200422-1833\",\n            \"id\":1,\n            \"state\":\"ACTIVE\",\n            \"signed\":false\n        }\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#start-bundle","title":"Start bundle","text":"
                      • Method: POST
                      • API PATH: /services/inventory/v1/bundles/_start
                      "},{"location":"references/rest-apis/rest-inventory-api/#request-body","title":"Request Body","text":"
                      { \n\"name\":\"org.eclipse.osgi\",\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#responses","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/#stop-bundle","title":"Stop bundle","text":"
                      • Method: POST
                      • API PATH: /services/inventory/v1/bundles/_stop
                      "},{"location":"references/rest-apis/rest-inventory-api/#request-body_1","title":"Request Body","text":"
                      { \n\"name\":\"org.eclipse.osgi\",\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/#deployment-packages","title":"Deployment Packages","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-packages","title":"GET packages","text":"
                      • Method: GET
                      • Description: returns a list of deployment packages.
                      • API PATH: /services/inventory/v1/deploymentPackages
                      "},{"location":"references/rest-apis/rest-inventory-api/#response_2","title":"Response","text":"
                      • 200 OK
                        {\n    \"deploymentPackages\":[\n        {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"signed\":false,\n            \"bundles\":[\n                {\n                    \"name\":\"org.eclipse.kura.example.beacon\",\n                    \"version\":\"1.0.500\",\n                    \"id\": 171,\n                    \"state\": \"ACTIVE\",\n                    \"signed\": false\n                }\n            ]\n        }\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#system-packages-debrpmapk","title":"System Packages (DEB/RPM/APK)","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-system-packages","title":"GET System Packages","text":"
                      • Method: GET
                      • Description: returns a list of system packages.
                      • API PATH: /services/inventory/v1/systemPackages
                      "},{"location":"references/rest-apis/rest-inventory-api/#response_3","title":"Response","text":"
                      • 200 OK
                        {\n    \"systemPackages\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"alsa-utils\",\n            \"version\":\"1.1.8-2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"ansible\",\n            \"version\":\"2.7.7+dfsg-1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apparmor\",\n            \"version\":\"2.13.2-10\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-listchanges\",\n            \"version\":\"3.19\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-transport-https\",\n            \"version\":\"1.8.2.2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-utils\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        }\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#containers","title":"Containers","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-containers","title":"GET Containers","text":"
                      • Method: Get
                      • Description: returns a list of Containers.
                      • API PATH: /services/inventory/v1/containers
                      "},{"location":"references/rest-apis/rest-inventory-api/#response_4","title":"Response","text":"
                      • 200 OK
                        {\n  \"containers\":\n  [\n    {\n      \"name\":\"container_1\",\n      \"version\":\"nginx:latest\",\n      \"type\":\"DOCKER\",\n      \"state\":\"active\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#start-container","title":"Start container","text":"
                      • Method: POST
                      • API PATH: /services/inventory/v1/containers/_start
                      "},{"location":"references/rest-apis/rest-inventory-api/#request-body_2","title":"Request Body","text":"
                      {\n    \"name\":\"container_1\",\n    \"version\": \"nginx:latest\",\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#responses_2","title":"Responses","text":"
                      • 200 OK
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/#stop-container","title":"Stop container","text":"
                      • Method: POST
                      • API PATH: /services/inventory/v1/containers/_stop
                      "},{"location":"references/rest-apis/rest-inventory-api/#request-body_3","title":"Request Body","text":"
                      {\n    \"name\":\"container_1\",\n    \"version\": \"nginx:latest\",\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#responses_3","title":"Responses","text":"
                      • 200 OK
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/#images","title":"Images","text":""},{"location":"references/rest-apis/rest-inventory-api/#images-images","title":"Images Images","text":"
                      • Method: Get
                      • Description: returns a list of Images.
                      • API PATH: /services/inventory/v1/images
                      "},{"location":"references/rest-apis/rest-inventory-api/#response_5","title":"Response","text":"
                      • 200 OK
                        {\n  \"containers\":\n  [\n    {\n      \"name\":\"nginx\",\n      \"version\":\"latest\",\n      \"type\":\"ContainerImage\",\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#delete-image","title":"Delete Image","text":"
                      • Method: POST
                      • API PATH: /services/inventory/v1/images/_delete
                      "},{"location":"references/rest-apis/rest-inventory-api/#request-body_4","title":"Request Body","text":"
                      {\n      \"name\":\"nginx\",\n      \"version\":\"latest\",\n}\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#responses_4","title":"Responses","text":"
                      • 200 OK
                      • 400 Bad Request (Malformed Client JSON)
                      • 404 Resource Not Found
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-inventory-api/#states","title":"States","text":""},{"location":"references/rest-apis/rest-inventory-api/#bundle-states","title":"Bundle States","text":"
                      -   `ACTIVE`: Container is running\n-   `INSTALLED`: Container is starting\n-   `UNINSTALLED`: Container has failed, or is stopped\n-   `UNKNOWN`: Container state can not be determined\n
                      "},{"location":"references/rest-apis/rest-inventory-api/#container-states","title":"Container States","text":"
                      -   `active`: Container is running\n-   `installed`: Container is starting\n-   `uninstalled`: Container has failed, or is stopped\n-   `unknown`: Container state can not be determined\n
                      "},{"location":"references/rest-apis/rest-network-configuration-api/","title":"Network Configuration V1 REST APIs","text":"

                      This section describes the networkConfiguration/v1 REST APIs, that allow to retrieve information about the network configuration of the running system. The services pid for which it's possible to obtain and update the configurations are:

                      • org.eclipse.kura.net.admin.NetworkConfigurationService: which manages the network configuration of the system, so the network interfaces along with their network configuration (Ip4/Ip6 settings, DHCP, Nat, and so on)
                      • org.eclipse.kura.net.admin.FirewallConfigurationService: which manages the Ip4 firewall configurations (open ports, port forwarding and masquerading for IPv4 protocol)
                      • org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6: which manages the Ip6 firewall configurations (open ports, port forwarding and masquerading for IPv6 protocol)

                      To access these REST APIs, an identity with rest.network.configuration permission assigned is required.

                      • Request definitions
                        • GET/configurableComponents
                        • GET/configurableComponents/configurations
                        • POST/configurableComponents/configurations/byPid
                        • POST/configurableComponents/configurations/byPid/_default
                        • PUT/configurableComponents/configurations/_update
                        • GET/factoryComponents
                        • POST/factoryComponents
                        • DEL/factoryComponents/byPid
                        • GET/factoryComponents/ocd
                        • POST/factoryComponents/ocd/byFactoryPid
                      • JSON definitions
                        • BatchFailureReport
                        • ComponentConfigurationList
                        • GenericFailureReport
                        • PidSet
                        • UpdateComponentConfigurationRequest
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-network-configuration-api/#getconfigurablecomponents","title":"GET/configurableComponents","text":"
                      • REST API path : /services/networkConfiguration/v1/configurableComponents
                      • description : Returns the list of the available services that manages the network configurations on the system.
                      • responses :
                        • 200
                          • description : The request succeeded.
                          • response body :
                            • PidSet
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#getconfigurablecomponentsconfigurations","title":"GET/configurableComponents/configurations","text":"
                      • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations
                      • description : Returns all of network component configurations available on the system. This request will return the pid, ocd and properties.
                      • responses :
                        • 200
                          • description : The request succeeded.
                          • response body :
                            • ComponentConfigurationList
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#postconfigurablecomponentsconfigurationsbypid","title":"POST/configurableComponents/configurations/byPid","text":"
                      • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/byPid
                      • description : Returns a user selected set of network configurations. This request will return the pid, ocd and properties.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                          • description : The request succeeded. If the network configuration for a given pid cannot be found, it will not be included in the result.
                          • response body :
                            • ComponentConfigurationList
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#postconfigurablecomponentsconfigurationsbypid_default","title":"POST/configurableComponents/configurations/byPid/_default","text":"
                      • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/byPid/_default
                      • description : Returns the default network configuration for a given set of network component pids. The default configurations are generated basing on component definition only, user applied modifications will not be taken into account. This request will return the pid, ocd and properties.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                          • description : The request succeeded. If the network configuration for a given pid cannot be found, it will not be included in the result.
                          • response body :
                            • ComponentConfigurationList
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#putconfigurablecomponentsconfigurations_update","title":"PUT/configurableComponents/configurations/_update","text":"
                      • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/_update
                      • description : Updates a given set of network component configurations. This request can be also used to apply a network configuration snapshot.
                      • request body :
                        • UpdateComponentConfigurationRequest
                      • responses :
                        • 200
                          • description : The request succeeded.
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid for component update operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                          • response body :
                            • Variants:
                              • object
                                • GenericFailureReport
                              • object
                                • BatchFailureReport

                      Warning

                      factoryComponents endopoints are available in the current version of Kura for future compatibility. Currently, as of Kura 5.4.0, there are no network related components that are factory components.

                      The endopoints that should return a list of pids will always return an empty array, while those that should create, delete or update a component will always return a 500 error.

                      "},{"location":"references/rest-apis/rest-network-configuration-api/#getfactorycomponents","title":"GET/factoryComponents","text":"
                      • REST API path : /services/networkConfiguration/v1/factoryComponents
                      • description : Returns the ids of the network component factories available on the device.
                      • responses :
                        • 200
                          • description : The factory pid set.
                          • response body :
                            • PidSet
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#postfactorycomponents","title":"POST/factoryComponents","text":"
                      • REST API path : /services/networkConfiguration/v1/factoryComponents
                      • description : This is a batch request that allows to create one or more network factory component instances and optionally create a new snapshot.
                      • request body :
                        • CreateFactoryComponentConfigurationsRequest
                      • responses :
                        • 200
                          • description : The request succeeded.
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500

                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: create:$pid for component creation operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.

                          • response body :

                            • Variants:
                              • object
                                • GenericFailureReport
                              • object
                                • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#delfactorycomponentsbypid","title":"DEL/factoryComponents/byPid","text":"
                      • REST API path : /services/networkConfiguration/v1/factoryComponents/byPid
                      • description : This is a batch request that allows to delete one or more network factory component instances and optionally create a new snapshot.
                      • request body :
                        • DeleteFactoryComponentConfigurationsRequest
                      • responses :
                        • 200
                          • description : The request succeeded.
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for component delete operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                          • response body :
                            • Variants:
                              • object
                                • GenericFailureReport
                              • object
                                • BatchFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#getfactorycomponentsocd","title":"GET/factoryComponents/ocd","text":"
                      • REST API path : /services/networkConfiguration/v1/factoryComponents/ocd
                      • description : Returns the OCD of the network components created by the factories available on the device without the need of creating an instance. This request returns the information related to all available network factories.
                      • responses :
                        • 200
                          • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present.
                          • response body :
                            • ComponentConfigurationList
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#postfactorycomponentsocdbyfactorypid","title":"POST/factoryComponents/ocd/byFactoryPid","text":"
                      • REST API path : /services/networkConfiguration/v1/factoryComponents/ocd/byFactoryPid
                      • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to a user selected set of factories.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                          • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present. If the OCD for a given factory pid cannot be found, it will not be included in the result.
                          • response body :
                            • ComponentConfigurationList
                        • 400
                          • description : The request body is not valid JSON or it contains invalid parameters.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : An unexpected internal error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-network-configuration-api/#batchfailurereport","title":"BatchFailureReport","text":"

                      An object that is returned by requests that involve multiple operations, when at least one operation failed. This object report an error message for each of the operations that failed. The operations are identified by an id, see request documentation for more details. If an operation is not listed by this object, then the operation succeeded. Properties:

                      • failures: array The list of operations that failed.

                        • array elements: object An object reporting details about an operation failure. Properties:

                        • id: string An identifier of the failed operation.

                        • message: string A message describing the failure.
                      {\n  \"failures\": [\n    {\n      \"id\": \"create:testComponent\",\n      \"message\": \"Invalid parameter. pid testComponent already exists\"\n    },\n    {\n      \"id\": \"create:otherComponent\",\n      \"message\": \"Invalid parameter. pid otherComponent already exists\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#componentconfigurationlist","title":"ComponentConfigurationList","text":"

                      Represents a list of component configurations. Properties:

                      • configs: array The component configurations
                        • array elements: object
                        • ComponentConfiguration
                      {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"firewall.open.ports\",\n                        \"description\": \"The list of firewall opened ports.\",\n                        \"id\": \"firewall.open.ports\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.port.forwarding\",\n                        \"description\": \"The list of firewall port forwarding rules.\",\n                        \"id\": \"firewall.port.forwarding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.nat\",\n                        \"description\": \"The list of firewall NAT rules.\",\n                        \"id\": \"firewall.nat\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"FirewallConfigurationService\",\n                \"description\": \"Firewall Configuration Service\",\n                \"id\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\"\n            },\n            \"properties\": {\n                \"firewall.open.ports\": {\n                    \"value\": \"22,tcp,0.0.0.0/0,eth0,,,,#;22,tcp,0.0.0.0/0,wlan0,,,,#;443,tcp,0.0.0.0/0,eth0,,,,#;443,tcp,0.0.0.0/0,wlan0,,,,#;4443,tcp,0.0.0.0/0,eth0,,,,#;4443,tcp,0.0.0.0/0,wlan0,,,,#;1450,tcp,0.0.0.0/0,eth0,,,,#;1450,tcp,0.0.0.0/0,wlan0,,,,#;5002,tcp,127.0.0.1/32,,,,,#;53,udp,0.0.0.0/0,eth0,,,,#;53,udp,0.0.0.0/0,wlan0,,,,#;67,udp,0.0.0.0/0,eth0,,,,#;67,udp,0.0.0.0/0,wlan0,,,,#;8000,tcp,0.0.0.0/0,eth0,,,,#;8000,tcp,0.0.0.0/0,wlan0,,,,#\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.port.forwarding\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.nat\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"firewall.ipv6.open.ports\",\n                        \"description\": \"The list of firewall opened ports.\",\n                        \"id\": \"firewall.ipv6.open.ports\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.ipv6.port.forwarding\",\n                        \"description\": \"The list of firewall port forwarding rules.\",\n                        \"id\": \"firewall.ipv6.port.forwarding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.ipv6.nat\",\n                        \"description\": \"The list of firewall NAT rules.\",\n                        \"id\": \"firewall.ipv6.nat\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"FirewallConfigurationServiceIPv6\",\n                \"description\": \"Firewall Configuration Service IPV6\",\n                \"id\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\"\n            },\n            \"properties\": {\n                \"firewall.ipv6.port.forwarding\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.ipv6.nat\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.ipv6.open.ports\": {\n                    \"value\": \"1234,tcp,0:0:0:0:0:0:0:0/0,,,,,#\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#genericfailurereport","title":"GenericFailureReport","text":"

                      An object reporting a failure message. Properties:

                      • message: string A message describing the failure.
                      {\n  \"message\": \"An unexpected error occurred.\"\n}\n
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#pidset","title":"PidSet","text":"

                      Represents a set of pids or factory pids. Properties:

                      • pids: array The set of pids
                        • array elements: string The pid
                      {\n    \"pids\": [\n        \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n        \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n        \"org.eclipse.kura.net.admin.FirewallConfigurationService\"\n    ]\n}\n
                      "},{"location":"references/rest-apis/rest-network-configuration-api/#updatecomponentconfigurationrequest","title":"UpdateComponentConfigurationRequest","text":"

                      An object that describes a set of configurations that need to be updated. Properties:

                      • configs: array The configurations to be updated. The ocd field can be omitted, it will be ignored if specified.
                        • array elements: object
                        • ComponentConfiguration
                      • takeSnapshot: bool
                      • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the component configurations have been applied.
                      {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n            \"properties\": {\n                \"firewall.ipv6.open.ports\": {\n                    \"value\": \"1234,tcp,0:0:0:0:0:0:0:0/0,,,,,#\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n            \"properties\": {\n                \"net.interface.eth0.config.ip6.status\": {\n                    \"value\": \"netIPv6StatusUnmanaged\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ],\n    \"takeSnapshot\": true\n}\n
                      "},{"location":"references/rest-apis/rest-position-api/","title":"Position","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: POS-V1.

                      "},{"location":"references/rest-apis/rest-position-api/#get-position","title":"Get Position","text":"
                      • Method: GET
                      • API PATH: /services/position/v1/position
                      "},{"location":"references/rest-apis/rest-position-api/#responses","title":"Responses","text":"
                      • 200 OK status
                      {\n    //longitude of this position in degrees.\n    \"longitude\": 7.729571119959449,\n\n    //latitude of this position in degrees.\n    \"latitude\": 50.45345802194789,\n\n    //altitude of this position in meters.\n    \"altitude\": 1000.0,\n\n    //the ground speed of this position in meters per second.\n    \"speed\": 1000.0,\n\n    //the track of this position in degrees as a compass heading.\n    \"track\": 1000.0\n\n    //the gnss system used to retrieve position information\n    \"gnssType\": [\n        \"Gps\"\n    ]\n}\n
                      • 500 Internal Server Error
                        • will also occur when GPS Position is not locked
                          {\n    \"message\": \"Service unavailable. Position is not locked.\"\n}\n
                      "},{"location":"references/rest-apis/rest-position-api/#get-date-time","title":"Get Date Time","text":"
                      • Method: GET
                      • API PATH: /services/position/v1/dateTime
                      "},{"location":"references/rest-apis/rest-position-api/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      {\n    //The dateTime string is formatted according to the ISO 8601 standard, and will always be in the UTC timezone. \n    \"dateTime\": \"2023-07-19T18:26:38Z\"\n}\n
                      • 500 Internal Server Error
                        • will also occur when GPS Position is not locked
                          {\n    \"message\": \"Service unavailable. Position is not locked.\"\n}\n
                      "},{"location":"references/rest-apis/rest-position-api/#get-is-position-locked","title":"Get Is Position Locked","text":"
                      • Method: GET
                      • API PATH: /services/position/v1/isLocked
                      "},{"location":"references/rest-apis/rest-position-api/#responses_2","title":"Responses","text":"
                      • 200 OK status

                      {\n    \"islocked\": false\n}\n
                      - 500 Internal Server Error

                      "},{"location":"references/rest-apis/rest-security-api-v1/","title":"Rest Security V1 API","text":"

                      Warning

                      This API id deprecated and superseded by the Security V2 REST APIs.

                      Note

                      This API can also be accessed via the RequestHandler with app-id: SEC-V1.

                      This REST API requires a SecurityService implementation to be registered on the framework, which is not provided by the standard Kura distribution.

                      The SecurityRestService APIs provides methods to manage the system security. Identities with rest.security permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-security-api-v1/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-security-api-v1/#security-policy-fingerprint-reload","title":"Security policy fingerprint reload","text":"
                      • Description: This method allows the reload of the security policy's fingerprint
                      • Method: POST
                      • API PATH: services/security/v1/security-policy-fingerprint/reload
                      "},{"location":"references/rest-apis/rest-security-api-v1/#responses","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v1/#reload-command-line-fingerprint","title":"Reload command line fingerprint","text":"
                      • Description: This method allows the reload of the command line fingerprint
                      • Method: POST
                      • API PATH: services/security/v1/command-line-fingerprint/reload
                      "},{"location":"references/rest-apis/rest-security-api-v1/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v1/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-security-api-v1/#debug-enabled","title":"Debug enabled","text":"

                      Note

                      Access to this resource doesn't require the rest.security permission.

                      • Description: This method allows you to check whether debug mode is enabled in the system.
                      • Method: GET
                      • API PATH: services/security/v1/debug-enabled
                      "},{"location":"references/rest-apis/rest-security-api-v1/#responses_2","title":"Responses","text":"
                      • 200 OK status
                        {\n    \"enabled\":true\n}\n
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v2/","title":"Rest Security V2 API","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: SEC-V2.

                      This REST API requires a SecurityService implementation to be registered on the framework, which is not provided by the standard Kura distribution.

                      The SecurityRestService APIs provides methods to manage the system security. Identities with rest.security permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-security-api-v2/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-security-api-v2/#security-policy-fingerprint-reload","title":"Security policy fingerprint reload","text":"
                      • Description: This method allows the reload of the security policy's fingerprint
                      • Method: POST
                      • API PATH: services/security/v2/security-policy-fingerprint/reload
                      "},{"location":"references/rest-apis/rest-security-api-v2/#responses","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v2/#reload-command-line-fingerprint","title":"Reload command line fingerprint","text":"
                      • Description: This method allows the reload of the command line fingerprint
                      • Method: POST
                      • API PATH: services/security/v2/command-line-fingerprint/reload
                      "},{"location":"references/rest-apis/rest-security-api-v2/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v2/#apply-default-production-security-policy","title":"Apply default production security policy","text":"
                      • Description: This method allows to apply the default production security policy available in the system
                      • Method: POST
                      • API PATH: services/security/v2/security-policy/apply-default-production
                      "},{"location":"references/rest-apis/rest-security-api-v2/#responses_2","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v2/#apply-security-policy","title":"Apply security policy","text":"
                      • Description: This method allows to apply the user provided security policy. The maximum allowed security policy size is 1MB.
                      • Method: POST
                      • API PATH: services/security/v2/security-policy/apply
                      "},{"location":"references/rest-apis/rest-security-api-v2/#request","title":"Request","text":"
                      <plain text security policy>\n
                      "},{"location":"references/rest-apis/rest-security-api-v2/#responses_3","title":"Responses","text":"
                      • 200 OK status
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-security-api-v2/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-security-api-v2/#debug-enabled","title":"Debug enabled","text":"

                      Note

                      Access to this resource doesn't require the rest.security permission.

                      • Description: This method allows you to check whether debug mode is enabled in the system.
                      • Method: GET
                      • API PATH: services/security/v2/debug-enabled
                      "},{"location":"references/rest-apis/rest-security-api-v2/#responses_4","title":"Responses","text":"
                      • 200 OK status
                        {\n    \"enabled\":true\n}\n
                      • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                      "},{"location":"references/rest-apis/rest-service-listing-api/","title":"Service Listing V1 REST APIs","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: SVCLIST-V1.

                      The SVCLIST-V1 cloud request handler and the corresponding REST APIs allow to retrieve the identifiers (kura.service.pid) of the service running on the system that match user provided search criteria.

                      • Request definitions
                        • GET/servicePids
                        • POST/servicePids/byInterface
                        • POST/servicePids/byProperty
                        • POST/servicePids/satisfyingReference
                        • GET/factoryPids
                        • POST/factoryPids/byInterface
                        • POST/factoryPids/byProperty
                      • JSON definitions
                        • InterfaceNames
                        • Reference
                        • Filter
                        • PropertyFilter
                        • NotFilter
                        • AndFilter
                        • OrFilter
                        • PidSet
                        • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-service-listing-api/#getservicepids","title":"GET/servicePids","text":"
                      • REST API path : /services/serviceListing/v1/servicePids
                      • description : Returns the pid of all services running in the framework.
                      • responses :
                        • 200
                          • description : An object reporting the pid of all services running in the framework
                          • response body :
                            • PidSet
                        • 500
                          • description : If an unexpected failure occurs while retrieving the service pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidsbyinterface","title":"POST/servicePids/byInterface","text":"
                      • REST API path : /services/serviceListing/v1/servicePids/byInterface
                      • description : Returns the pid of the services providing all of the service interfaces specified in the request
                      • request body :
                        • InterfaceNames
                      • responses :
                        • 200
                          • description : An object reporting the pid of the matching services.
                          • response body :
                            • PidSet
                        • 400
                          • description : If the request body is not syntactically correct.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the service pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidsbyproperty","title":"POST/servicePids/byProperty","text":"
                      • REST API path : /services/serviceListing/v1/servicePids/byProperty
                      • description : Returns the pid of the services whose properties match the specified filter.
                      • request body :
                        • Filter
                      • responses :
                        • 200
                          • description : An object reporting the pid of the matching services.
                          • response body :
                            • PidSet
                        • 400
                          • description : If the request body is not syntactically correct.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the service pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidssatisfyingreference","title":"POST/servicePids/satisfyingReference","text":"
                      • REST API path : /services/serviceListing/v1/servicePids/satisfyingReference
                      • description : Returns the pid of the services that provide an interface compatible with a Declarative Service reference. Reference examples are KeystoreService and TruststoreKeystoreService defined by the org.eclipse.kura.ssl.SslManagerService component.
                      • request body :
                        • Reference
                      • responses :
                        • 200
                          • description : An object reporting the pid of the matching services.
                          • response body :
                            • PidSet
                        • 400
                          • description : If the request body is not syntactically correct.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the service pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#getfactorypids","title":"GET/factoryPids","text":"
                      • REST API path : /services/serviceListing/v1/factoryPids
                      • description : Returns the factory pids defined in the framework.
                      • responses :
                        • 200
                          • description : An object reporting the factory pids defined in the framework.
                          • response body :
                            • PidSet
                        • 500
                          • description : If an unexpected failure occurs while retrieving the factory pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#postfactorypidsbyinterface","title":"POST/factoryPids/byInterface","text":"
                      • REST API path : /services/serviceListing/v1/factoryPids/byInterface
                      • description : Returns the factory pid of the services that provide all of the specified interfaces.
                      • request body :
                        • InterfaceNames
                      • responses :
                        • 200
                          • description : An object reporting the matching factory pids.
                          • response body :
                            • PidSet
                        • 400
                          • description : If the request body is not syntactically correct.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the factory pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#postfactorypidsbyproperty","title":"POST/factoryPids/byProperty","text":"
                      • REST API path : /services/serviceListing/v1/factoryPids/byProperty
                      • description : Returns the list of factory pids whose properties match the specified filter.
                      • request body :
                        • Filter
                      • responses :
                        • 200
                          • description : An object reporting the matching factory pids.
                          • response body :
                            • PidSet
                        • 400
                          • description : If the request body is not syntactically correct.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the factory pid list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-service-listing-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-service-listing-api/#interfacenames","title":"InterfaceNames","text":"

                      A list of serviceinterface names.

                      Properties:

                      • interfaceNames: array The list of service interface names.

                        • array element type: string A service interface name.
                      {\n  \"interfaceNames\": [\n    \"org.eclipse.kura.security.keystore.KeystoreService\",\n    \"org.eclipse.kura.configuration.ConfigurableComponent\"\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#reference","title":"Reference","text":"

                      A object specifying a service pid and a reference name.

                      Properties:

                      • pid: string The pid of the service containing the reference

                      • referenceName: string The reference name

                      {\n  \"pid\": \"org.eclipse.kura.ssl.SslManagerService\",\n  \"referenceName\": \"KeystoreService\"\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#propertyfilter","title":"PropertyFilter","text":"

                      A filter matching property keys and values. If the value property omitted, the filter will match if the property is set, regardless of the value. If the service property value is of array or list type, the filter will match if at least one of the elements match.

                      Properties:

                      • name: string The property name that should be matched, it must not contain spaces

                      • value: string (optional) The property value that should be matched.

                      {\n  \"name\": \"foo\",\n  \"value\": \"bar\"\n}\n
                      {\n  \"name\": \"foo\"\n}\n

                      "},{"location":"references/rest-apis/rest-service-listing-api/#notfilter","title":"NotFilter","text":"

                      A filter that negates the result returned by the filter specified as the \"not\" propertiy.

                      Properties:

                      • not: object
                        • Filter
                      {\n  \"not\": {\n    \"name\": \"foo\",\n    \"value\": \"bar\"\n  }\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#andfilter","title":"AndFilter","text":"

                      A filter that matches if all filters specified by the \"and\" propertiy match.

                      Properties:

                      • and: array A list of filters that should be combined with the and operator

                        • array element type: object
                          • Filter
                      {\n  \"and\": [\n    {\n      \"name\": \"foo\",\n      \"value\": \"bar\"\n    },\n    {\n      \"name\": \"baz\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#orfilter","title":"OrFilter","text":"

                      A filter that matches if any of the filters specified by the \"or\" propertiy match.

                      Properties:

                      • or: array A list of filters that should be combined with the or operator

                        • array element type: object
                          • Filter
                      {\n  \"or\": [\n    {\n      \"name\": \"foo\",\n      \"value\": \"bar\"\n    },\n    {\n      \"name\": \"baz\"\n    }\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#filter","title":"Filter","text":"

                      A filter that operates on service properties. This object allows to specify basic property key/value matchers and the and or and not operators.

                      • Variants:
                      • object
                        • PropertyFilter
                      • object
                        • NotFilter
                      • object
                        • AndFilter
                      • object
                        • OrFilter
                      "},{"location":"references/rest-apis/rest-service-listing-api/#pidset","title":"PidSet","text":"

                      Represents a set of pids or factory pids.

                      Properties:

                      • pids: array The set of pids

                        • array element type: string The pid
                      {\n  \"pids\": [\n    \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"org.eclipse.kura.cloud.publisher.CloudNotificationPublisher\",\n    \"org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"org.eclipse.kura.core.deployment.CloudDeploymentHandlerV2\",\n    \"org.eclipse.kura.crypto.CryptoService\",\n    \"org.eclipse.kura.data.DataService\",\n    \"org.eclipse.kura.db.H2DbService\",\n    \"org.eclipse.kura.deployment.agent\"\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-service-listing-api/#genericfailurereport","title":"GenericFailureReport","text":"

                      An object reporting a failure message.

                      Properties:

                      • message: string A message describing the failure.
                      "},{"location":"references/rest-apis/rest-session-api/","title":"Session V1 Rest APIs","text":"

                      The session management REST APIs allow to authenticate and establish an HTTP session. Using this APIs and creating a session is the recommended way for interact with Kura rest APIs from a browser based application.

                      The supported workflows are the following:

                      "},{"location":"references/rest-apis/rest-session-api/#login-and-resource-access-workflow","title":"Login and resource access workflow","text":"
                      1. Try calling the GET/xsrfToken to get an XSRF token, if the request succeeds a vaild session is already available, it is possible to proceed to step 4.

                        • It is not necessary to call GET/xsrfToken again until the current session expires, the obtained token is valid as long as the current session is valid.
                      2. Call the POST/login/password or POST/login/certificate providing the credentials to create a new session.

                        • This request will return a session cookie with the response. The session cookie name is currently JSESSIONID. It is important to provide the received cookies in successive requests using the Cookie HTTP request header. If this is not done, requests will fail with 401 code. If the request is performed by a browser, cookie management should be performed automatically.

                        • If password authentication has been used and the response object reports that a password change is needed, perform the Update password workflow

                      3. Repeat step 1. to get an XSRF token.

                      4. Access the desired resources, set the XSRF token previously obtained as the value of the X-XSRF-Token HTTP header on each request. If a reuqest fails with error code 401, proceed to step 2.

                      "},{"location":"references/rest-apis/rest-session-api/#login-example-with-curl","title":"Login example with curl","text":""},{"location":"references/rest-apis/rest-session-api/#1-login-with-usernamepassword-and-collect-the-session-cookie","title":"1. Login with username/password and collect the session cookie","text":"
                      curl -k -X POST \\\n    -H 'Content-Type: application/json' \\\n    https://$ADDRESS/services/session/v1/login/password \\\n    -d '{\"password\": \"$KURA_PASS\", \"username\": \"$KURA_USER\"}' -v\n

                      where:

                      • $ADDRESS: is the address of the Kura instance
                      • $KURA_USER: is the Kura username
                      • $KURA_PASS: is the Kura password

                      in the log you should find a JSESSIONID you'll use in subsequent requests

                      ...\n< HTTP/1.1 200 OK\n< Date: Tue, 14 Nov 2023 08:17:26 GMT\n< Set-Cookie: JSESSIONID=myawesomecookie; Path=/; Secure; HttpOnly\n< Expires: Thu, 01 Jan 1970 00:00:00 GMT\n< Content-Type: application/json\n< Content-Length: 30\n<\n* Connection #0 to host 192.168.1.111 left intact\n{\"passwordChangeNeeded\":false}%\n
                      "},{"location":"references/rest-apis/rest-session-api/#2-retrieve-the-xsrf-token","title":"2. Retrieve the XSRF token","text":"
                      curl -k -X GET \\\n    -b \"JSESSIONID=myawesomecookie\" \\\n    https://$ADDRESS/services/session/v1/xsrfToken\n

                      in the response you'll find your XSRF token you'll need to use in subsequent requests

                      {\"xsrfToken\":\"myawesometoken\"}%\n
                      "},{"location":"references/rest-apis/rest-session-api/#3-access-the-resource","title":"3. Access the resource","text":"

                      Using the Cookie and the XSRF token you just retrieved you can access your desired resource

                      curl -k -X GET \\\n    -H 'X-XSRF-Token: myawesometoken' \\\n    -b \"JSESSIONID=myawesomecookie\" \\\n    https://$ADDRESS/services/deploy/v2/\n
                      "},{"location":"references/rest-apis/rest-session-api/#update-password-workflow","title":"Update password workflow","text":"
                      1. Get an XSRF token using the GET/xsrfToken endpoint.

                      2. Call the POST/changePassword, providing both the new and old password, make sure to include the X-XSRF-Token HTTP header.

                      3. Repeat the Authentication and resource access workflow, starting from step 1.

                      Sessions will expire after an inactivity interval that can be configured using the Session Inactivity Interval (Seconds) RestService configuration parameter. After session expiration, a new login will be necessary.

                      In order to add protection against XSRF attacks, it is necessary to provide an token using the X-XSRF-Token HTTP header in all requests. The token can be obtained using the GET/xsrfToken endpoint after a successful login.

                      Session will be invalidated if the current identity credentials are changed, in this case a new login will be necessary.

                      If a password change is required for the current identity, it will be necessary to perform the Update password workflow before being able to access the other resources.

                      "},{"location":"references/rest-apis/rest-session-api/#reference","title":"Reference","text":"
                      • Request definitions
                        • POST/login/password
                        • POST/login/certificate
                        • GET/xsrfToken
                        • POST/logout
                        • POST/changePassword
                        • GET/currentIdentity
                        • GET/authenticationMethods
                      • JSON definitions
                        • AuthenticationResponse
                        • UsernamePassword
                        • XSRFToken
                        • PasswordChangeRequest
                        • IdentityInfo
                        • AuthenticationInfo
                        • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-session-api/#postloginpassword","title":"POST/login/password","text":"
                      • REST API path : /services/session/v1/login/password
                      • description : Creates a new session by providing identity name and password. If the response reports that a password change is needed, it is necessary to update the current password in order to be able to access other REST APIs.
                      • request body :
                        • UsernamePassword
                      • responses :
                        • 200
                          • description : Request succeeded.
                          • response body :
                            • AuthenticationResponse
                        • 401
                          • description : The provided credentials are not correct.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#postlogincertificate","title":"POST/login/certificate","text":"
                      • REST API path : /services/session/v1/login/certificate
                      • description : Creates a new session using certificate based authentication. The response will report if the current identity needs a password change for informational purposes only. If authentication is performed using this endpoint, access to other REST APIs will be allowd even if password change is required for the current identity.
                      • responses :
                        • 200
                          • description : Request succeeded.
                          • response body :
                            • AuthenticationResponse
                        • 401
                          • description : The provided credentials are not correct.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#getxsrftoken","title":"GET/xsrfToken","text":"
                      • REST API path : /services/session/v1/xsrfToken
                      • description : Gets the XSRF token associated with the current session. It is not necessary to call this method again until the current session expires, the obtained token is valid as long as the current session is valid.
                      • responses :
                        • 200
                          • description : Request succeeded, the XSRF token is returned in response body.
                          • response body :
                            • XSRFToken
                        • 401
                          • description : The current session is not valid.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#postlogout","title":"POST/logout","text":"
                      • REST API path : /services/session/v1/logout
                      • description : Terminates the current session.
                      • responses :
                        • 204
                          • description : Request succeeded, the session is no longer valid.
                        • 401
                          • description : The current session is not valid
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#postchangepassword","title":"POST/changePassword","text":"
                      • REST API path : /services/session/v1/changePassword
                      • description : Changes the password associated with the current identity. The new password will be validated against the currently configured password strength requirements. The current password strenght requirements can be retrieved using the identity/v1/passwordRequirements endpoint.
                      • request body :
                        • PasswordChangeRequest
                      • responses :
                        • 204
                          • description : Request succeeded. The current password has been changed. The session is no longer valid, a new login is required.
                        • 400
                          • description : The new password is not valid. This can be due to the fact that it does not fullfill the current password strenght requirements.
                        • 401
                          • description : The current session is not valid.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#getcurrentidentity","title":"GET/currentIdentity","text":"
                      • REST API path : /services/session/v1/currentIdentity
                      • description : Provides information about the currently authenticated identity.
                      • responses :
                        • 200
                          • description : Request succeeded
                          • response body :
                            • IdentityInfo
                        • 401
                          • description : The current session is not valid.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#getauthenticationmethods","title":"GET/authenticationMethods","text":"
                      • REST API path : /services/session/v1/authenticationInfo
                      • description : Provides information about the available authentication methods.
                      • responses :
                        • 200
                          • description : Request succeeded
                          • response body :
                            • AuthenticationInfo
                        • 401
                          • description : The current session is not valid.
                        • 500
                          • description : An unexpected error occurred.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"references/rest-apis/rest-session-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-session-api/#authenticationresponse","title":"AuthenticationResponse","text":"

                      Represents the response for a successful authentication request.

                      Properties:

                      • passwordChangeNeeded: bool Determines whether a password change is required for the current identity.
                      {\n  \"passwordChangeNeeded\": true\n}\n
                      "},{"location":"references/rest-apis/rest-session-api/#usernamepassword","title":"UsernamePassword","text":"

                      Contains an username and password.

                      Properties:

                      • username: string The user name.

                      • password: string The user password.

                      {\n  \"password\": \"bar\",\n  \"username\": \"foo\"\n}\n
                      "},{"location":"references/rest-apis/rest-session-api/#xsrftoken","title":"XSRFToken","text":"

                      An object containing an XSRF token.

                      Properties:

                      • xsrfToken: string The XSRF token.
                      {\n  \"xsrfToken\": \"d2b68613-152f-41d5-8b5b-a19448ed0e4e\"\n}\n
                      "},{"location":"references/rest-apis/rest-session-api/#passwordchangerequest","title":"PasswordChangeRequest","text":"

                      An object containing the current password and a new password.

                      Properties:

                      • currentPassword: string The current password.

                      • newPassword: string The new password.

                      {\n  \"currentPassword\": \"foo\",\n  \"newPassword\": \"bar\"\n}\n
                      "},{"location":"references/rest-apis/rest-session-api/#identityinfo","title":"IdentityInfo","text":"

                      An object containing information about the current identity

                      Properties:

                      • name: string The name of the current identity.

                      • passwordChangeNeeded: bool Determines whether a password change is required for the current identity.

                      • permissions: array The list of permissions assigned to the current identity.

                        • array element type: string The permission name.
                      {\n  \"name\": \"foo\",\n  \"passwordChangeNeeded\": false,\n  \"permissions\": [\n    \"rest.bar\",\n    \"rest.assets\",\n    \"rest.foo\"\n  ]\n}\n
                      "},{"location":"references/rest-apis/rest-session-api/#authenticationinfo","title":"AuthenticationInfo","text":"

                      An object containing information about the enabled authentication methods.

                      Properties:

                      • passwordAuthenticationEnabled: bool Reports whether authentication using the login/password endpoint is enabled.

                      • certificateAuthenticationEnabled: bool Reports whether authentication using the login/certificate endpoint is enabled.

                      • certificateAuthenticationPorts: array (optional) The list of ports available for certificate based authentication. This property will be present only if certificateAuthenticationEnabled is true

                        • array element type: string A port that can be used for certificate based authentication.
                      • message: string (optional) Reports the content of the Login Banner, if configured on the device. A browser based application should display this message to the user before login if this property is set. This property will be missing if the login banner is not enabled.

                      {\n  \"certificateAuthenticationEnabled\": false,\n  \"passwordAuthenticationEnabled\": true\n}\n
                      {\n  \"certificateAuthenticationEnabled\": true,\n  \"certificateAuthenticationPorts\": [\n    4443,\n    4444\n  ],\n  \"passwordAuthenticationEnabled\": true\n}\n
                      {\n  \"certificateAuthenticationEnabled\": false,\n  \"message\": \"login banner content\",\n  \"passwordAuthenticationEnabled\": true\n}\n

                      "},{"location":"references/rest-apis/rest-session-api/#genericfailurereport","title":"GenericFailureReport","text":"

                      An object reporting a failure message.

                      Properties:

                      • message: string A message describing the failure.
                      "},{"location":"references/rest-apis/rest-system-api/","title":"System","text":"

                      Note

                      This API can also be accessed via the RequestHandler with app-id: SYS-V1.

                      The SystemService APIs return properties that are divided into 3 categories:

                      • framework properties,
                      • extended properties, and
                      • kura properties.

                      Identities with rest.system permissions can access these APIs.

                      "},{"location":"references/rest-apis/rest-system-api/#framework-properties","title":"Framework properties","text":"Property name Type biosVersion String cpuVersion String deviceName String modelId String modelName String partNumber String platform String numberOfProcessors Integer totalMemory Integer freeMemory Integer serialNumber String javaHome String javaVendor String javaVersion String javaVmInfo String javaVmName String javaVmVersion String javaVmVendor String jdkVendorVersion String osArch String osDistro String osDistroVersion String osName String osVersion String isLegacyBluetoothBeaconScan Boolean isLegacyPPPLoggingEnabled Boolean primaryMacAddress String primaryNetworkInterfaceName String fileSeparator String firmwareVersion String kuraDataDirectory String kuraFrameworkConfigDirectory String kuraHomeDirectory String kuraMarketplaceCompatibilityVersion String kuraSnapshotsCount Integer kuraSnapshotsDirectory String kuraStyleDirectory String kuraTemporaryConfigDirectory String kuraUserConfigDirectory String kuraVersion String kuraHaveWebInterface Boolean kuraHaveNetAdmin Boolean kuraWifiTopChannel Integer kuraDefaultNetVirtualDevicesConfig String osgiFirmwareName String osgiFirmwareVersion String commandUser String commandZipMaxUploadNumber Integer commandZipMaxUploadSize Integer"},{"location":"references/rest-apis/rest-system-api/#kura-properties","title":"Kura properties","text":"Property name Type kura.platform String org.osgi.framework.version String kura.user.config String java.vm.vendor String kura.name String file.command.zip.max.number String kura.legacy.ppp.logging.enabled String kura.tmp String kura.packages String build.version String kura.log.download.journal.fields String kura.data String os.name String dpa.read.timeout String file.upload.size.max String console.device.management.service.ignore String kura.command.user String kura.device.name String kura.partNumber String kura.project String kura.company String java.home String version String kura.style.dir String kura.model.id String file.separator String jdk.vendor.version String kura.model.name String kura.serialNumber.provider String kura.have.web.inter String kura.legacy.bluetooth.beacon.scan String java.runtime.version String kura.bios.version String kura.marketplace.compatibility.version String kura.framework.config String kura.firmware.version String kura.plugins String os.version String kura.version String org.osgi.framework.vendor String java.runtime.name String kura.log.download.sources String os.distribution String java.vm.name String kura.primary.network.interface String kura.home String file.command.zip.max.size String os.arch String os.distribution.version String file.upload.in.memory.size.threshold String kura.net.virtual.devices.config String kura.snapshots String kura.have.net.admin String java.vm.info String java.vm.version String dpa.connection.timeout String build.number String ccs.status.notification.url String

                      Note

                      Some of the listed properties, depending on the device, might not be available.

                      "},{"location":"references/rest-apis/rest-system-api/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-system-api/#get-framework-properties","title":"Get framework properties","text":"
                      • Method: GET
                      • API PATH: /services/system/v1/properties/framework
                      "},{"location":"references/rest-apis/rest-system-api/#responses","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"biosVersion\": \"N/A\",\n    \"cpuVersion\": \"unknown\",\n    \"deviceName\": \"raspberry\",\n    \"modelId\": \"raspberry\",\n    \"modelName\": \"raspberry\",\n    \"partNumber\": \"raspberry\",\n    \"platform\": \"aarch64\",\n    \"numberOfProcessors\": 4,\n    \"totalMemory\": 506816,\n    \"freeMemory\": 380379,\n    \"serialNumber\": \"10000000ba7c7bfd\",\n    \"javaHome\": \"/usr/lib/jvm/java-8-openjdk-armhf/jre\",\n    \"javaVendor\": \"OpenJDK Runtime Environment\",\n    \"javaVersion\": \"1.8.0_312-8u312-b07-1+rpi1-b07\",\n    \"javaVmInfo\": \"mixed mode\",\n    \"javaVmName\": \"OpenJDK Client VM\",\n    \"javaVmVersion\": \"25.312-b07\",\n    \"javaVmVendor\": \"Temurin\",\n    \"jdkVendorVersion\": \"Zulu 8.70.0.24-SA-linux64\",\n    \"osArch\": \"arm\",\n    \"osDistro\": \"Linux\",\n    \"osDistroVersion\": \"N/A\",\n    \"osName\": \"Linux\",\n    \"osVersion\": \"6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023\",\n    \"isLegacyBluetoothBeaconScan\": false,\n    \"isLegacyPPPLoggingEnabled\": true,\n    \"primaryMacAddress\": \"E4:5F:01:35:7F:F4\",\n    \"primaryNetworkInterfaceName\": \"eth0\",\n    \"fileSeparator\": \"/\",\n    \"firmwareVersion\": \"N/A\",\n    \"kuraDataDirectory\": \"/opt/eclipse/kura/data\",\n    \"kuraFrameworkConfigDirectory\": \"/opt/eclipse/kura/framework\",\n    \"kuraHomeDirectory\": \"/opt/eclipse/kura\",\n    \"kuraMarketplaceCompatibilityVersion\": \"5.4.0.SNAPSHOT\",\n    \"kuraSnapshotsCount\": 10,\n    \"kuraSnapshotsDirectory\": \"/opt/eclipse/kura/user/snapshots\",\n    \"kuraStyleDirectory\": \"/opt/eclipse/kura/console/skin\",\n    \"kuraTemporaryConfigDirectory\": \"/tmp/.kura\",\n    \"kuraUserConfigDirectory\": \"/opt/eclipse/kura/user\",\n    \"kuraVersion\": \"KURA_5.4.0-SNAPSHOT\",\n    \"kuraHaveWebInterface\": true,\n    \"kuraHaveNetAdmin\": true,\n    \"kuraWifiTopChannel\": 2147483647,\n    \"kuraDefaultNetVirtualDevicesConfig\": \"netIPv4StatusUnmanaged\",\n    \"osgiFirmwareName\": \"Eclipse\",\n    \"osgiFirmwareVersion\": \"1.10.0\",\n    \"commandUser\": \"kura\",\n    \"commandZipMaxUploadNumber\": 1024,\n    \"commandZipMaxUploadSize\": 100\n}\n
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-system-api/#get-extended-properties","title":"Get extended properties","text":"
                      • Method: GET
                      • API PATH: /services/system/v1/properties/extended
                      "},{"location":"references/rest-apis/rest-system-api/#responses_1","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"version\": \"1.0\",\n    \"extendedProperties\": {\n        \"Device Info 1\": {\n            \"exampleKey1\": \"value1\",\n            \"exampleKey2\": \"value2\"\n        },\n        \"Device Info 2\": {\n            \"key1\": \"val1\",\n            \"key2\": \"val2\"\n        }\n    }\n}\n
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-system-api/#get-kura-properties","title":"Get Kura properties","text":"
                      • Method: GET
                      • API PATH: /services/system/v1/properties/kura
                      "},{"location":"references/rest-apis/rest-system-api/#responses_2","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"kuraProperties\": {\n        \"kura.platform\": \"aarch64\",\n        \"org.osgi.framework.version\": \"1.10.0\",\n        \"kura.user.config\": \"/opt/eclipse/kura/user\",\n        \"java.vm.vendor\": \"Temurin\",\n        \"kura.name\": \"Eclipse Kura\",\n        \"file.command.zip.max.number\": \"1024\",\n        \"kura.legacy.ppp.logging.enabled\": \"true\",\n        \"kura.tmp\": \"/tmp/.kura\",\n        \"kura.packages\": \"/opt/eclipse/kura/packages\",\n        \"build.version\": \"buildNumber\",\n        \"kura.log.download.journal.fields\": \"SYSLOG_IDENTIFIER,PRIORITY,MESSAGE,STACKTRACE\",\n        \"kura.data\": \"/opt/eclipse/kura/data\",\n        \"os.name\": \"Linux\",\n        \"dpa.read.timeout\": \"60000\",\n        \"file.upload.size.max\": \"-1\",\n        \"console.device.management.service.ignore\": \"org.eclipse.kura.net.admin.NetworkConfigurationService,org.eclipse.kura.net.admin.FirewallConfigurationService\",\n        \"kura.command.user\": \"kura\",\n        \"kura.device.name\": \"raspberry\",\n        \"kura.partNumber\": \"raspberry\",\n        \"kura.project\": \"generic-arm32\",\n        \"kura.company\": \"EUROTECH\",\n        \"java.home\": \"/usr/lib/jvm/java-8-openjdk-armhf/jre\",\n        \"version\": \"5.4.0-SNAPSHOT\",\n        \"kura.style.dir\": \"/opt/eclipse/kura/console/skin\",\n        \"kura.model.id\": \"raspberry\",\n        \"file.separator\": \"/\",\n        \"jdk.vendor.version\": \"Zulu 8.70.0.24-SA-linux64\",\n        \"kura.model.name\": \"raspberry\",\n        \"kura.serialNumber.provider\": \"cat /proc/cpuinfo | grep Serial | cut -d ' ' -f 2\",\n        \"kura.have.web.inter\": \"true\",\n        \"kura.legacy.bluetooth.beacon.scan\": \"false\",\n        \"java.runtime.version\": \"1.8.0_312-8u312-b07-1+rpi1-b07\",\n        \"kura.bios.version\": \"N/A\",\n        \"kura.marketplace.compatibility.version\": \"KURA_5.4.0-SNAPSHOT\",\n        \"kura.framework.config\": \"/opt/eclipse/kura/framework\",\n        \"kura.firmware.version\": \"N/A\",\n        \"kura.plugins\": \"/opt/eclipse/kura/plugins\",\n        \"os.version\": \"6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023\",\n        \"kura.version\": \"KURA_5.4.0-SNAPSHOT\",\n        \"org.osgi.framework.vendor\": \"Eclipse\",\n        \"java.runtime.name\": \"OpenJDK Runtime Environment\",\n        \"kura.log.download.sources\": \"/var/log\",\n        \"os.distribution\": \"Linux\",\n        \"java.vm.name\": \"OpenJDK Client VM\",\n        \"kura.primary.network.interface\": \"eth0\",\n        \"kura.home\": \"/opt/eclipse/kura\",\n        \"file.command.zip.max.size\": \"100\",\n        \"os.arch\": \"arm\",\n        \"os.distribution.version\": \"N/A\",\n        \"file.upload.in.memory.size.threshold\": \"10240\",\n        \"kura.net.virtual.devices.config\": \"unmanaged\",\n        \"kura.snapshots\": \"/opt/eclipse/kura/user/snapshots\",\n        \"kura.have.net.admin\": \"true\",\n        \"java.vm.info\": \"mixed mode\",\n        \"java.vm.version\": \"25.312-b07\",\n        \"dpa.connection.timeout\": \"60000\",\n        \"build.number\": \"generic-arm32-buildNumber\",\n        \"ccs.status.notification.url\": \"ccs:log\"\n    }\n}\n
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-system-api/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-system-api/#filter-framework-properties","title":"Filter framework properties","text":"

                      This method allows to retrieve framework-related properties by their name. Available properties are in table Framework properties.

                      • Method: POST
                      • API PATH: /services/system/v1/properties/framework/filter
                      "},{"location":"references/rest-apis/rest-system-api/#request-body","title":"Request Body","text":"
                      {\n    \"names\": [\"deviceName\", \"numberOfProcessors\", \"kuraHaveNetAdmin\"]\n}\n
                      "},{"location":"references/rest-apis/rest-system-api/#responses_3","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"deviceName\": \"RASBPERRY PI 4\",\n    \"numberOfProcessors\": 4,\n    \"kuraHaveNetAdmin\": true\n}\n
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-system-api/#filter-extended-properties","title":"Filter extended properties","text":"

                      This method allows to retrieve the extended properties and to filter them by group name.

                      • Method: POST
                      • API PATH: /services/system/v1/properties/extended/filter
                      "},{"location":"references/rest-apis/rest-system-api/#request-body_1","title":"Request Body","text":"
                      {\n    \"groupNames\": [\"Device Info 1\"]\n}\n
                      "},{"location":"references/rest-apis/rest-system-api/#responses_4","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"extendedProperties\": {\n        \"Device Info 1\": {\n            \"exampleKey1\": \"value1\",\n            \"exampleKey2\": \"value2\"\n        }\n    }\n}\n
                      • 500 Internal Server Error
                      "},{"location":"references/rest-apis/rest-system-api/#filter-kura-properties","title":"Filter kura properties","text":"

                      This method allows to retrieve Kura-related properties (derived from the kura.properties file) and filter by their name. Available properties are listed in the table Kura properties.

                      • Method: POST
                      • API PATH: /services/system/v1/properties/kura/filter
                      "},{"location":"references/rest-apis/rest-system-api/#request-body_2","title":"Request Body","text":"
                      {\n    \"names\": [\"kura.platform\", \"file.upload.size.max\", \"kura.have.net.admin\"]\n}\n
                      "},{"location":"references/rest-apis/rest-system-api/#responses_5","title":"Responses","text":"
                      • 200 OK status
                      {\n    \"kuraProperties\": {\n        \"kura.platform\": \"aarch64\",\n        \"kura.have.net.admin\": \"true\",\n        \"file.upload.size.max\": \"-1\"\n    }\n}\n
                      • 500 Internal Server Error
                      "},{"location":"tutorials/AD-EdgeAI/","title":"Edge AI Anomaly Detection","text":"In\u00a0[1]: Copied!
                      !wget https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv\n
                      !wget https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv
                      --2022-10-18 15:32:34--  https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv\nResolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...\nConnecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.\nHTTP request sent, awaiting response... 200 OK\nLength: 13288126 (13M) [text/plain]\nSaving to: \u2018train-data-raw.csv.1\u2019\n\ntrain-data-raw.csv. 100%[===================>]  12,67M  4,56MB/s    in 2,8s    \n\n2022-10-18 15:32:38 (4,56 MB/s) - \u2018train-data-raw.csv.1\u2019 saved [13288126/13288126]\n\n
                      In\u00a0[2]: Copied!
                      !ls *.csv\n
                      !ls *.csv
                      train-data-raw.csv\n

                      Let's start taking a look at the content of this dataset, we'll use pandas (Python Data Analysis library) for this.

                      In\u00a0[3]: Copied!
                      import pandas as pd\n\nraw_data = pd.read_csv(\"./train-data-raw.csv\")\n\nraw_data.head()\n
                      import pandas as pd raw_data = pd.read_csv(\"./train-data-raw.csv\") raw_data.head() Out[3]: ID TIMESTAMP MAGNET_X TEMP_HUM_timestamp MAGNET_Z MAGNET_Y ACC_Y ACC_X GYRO_Y_timestamp ACC_Z ... PRESSURE_timestamp MAGNET_X_timestamp ACC_X_timestamp GYRO_Z_timestamp HUMIDITY_timestamp assetName ACC_Z_timestamp GYRO_X GYRO_Y GYRO_Z 0 1 1645778791786 -2.680372 1645778791413 5.036951 8.646852 0.004364 0.080122 1645778791413 0.984048 ... 1645778791413 1645778791413 1645778791413 1645778791413 1645778791413 asset-sensehat 1645778791413 0.053243 0.028920 0.036950 1 2 1645778792381 -3.110756 1645778792378 5.952562 10.521458 0.005091 0.080122 1645778792378 0.992090 ... 1645778792378 1645778792378 1645778792378 1645778792378 1645778792378 asset-sensehat 1645778792378 -0.051105 -0.028920 -0.037256 2 3 1645778793412 -3.482263 1645778793408 6.719675 11.944528 0.005334 0.080122 1645778793408 0.986729 ... 1645778793408 1645778793408 1645778793408 1645778793408 1645778793408 asset-sensehat 1645778793408 -0.025253 0.025560 0.038478 3 4 1645778794411 -3.813552 1645778794407 7.375115 13.093461 0.006061 0.080122 1645778794407 0.990384 ... 1645778794407 1645778794407 1645778794407 1645778794407 1645778794407 asset-sensehat 1645778794407 0.100695 -0.023422 -0.037867 4 5 1645778795411 -4.050513 1645778795407 7.854155 14.029530 0.004849 0.080607 1645778795407 0.988922 ... 1645778795407 1645778795407 1645778795407 1645778795407 1645778795407 asset-sensehat 1645778795407 -0.100389 0.021895 0.038172

                      5 rows \u00d7 29 columns

                      In\u00a0[4]: Copied!
                      features = ['ACC_Y', 'ACC_X', 'ACC_Z',\n            'PRESSURE', 'TEMP_PRESS', 'TEMP_HUM',\n            'HUMIDITY', 'GYRO_X', 'GYRO_Y', 'GYRO_Z']\n\ndata = raw_data[features]\n\ndata.head()\n
                      features = ['ACC_Y', 'ACC_X', 'ACC_Z', 'PRESSURE', 'TEMP_PRESS', 'TEMP_HUM', 'HUMIDITY', 'GYRO_X', 'GYRO_Y', 'GYRO_Z'] data = raw_data[features] data.head() Out[4]: ACC_Y ACC_X ACC_Z PRESSURE TEMP_PRESS TEMP_HUM HUMIDITY GYRO_X GYRO_Y GYRO_Z 0 0.004364 0.080122 0.984048 992.322998 38.724998 40.330822 19.487146 0.053243 0.028920 0.036950 1 0.005091 0.080122 0.992090 992.288330 38.772915 40.385788 19.465750 -0.051105 -0.028920 -0.037256 2 0.005334 0.080122 0.986729 992.275635 38.795834 40.349144 19.572731 -0.025253 0.025560 0.038478 3 0.006061 0.080122 0.990384 992.279053 38.797916 40.330822 19.358767 0.100695 -0.023422 -0.037867 4 0.004849 0.080607 0.988922 992.333008 38.845833 40.385788 19.390862 -0.100389 0.021895 0.038172 In\u00a0[5]: Copied!
                      %matplotlib inline\nimport matplotlib.pyplot as plt\n\ndata.hist(bins=50, figsize=(20,15))\nplt.show()\n
                      %matplotlib inline import matplotlib.pyplot as plt data.hist(bins=50, figsize=(20,15)) plt.show()

                      Note: Some of you might notice that this is a really simple dataset: some of the input data (like GYRO_* and ACC_*) do not change much over time. Such a dataset is not very challenging and a few, well-placed, thresholds might be sufficient to spot anomalous behaviour. For this tutorial we decided to keep things simple and easy to replicate. Anomalies can be simply triggered by moving the Raspberry Pi around.

                      Keep in mind that this approach is generic: any dataset from any appliance/connected device can be processed in the same way we're showing here. That's the magic of neural networks!

                      In\u00a0[6]: Copied!
                      print(\"Data used in the Triton preprocessor\")\nprint(\"-----------Min-----------\")\nprint(data.min())\nprint(\"-----------Max-----------\")\nprint(data.max())\nprint(\"-------------------------\")\n
                      print(\"Data used in the Triton preprocessor\") print(\"-----------Min-----------\") print(data.min()) print(\"-----------Max-----------\") print(data.max()) print(\"-------------------------\")
                      Data used in the Triton preprocessor\n-----------Min-----------\nACC_Y          -0.132551\nACC_X          -0.049693\nACC_Z           0.759847\nPRESSURE      976.001709\nTEMP_PRESS     38.724998\nTEMP_HUM       40.220890\nHUMIDITY       13.003981\nGYRO_X         -1.937896\nGYRO_Y         -0.265019\nGYRO_Z         -0.250647\ndtype: float64\n-----------Max-----------\nACC_Y            0.093099\nACC_X            0.150289\nACC_Z            1.177543\nPRESSURE      1007.996338\nTEMP_PRESS      46.093750\nTEMP_HUM        48.355824\nHUMIDITY        23.506138\nGYRO_X           1.923712\nGYRO_Y           0.219204\nGYRO_Z           0.671759\ndtype: float64\n-------------------------\n
                      In\u00a0[7]: Copied!
                      from sklearn.preprocessing import MinMaxScaler\n\nscaler = MinMaxScaler()\nscaled_data = scaler.fit_transform(data.to_numpy())\n
                      from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() scaled_data = scaler.fit_transform(data.to_numpy()) In\u00a0[8]: Copied!
                      pd.DataFrame(scaled_data).describe()\n
                      pd.DataFrame(scaled_data).describe() Out[8]: 0 1 2 3 4 5 6 7 8 9 count 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 mean 0.603124 0.674196 0.550454 0.526446 0.605576 0.552252 0.466400 0.501160 0.545457 0.271295 std 0.049333 0.015135 0.031627 0.054050 0.288300 0.256587 0.176293 0.062908 0.067678 0.014665 min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 25% 0.597087 0.667343 0.544924 0.481917 0.501060 0.441442 0.325637 0.501348 0.544670 0.270709 50% 0.603534 0.673413 0.551342 0.521377 0.655357 0.608108 0.511715 0.501841 0.547096 0.271685 75% 0.611055 0.680698 0.555426 0.552892 0.819339 0.734234 0.575212 0.502407 0.549386 0.272577 max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 In\u00a0[9]: Copied!
                      from sklearn.model_selection import train_test_split\nimport numpy as np\n\nx_train, x_test = train_test_split(scaled_data, test_size=0.3, random_state=42)\nx_train = x_train.astype(np.float32)\nx_test = x_test.astype(np.float32)\n
                      from sklearn.model_selection import train_test_split import numpy as np x_train, x_test = train_test_split(scaled_data, test_size=0.3, random_state=42) x_train = x_train.astype(np.float32) x_test = x_test.astype(np.float32) In\u00a0[10]: Copied!
                      import os\nos.environ['TF_CPP_MIN_LOG_LEVEL']='2' # Avoid AVX2 error\n\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Input, Dense, Dropout\n\ndef create_model(input_dim):\n    # The encoder will consist of a number of dense layers that decrease in size\n    # as we taper down towards the bottleneck of the network, the latent space\n    input_data = Input(shape=(input_dim,), name='INPUT0')\n\n    # hidden layers\n    encoder = Dense(9, activation='tanh', name='encoder_1')(input_data)\n    encoder = Dropout(.15)(encoder)\n    encoder = Dense(6, activation='tanh', name='encoder_2')(encoder)\n    encoder = Dropout(.15)(encoder)\n\n    # bottleneck layer\n    latent_encoding = Dense(3, activation='linear', name='latent_encoding')(encoder)\n\n    # The decoder network is a mirror image of the encoder network\n    decoder = Dense(6, activation='tanh', name='decoder_1')(latent_encoding)\n    decoder = Dropout(.15)(decoder)\n    decoder = Dense(9, activation='tanh', name='decoder_2')(decoder)\n    decoder = Dropout(.15)(decoder)\n\n    # The output is the same dimension as the input data we are reconstructing\n    reconstructed_data = Dense(input_dim, activation='linear', name='OUTPUT0')(decoder)\n\n    autoencoder_model = Model(input_data, reconstructed_data)\n\n    return autoencoder_model\n
                      import os os.environ['TF_CPP_MIN_LOG_LEVEL']='2' # Avoid AVX2 error from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Dense, Dropout def create_model(input_dim): # The encoder will consist of a number of dense layers that decrease in size # as we taper down towards the bottleneck of the network, the latent space input_data = Input(shape=(input_dim,), name='INPUT0') # hidden layers encoder = Dense(9, activation='tanh', name='encoder_1')(input_data) encoder = Dropout(.15)(encoder) encoder = Dense(6, activation='tanh', name='encoder_2')(encoder) encoder = Dropout(.15)(encoder) # bottleneck layer latent_encoding = Dense(3, activation='linear', name='latent_encoding')(encoder) # The decoder network is a mirror image of the encoder network decoder = Dense(6, activation='tanh', name='decoder_1')(latent_encoding) decoder = Dropout(.15)(decoder) decoder = Dense(9, activation='tanh', name='decoder_2')(decoder) decoder = Dropout(.15)(decoder) # The output is the same dimension as the input data we are reconstructing reconstructed_data = Dense(input_dim, activation='linear', name='OUTPUT0')(decoder) autoencoder_model = Model(input_data, reconstructed_data) return autoencoder_model In\u00a0[11]: Copied!
                      autoencoder_model = create_model(len(features))\nautoencoder_model.summary()\n
                      autoencoder_model = create_model(len(features)) autoencoder_model.summary()
                      Model: \"model\"\n_________________________________________________________________\n Layer (type)                Output Shape              Param #   \n=================================================================\n INPUT0 (InputLayer)         [(None, 10)]              0         \n                                                                 \n encoder_1 (Dense)           (None, 9)                 99        \n                                                                 \n dropout (Dropout)           (None, 9)                 0         \n                                                                 \n encoder_2 (Dense)           (None, 6)                 60        \n                                                                 \n dropout_1 (Dropout)         (None, 6)                 0         \n                                                                 \n latent_encoding (Dense)     (None, 3)                 21        \n                                                                 \n decoder_1 (Dense)           (None, 6)                 24        \n                                                                 \n dropout_2 (Dropout)         (None, 6)                 0         \n                                                                 \n decoder_2 (Dense)           (None, 9)                 63        \n                                                                 \n dropout_3 (Dropout)         (None, 9)                 0         \n                                                                 \n OUTPUT0 (Dense)             (None, 10)                100       \n                                                                 \n=================================================================\nTotal params: 367\nTrainable params: 367\nNon-trainable params: 0\n_________________________________________________________________\n

                      In\u00a0[12]: Copied!
                      from tensorflow.keras import optimizers\n\nbatch_size = 32\nmax_epochs = 15\nlearning_rate = .0001\n\nopt = optimizers.Adam(learning_rate=learning_rate)\nautoencoder_model.compile(optimizer=opt, loss='mse', metrics=['accuracy'])\ntrain_history = autoencoder_model.fit(x_train, x_train,\n                      shuffle=True,\n                      epochs=max_epochs,\n                      batch_size=batch_size,\n                      validation_data=(x_test, x_test))\n
                      from tensorflow.keras import optimizers batch_size = 32 max_epochs = 15 learning_rate = .0001 opt = optimizers.Adam(learning_rate=learning_rate) autoencoder_model.compile(optimizer=opt, loss='mse', metrics=['accuracy']) train_history = autoencoder_model.fit(x_train, x_train, shuffle=True, epochs=max_epochs, batch_size=batch_size, validation_data=(x_test, x_test))
                      Epoch 1/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.2282 - accuracy: 0.1129 - val_loss: 0.0922 - val_accuracy: 0.0045\nEpoch 2/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0949 - accuracy: 0.1541 - val_loss: 0.0279 - val_accuracy: 0.4210\nEpoch 3/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0613 - accuracy: 0.1779 - val_loss: 0.0206 - val_accuracy: 0.4426\nEpoch 4/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0466 - accuracy: 0.2152 - val_loss: 0.0186 - val_accuracy: 0.5276\nEpoch 5/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0366 - accuracy: 0.2514 - val_loss: 0.0157 - val_accuracy: 0.5944\nEpoch 6/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0290 - accuracy: 0.3083 - val_loss: 0.0119 - val_accuracy: 0.6403\nEpoch 7/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0228 - accuracy: 0.3930 - val_loss: 0.0078 - val_accuracy: 0.7182\nEpoch 8/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0186 - accuracy: 0.4668 - val_loss: 0.0059 - val_accuracy: 0.8195\nEpoch 9/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0157 - accuracy: 0.5021 - val_loss: 0.0048 - val_accuracy: 0.8256\nEpoch 10/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0136 - accuracy: 0.5277 - val_loss: 0.0042 - val_accuracy: 0.8263\nEpoch 11/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0121 - accuracy: 0.5409 - val_loss: 0.0037 - val_accuracy: 0.8296\nEpoch 12/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0107 - accuracy: 0.5569 - val_loss: 0.0036 - val_accuracy: 0.8306\nEpoch 13/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0098 - accuracy: 0.5857 - val_loss: 0.0034 - val_accuracy: 0.8256\nEpoch 14/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0089 - accuracy: 0.6076 - val_loss: 0.0033 - val_accuracy: 0.8281\nEpoch 15/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0083 - accuracy: 0.6337 - val_loss: 0.0032 - val_accuracy: 0.8262\n
                      In\u00a0[13]: Copied!
                      plt.plot(train_history.history['loss'])\nplt.plot(train_history.history['val_loss'])\nplt.legend(['loss on train data', 'loss on test data'])\n
                      plt.plot(train_history.history['loss']) plt.plot(train_history.history['val_loss']) plt.legend(['loss on train data', 'loss on test data']) Out[13]:
                      <matplotlib.legend.Legend at 0x16d0c3400>

                      Here we can see the loss for the training set and the test set on the epochs.

                      Some of you might notice that this graph is somewhat unexpected. Why the validation loss is lower than the train loss? This is the effect of the regularization: regularization terms and dropout layer are affecting the network during training. A good writeup of this effect can be found here.

                      As an excercise try and compute the average MSE on the training set and the test set. You'll find that the MSE is lower in the training set!

                      We can now save the model on disk as we'll use this later.

                      In\u00a0[14]: Copied!
                      autoencoder_model.save(\"./saved_model/autoencoder\")\n
                      autoencoder_model.save(\"./saved_model/autoencoder\")
                      WARNING:absl:Function `_wrapped_model` contains input name(s) INPUT0 with unsupported characters which will be renamed to input0 in the SavedModel.\n
                      INFO:tensorflow:Assets written to: ./saved_model/autoencoder/assets\n
                      INFO:tensorflow:Assets written to: ./saved_model/autoencoder/assets\n
                      In\u00a0[15]: Copied!
                      !ls ./saved_model/autoencoder\n
                      !ls ./saved_model/autoencoder
                      assets            keras_metadata.pb saved_model.pb    variables\n
                      In\u00a0[16]: Copied!
                      input_sample = x_test[3:4].copy() # Deep copy\n\nreconstructed_sample = autoencoder_model.predict(input_sample)\n\nprint(input_sample)\nprint(reconstructed_sample)\n
                      input_sample = x_test[3:4].copy() # Deep copy reconstructed_sample = autoencoder_model.predict(input_sample) print(input_sample) print(reconstructed_sample)
                      1/1 [==============================] - 0s 109ms/step\n[[0.603534   0.6770555  0.54900813 0.5327966  0.6680801  0.6171171\n  0.5198642  0.50135666 0.54716927 0.2718224 ]]\n[[0.59638697 0.67410123 0.5484349  0.52024144 0.64766663 0.5916597\n  0.4445051  0.499677   0.54471916 0.26904327]]\n
                      In\u00a0[17]: Copied!
                      import matplotlib.pyplot as plt\n\nindex = np.arange(10)\nbar_width = 0.35\n\nfigure, ax = plt.subplots()\n\ninbar = ax.bar(index, input_sample[0], bar_width, label=\"Input data\")\nrecbar = ax.bar(index+bar_width, reconstructed_sample[0], bar_width, label=\"Reconstruced data\")\n\nax.set_xlabel('Features')\nax.set_xticks(index + bar_width / 2)\nax.set_xticklabels(features, rotation = 45)\nax.legend()\n
                      import matplotlib.pyplot as plt index = np.arange(10) bar_width = 0.35 figure, ax = plt.subplots() inbar = ax.bar(index, input_sample[0], bar_width, label=\"Input data\") recbar = ax.bar(index+bar_width, reconstructed_sample[0], bar_width, label=\"Reconstruced data\") ax.set_xlabel('Features') ax.set_xticks(index + bar_width / 2) ax.set_xticklabels(features, rotation = 45) ax.legend() Out[17]:
                      <matplotlib.legend.Legend at 0x16d184880>

                      As we can see from the graph above it reconstructed the input fairly well. It is not perfect since the Autoencoder is lossy but it is good enough

                      What happens if we manipulate this sample in a way the autoencoder doesn't expect (i.e. we introduce an anomaly)?

                      Let's try and set the ACC_Z to a value the autoencoder has never seen before.

                      In\u00a0[18]: Copied!
                      input_anomaly = input_sample.copy() # Deep copy\n\ninput_anomaly[0][2] = 0.15\n\nreconstructed_anomaly = autoencoder_model.predict(input_anomaly)\n\nprint(input_anomaly)\nprint(reconstructed_anomaly)\n
                      input_anomaly = input_sample.copy() # Deep copy input_anomaly[0][2] = 0.15 reconstructed_anomaly = autoencoder_model.predict(input_anomaly) print(input_anomaly) print(reconstructed_anomaly)
                      1/1 [==============================] - 0s 21ms/step\n[[0.603534   0.6770555  0.15       0.5327966  0.6680801  0.6171171\n  0.5198642  0.50135666 0.54716927 0.2718224 ]]\n[[0.60162103 0.69035804 0.55594885 0.51874125 0.7346029  0.6700014\n  0.40932336 0.5034408  0.5424664  0.26861513]]\n
                      In\u00a0[19]: Copied!
                      figure, ax = plt.subplots()\n\ninbar = ax.bar(index, input_anomaly[0], bar_width, label=\"Input anomaly\")\nrecbar = ax.bar(index+bar_width, reconstructed_anomaly[0], bar_width, label=\"Reconstruced anomaly\")\n\nax.set_xlabel('Features')\nax.set_xticks(index + bar_width / 2)\nax.set_xticklabels(features, rotation = 45)\nax.legend()\n
                      figure, ax = plt.subplots() inbar = ax.bar(index, input_anomaly[0], bar_width, label=\"Input anomaly\") recbar = ax.bar(index+bar_width, reconstructed_anomaly[0], bar_width, label=\"Reconstruced anomaly\") ax.set_xlabel('Features') ax.set_xticks(index + bar_width / 2) ax.set_xticklabels(features, rotation = 45) ax.legend() Out[19]:
                      <matplotlib.legend.Legend at 0x16d3c0220>

                      The autoencoder fails to reconstruct the data it received at the input. This means that the reconstruction error is very high.

                      In\u00a0[20]: Copied!
                      from sklearn.metrics import mean_squared_error\n\nprint(\"Anomaly %f\"% mean_squared_error(input_anomaly[0], reconstructed_anomaly[0]))\nprint(\"Normal  %f\"% mean_squared_error(input_sample[0], reconstructed_sample[0]))\n
                      from sklearn.metrics import mean_squared_error print(\"Anomaly %f\"% mean_squared_error(input_anomaly[0], reconstructed_anomaly[0])) print(\"Normal %f\"% mean_squared_error(input_sample[0], reconstructed_sample[0]))
                      Anomaly 0.018465\nNormal  0.000698\n

                      It's working as expected!

                      We now need to decide when to trigger an alarm (i.e. classify an input sample as anomalous) from this reconstruction error. In other words we need to decide our threshold.

                      There are multiple ways to set this value, in this example we'll use the Z-Score.

                      From Wikipedia:

                      In statistics, the standard score is the number of standard deviations by which the value of a raw score (i.e., an observed value or data point) is above or below the mean value of what is being observed or measured.[...]

                      It is calculated by subtracting the population mean from an individual raw score and then dividing the difference by the population standard deviation.

                      We'll consider a sample an anomaly if the Reconstruction Error Z-Score is not in the range [-2, +2]. This means that if the reconstruction error for a sample is more than 2 standard deviation away from the average reconstruction error computed on the test set, the sample is an anomaly. This choice is arbirtary, we can control the sensitivity of the detector by changing this range.

                      In\u00a0[21]: Copied!
                      x_test_recon = autoencoder_model.predict(x_test)\nreconstruction_scores = np.mean((x_test - x_test_recon)**2, axis=1)  # MSE\n\nreconstruction_scores_pd = pd.DataFrame({'recon_score': reconstruction_scores})\nprint(reconstruction_scores_pd.describe())\n
                      x_test_recon = autoencoder_model.predict(x_test) reconstruction_scores = np.mean((x_test - x_test_recon)**2, axis=1) # MSE reconstruction_scores_pd = pd.DataFrame({'recon_score': reconstruction_scores}) print(reconstruction_scores_pd.describe())
                      237/237 [==============================] - 0s 620us/step\n       recon_score\ncount  7584.000000\nmean      0.003175\nstd       0.005438\nmin       0.000098\n25%       0.000816\n50%       0.001211\n75%       0.002108\nmax       0.106237\n
                      In\u00a0[22]: Copied!
                      def z_score(mse_sample):\n    return (mse_sample - reconstruction_scores_pd.mean())/reconstruction_scores_pd.std()\n
                      def z_score(mse_sample): return (mse_sample - reconstruction_scores_pd.mean())/reconstruction_scores_pd.std() In\u00a0[23]: Copied!
                      mse_anomaly = mean_squared_error(input_anomaly[0], reconstructed_anomaly[0])\nmse_normal = mean_squared_error(input_sample[0], reconstructed_sample[0])\n\nz_score_anomaly = z_score(mse_anomaly)\nz_score_normal = z_score(mse_normal)\n\nprint(\"Anomaly Z-score %f\"% z_score_anomaly)\nprint(\"Normal Z-score %f\"% z_score_normal)\n
                      mse_anomaly = mean_squared_error(input_anomaly[0], reconstructed_anomaly[0]) mse_normal = mean_squared_error(input_sample[0], reconstructed_sample[0]) z_score_anomaly = z_score(mse_anomaly) z_score_normal = z_score(mse_normal) print(\"Anomaly Z-score %f\"% z_score_anomaly) print(\"Normal Z-score %f\"% z_score_normal)
                      Anomaly Z-score 2.811887\nNormal Z-score -0.455488\n

                      We now have our anomaly detector... let's see how we can deploy it on our Kura\u2122-powered edge device.

                      In\u00a0[24]: Copied!
                      !rm -rf ./tf_autoencoder_fp32/ && mkdir -p ./tf_autoencoder_fp32/1\n
                      !rm -rf ./tf_autoencoder_fp32/ && mkdir -p ./tf_autoencoder_fp32/1 In\u00a0[25]: Copied!
                      !ls\n
                      !ls
                      AD-EdgeAI.ipynb      requirements.txt     train-data-raw.csv\nREADME.md            saved_model          train-data-raw.csv.1\nimgs                 tf_autoencoder_fp32\n
                      In\u00a0[26]: Copied!
                      cp -r ./saved_model/autoencoder tf_autoencoder_fp32/1/model.savedmodel\n
                      cp -r ./saved_model/autoencoder tf_autoencoder_fp32/1/model.savedmodel In\u00a0[27]: Copied!
                      !tree tf_autoencoder_fp32\n
                      !tree tf_autoencoder_fp32
                      tf_autoencoder_fp32\n\u2514\u2500\u2500 1\n    \u2514\u2500\u2500 model.savedmodel\n        \u251c\u2500\u2500 assets\n        \u251c\u2500\u2500 keras_metadata.pb\n        \u251c\u2500\u2500 saved_model.pb\n        \u2514\u2500\u2500 variables\n            \u251c\u2500\u2500 variables.data-00000-of-00001\n            \u2514\u2500\u2500 variables.index\n\n4 directories, 4 files\n

                      Now comes the hard part: we need to provide the model configuration (i.e. the config.pbtxt file). In the case of the autoencoder is pretty simple:

                      name: \"tf_autoencoder_fp32\"\nbackend: \"tensorflow\"\nmax_batch_size: 0\ninput [\n    {\n    name: \"INPUT0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n    }\n]\noutput [\n    {\n    name: \"OUTPUT0\"\n    data_type: TYPE_FP32\n    dims: [ -1, 10 ]\n    }\n]\nversion_policy: { all { }}\ninstance_group [{ kind: KIND_CPU }]\n

                      Each model input and output must specify the name, data_type and dims. We already know all of these:

                      • name: corresponds to the layer name we've seen in the Model Training section. INPUT0 for the input and OUTPUT0 for the output.
                      • data_type: will be float since we didn't perform any quantization
                      • dims: is the shape of the in/out tensor. In this case it will correspond to an array with the same length as the number of features.

                      Other interesting parameters of this configuration are:

                      • backend: where we set the backend for the model. In this case it will be the Tensorflow backend
                      • name: the name of the model that must correspond to the name of the folder
                      • instance_group: where we set where we want the model to run. In this case we'll use the CPU since we're on a Raspberry Pi but keep in mind that Triton support multiple accelerators.

                      for a deep dive into the model configuration parameter take a look at the official documentation.

                      "},{"location":"tutorials/AD-EdgeAI/#edge-ai-anomaly-detection","title":"Edge AI Anomaly Detection\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                      This document contains the code and the instructions for our EclipseCON 2022 Talk: \"How to Train Your Dragon and Its Friends: AI on the Edge with Eclipse Kura\u2122\"

                      This notebook can also be viewed and ran on Google Colab.

                      In this example scenario we will collect the data provided by a Raspberry Pi Sense HAT using Eclipse Kura\u2122 and upload them to a Eclipse Kapua\u2122 instance. We will then download this data and train an AI-based anomaly detector using TensorFlow. Finally we will deploy the trained anomaly detector model leveraging Nvidia Triton\u2122 Inference Server and Eclipse Kura\u2122 integration.

                      We'll subdivide this example scenario in three main sections:

                      1. Data collection: in this section we'll discuss how to retrieve training data from the field leveraging Eclipse Kura\u2122 and Eclipse Kapua\u2122
                      2. Model building and training: we'll further divide this section in three subsections:
                        • Data processing: where we'll show how to explore our training data and manipulate them to make them suitable for training (feature selection, scaling and dataset splitting). This will provide us with the \"Preprocessing\" stage of the resulting AI data-processing pipeline
                        • Model training: where we'll discuss how we can create a simple Autoencoder in Tensorflow Keras and how to train it. This will provide us with the \"Inference\" stage of the AI pipeline
                        • Model evaluation: where we'll cover how can we extract the high level data from the model output and ensure the model was trained correctly. This will provide us with the \"Postprocessing\" stage of the AI pipeline
                      3. Model deployment: finally we will convert the model to make it suitable for running on Eclipse Kura\u2122 and Nvidia Triton\u2122 and deploy it on the edge.
                      "},{"location":"tutorials/AD-EdgeAI/#data-collection","title":"Data collection\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                      In this setup we'll leverage Eclipe Kura\u2122 and Kapua\u2122 for retrieving data from a Raspberry Pi Sense HAT and upload them to the cloud.

                      The Sense HAT is an add-on board for Raspberry Pi which provides an 8\u00d78 RGB LED matrix, a five-button joystick and includes the following sensors:

                      • Gyroscope
                      • Accelerometer
                      • Magnetometer
                      • Temperature
                      • Barometric pressure
                      • Humidity

                      "},{"location":"tutorials/AD-EdgeAI/#kuratm-installation","title":"Kura\u2122 installation\u00b6","text":"

                      Requirement: A Raspberry Pi 3/4 running the latest version of Raspberry Pi OS 64 bit.

                      To make everything work on the Raspberry Pi we need to use the develop version of the raspberry-pi-ubuntu-20-nn Kura installer (yes, I know we're installing the Ubuntu package on the Raspberry Pi OS but bear with me...) . You can do so by downloading the repo and building locally or by downloading a pre-built installer from the Kura CI artifacts.

                      Copy the resulting file kura_<version>_raspberry-pi-ubuntu-20_installer-nn.deb on the target device.

                      On the target device run the following commands:

                      sudo apt-get install -y wget apt-transport-https gnupg\n
                      sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
                      sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
                      sudo apt-get update && sudo apt-get install temurin-8-jdk chrony\n

                      Finally install Kura with:

                      sudo apt install ./kura_<version>_raspberry-pi-ubuntu-20_installer-nn.deb\n
                      "},{"location":"tutorials/AD-EdgeAI/#cloud-connection","title":"Cloud connection\u00b6","text":"

                      After setting up an Eclipse Kura\u2122 instance on the Raspberry Pi we'll need to connect it to an Eclipse Kapua\u2122 instance.

                      An excellent tutorial on how to deploy a Kapua\u2122 instance using Docker is available in the official repository. For the purpose of this tutorial we'll assume a Kapua\u2122 instance is already running and is available for connection from Kura\u2122

                      After setting up the Kapua\u2122 instance you can refer to the official Kura\u2122 documentation for connecting the Raspberry Pi to the Kapua\u2122 instance. For the remaining of this tutorial we'll assume a connection with the Kapua\u2122 was correctly established.

                      "},{"location":"tutorials/AD-EdgeAI/#data-publisher","title":"Data publisher\u00b6","text":"

                      To publish the collected data on the Cloud we'll need to create a new Cloud Publisher through the Kura\u2122 web interface. Go to \"Cloud Connections\" and press \"New Pub/Sub\", in the example below we'll call our new publisher KapuaSenseHatPublisher.

                      To keep things clean we'll create a new topic called SenseHat. To do so we'll move to the KapuaSenseHatPublisher configuration and we'll update the Application Topic field to A1/SenseHat

                      "},{"location":"tutorials/AD-EdgeAI/#sensehat-driver","title":"SenseHat driver\u00b6","text":"

                      Kura\u2122 provides a driver that allows to interact to a RaspberryPi SenseHat device using Kura Driver, Asset and Wires frameworks.

                      From the Kura\u2122 documentation:

                      Eclipse Kura introduces a model based on the concepts of Drivers and Assets to simplify the communication with the field devices attached to a gateway.

                      A Driver encapsulates the communication protocol and its configuration parameters, dealing with the low-level characteristics of the field protocol. It opens, closes and performs the communication with the end field device. It also exposes field protocol specific information that can be used by upper levels of abstraction to simplify the interaction with the end devices.

                      An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

                      The Kura Sense Hat driver requires a few changes on the Raspberry Pi:

                      • Configured SenseHat: see SenseHat documentation
                      • I2C interface should be unlocked using sudo raspi-config

                      As others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace. It consists of two packages:

                      • SenseHat Example Driver.
                      • SenseHat Support Library

                      We need to install both. Complete installation instructions are available here.

                      "},{"location":"tutorials/AD-EdgeAI/#driver-configuration","title":"Driver configuration\u00b6","text":"

                      We now need to configure the driver to access the sensors on the SenseHat. Move to the \"Driver and Assets\" section of the web UI and create a new driver. We'll call it driver-sensehat.

                      Then add a new Asset (which we'll call asset-sensehat) to this driver and configure it as per the screenshots below. We'll need a Channel for every sensor we want to access.

                      Refer to the following table for the driver parameters:

                      name type value.type resource ACC_X READ FLOAT ACCELERATION_X ACC_Y READ FLOAT ACCELERATION_Y ACC_Z READ FLOAT ACCELERATION_Z GYRO_X READ FLOAT GYROSCOPE_X GYRO_Y READ FLOAT GYROSCOPE_Y GYRO_Z READ FLOAT GYROSCOPE_Z HUMIDITY READ FLOAT HUMIDITY PRESSURE READ FLOAT PRESSURE TEMP_HUM READ FLOAT TEMPERATURE_FROM_HUMIDITY TEMP_PRESS READ FLOAT TEMPERATURE_FROM_PRESSURE

                      After correctly configuring it you should see the data in the \"Data\" page of the UI.

                      "},{"location":"tutorials/AD-EdgeAI/#wire-graph","title":"Wire graph\u00b6","text":"

                      Now that we have our Driver and Cloud Publisher ready we can put everything together with a Kura Wire Graph.

                      From Kura\u2122 documentation:

                      The Kura\u2122 Wires feature aims to simplify the development of IoT Edge Computing Applications leveraging reusable configurable components that can be wired together and which, eventually, allows configurable cooperation between these components.

                      In the dataflow programming model, the application logic is expressed as a directed graph (flow) where each node can have inputs, outputs, and independent processing units. There are nodes that only produce outputs and ones that only consume inputs, which usually represent the start and the end of the flow. The inner-graph nodes process the inputs and produce outputs for downstream nodes. The processing unit of a node executes independently and does not affect the execution of other nodes. Thus, the nodes are highly reusable and portable.

                      Move to the \"Wire Graph\" section of the UI. We'll need a graph with three components:

                      • A Timer which will dictate the sample rate at which we will collect data coming from the Sense Hat
                      • A WireAsset for the Sense Hat driver asset
                      • A Publisher for the Kapua publisher we created before.

                      The resulting Wire Graph will look like this:

                      "},{"location":"tutorials/AD-EdgeAI/#timer","title":"Timer\u00b6","text":"

                      Configure the timer such that it will poll the SenseHat each second, this can be done by setting the simple.interval to 1.

                      "},{"location":"tutorials/AD-EdgeAI/#wireasset","title":"WireAsset\u00b6","text":"

                      Select the driver-sensehat when creating the WireAsset. No further configuration is needed for this component.

                      "},{"location":"tutorials/AD-EdgeAI/#publisher","title":"Publisher\u00b6","text":"

                      Create a \"Publisher\" Wire component and select the KapuaSensehatPublisher from the target filter.

                      Don't forget to press \"Apply\" to start the Wire Graph!

                      "},{"location":"tutorials/AD-EdgeAI/#collect-the-data","title":"Collect the data\u00b6","text":"

                      At this point you should see data coming from the Rasperry Pi from the Kapua\u2122 console under the SenseHat topic.

                      You can download the .csv file directly from the console using the \"Export to CSV\" button.

                      "},{"location":"tutorials/AD-EdgeAI/#model-building-and-training","title":"Model building and training\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                      We will now use the data collected in the previous section to train an artificial neural network-based Anomaly Detector of our design. To this end we will use an Autoencoder model. To understand why we choose such model we need to understand how it works. From Wikipedia:

                      An autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data (unsupervised learning). The encoding is validated and refined by attempting to regenerate the input from the encoding. The autoencoder learns a representation (encoding) for a set of data, typically for dimensionality reduction, by training the network to ignore insignificant data (\u201cnoise\u201d).

                      Another application for autoencoders is anomaly detection. By learning to replicate the most salient features in the training data [...] the model is encouraged to learn to precisely reproduce the most frequently observed characteristics. When facing anomalies, the model should worsen its reconstruction performance. In most cases, only data with normal instances are used to train the autoencoder; in others, the frequency of anomalies is small compared to the observation set so that its contribution to the learned representation could be ignored. After training, the autoencoder will accurately reconstruct \"normal\" data, while failing to do so with unfamiliar anomalous data. Reconstruction error (the error between the original data and its low dimensional reconstruction) is used as an anomaly score to detect anomalies

                      In simple terms:

                      • The Autoencoder is a artificial neural network model that learns how to reconstruct the input data at the output.
                      • If trained on \"normal\" data, it learns to recontruct only normal data and fails to reconstruct anomalies.
                      • We can detect anomalies by computing the reconstruction error of the Autoencoder. If the error is above a certain threshold (which we will decide) the input sample is an anomaly.

                      Why did we choose this approach over others?

                      • The Autoencoder falls in the \"Unsupervised Learning\" category: it doesn't need labeled data to be trained i.e. we don't need to go through all the dataset and manually label the samples as \"normal\" or \"anomaly\" (Supervised Learning).
                      • Simpler data collection: we just need to provide it with the \"normal\" data. We don't need to artificially generate anomalies to train it on them.
                      "},{"location":"tutorials/AD-EdgeAI/#data-processing","title":"Data Processing\u00b6","text":"

                      We can now work on our .csv file downloaded from Kapua. For demonstration purposes an already available dataset is provided within this repository.

                      If you're running this notebook through Google Colab you'll need to download the dataset running the cell below:

                      "},{"location":"tutorials/AD-EdgeAI/#feature-selection","title":"Feature selection\u00b6","text":"

                      As you might notice there's some information in the dataset we don't care about and are not meaningful for our application:

                      • ID
                      • The various timestamps
                      • assetName which doesn't change

                      Then we can remove them from the dataset.

                      "},{"location":"tutorials/AD-EdgeAI/#feature-scaling","title":"Feature scaling\u00b6","text":"

                      AI models don't perform well when the input numerical attributes have very different scales. As you can see ACC_X, ACC_Y and ACC_Z range from 0 to 1, while the PRESSURE have far higher values.

                      There are two common ways to address this: normalization and standardization.

                      Normalization (a.k.a. Min-max scaling) shifts and rescales values so that they end up ranging from 0 to 1. This can be done by subtracting the min value and dividing by the max minus the min.

                      x' = $\\frac{x - min(x)}{max(x) - min(x)}$

                      Standardization makes the values of each feature in the data have zero-mean (when subtracting the mean in the numerator) and unit-variance. The general method of calculation is to determine the distribution mean and standard deviation for each feature. Next we subtract the mean from each feature. Then we divide the values (mean is already subtracted) of each feature by its standard deviation.

                      x' = $\\frac{x - avg(x)}{\\sigma}$

                      Fortunately for us scikit-learn library provides a function for both of them. In this case we'll use normalization because it works well for this application.

                      "},{"location":"tutorials/AD-EdgeAI/#train-test-split","title":"Train test split\u00b6","text":"

                      The only way to know how well a model will generalize to new data points is to try it on new data. To do so we split our data into two sets: the training set and the test set.

                      To do so we'll use a function from scikit-learn.

                      "},{"location":"tutorials/AD-EdgeAI/#model-training","title":"Model training\u00b6","text":"

                      We can now leverage the Keras API of Tensorflow for creating our Autoencoder and then train it on our dataset.

                      We'll design a neural network architecture such that we impose a bottleneck in the network which forces a compressed knowledge representation of the original input (also called the latent-space representation). If the input features were each independent of one another, this compression and subsequent reconstruction would be a very difficult task. However, if some sort of structure exists in the data (ie. correlations between input features), this structure can be learned and consequently leveraged when forcing the input through the network's bottleneck.

                      The bottleneck consists of reducing the number of neurons for each layer of the neural network up to a certain point, and then increase the number until the original input number is reached. This will result in a hourglass shape which is typical for the Autoencoders.

                      "},{"location":"tutorials/AD-EdgeAI/#build-the-autoencoder-model","title":"Build the Autoencoder model\u00b6","text":"

                      In this example we'll use a basic fully-connected autoencoder but keep in mind that autoencoders can be built with different classes of neural network (i.e. Convolutional Neural Networks, Recurrent Neural Networks etc).

                      "},{"location":"tutorials/AD-EdgeAI/#model-training","title":"Model training\u00b6","text":"

                      As we already explained, the autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data. We'll use that to reconstruct the input at the output. To train an autoencoder we don\u2019t need to do anything fancy, just throw the raw input data at it. Autoencoders are considered an unsupervised learning technique since they don\u2019t need explicit labels to train on but to be more precise they are self-supervised because they generate their own labels from the training data.

                      To train our neural network we need to have a performance metric to measure how well it is learning to reconstruct the data i.e. our loss function. The loss function in our example, which we need to minimize during our training, is the error between the input data and the data reconstructed by the autoencoder. We'll use the Mean Squared Error.

                      MSE = $\\frac{1}{n}\\sum_{i=1}^{n}{(Y_i - Y'_i)^2}$

                      Where:

                      • $n$: is the number of features (10 in our example)
                      • $Y_i$: is the original data point i.e. the input of the autoencoder
                      • $Y'_i$: is the reconstructed data point i.e. the output of the autoencoder

                      Before starting the training we need to set the hyperparameters). Hyperparameters are parameters whose values control the learning process and determine the values of model parameters that a learning algorithm ends up learning. These are the learning_rate, max_epochs, optimizer and the batch_size you see in the code snippet below. You may ask yourself how to set them, it all comes down to trial and error. Try tweaking them below and see how they affect the learning process...

                      A good explaination of their meaning can be found in the Keras documentation.

                      "},{"location":"tutorials/AD-EdgeAI/#model-evaluation","title":"Model evaluation\u00b6","text":"

                      We now have a model that reconstruct the input at the output... doesn't sounds really useful right?

                      Let's see it in action. Let's take a sample from the test set and run it through our autoencoder.

                      "},{"location":"tutorials/AD-EdgeAI/#model-deployment","title":"Model deployment\u00b6","text":"

                      To deploy our model on the target device we'll leverage Kura\u2122's newly added Nvidia\u2122 Triton Inferece Server integration.

                      The Nvidia\u2122 Triton Inference Server is an open-source inference service software that enables the user to deploy trained AI models from any framework on GPU or CPU infrastructure. It supports all major frameworks like TensorFlow, TensorRT, PyTorch, ONNX Runtime, and even custom framework backend. With specific backends, it is also possible to run Python scripts, mainly for pre-and post-processing purposes, and exploit the DALI building block for optimized operations.

                      For installation refer to the official Kura\u2122 and Triton documentation. For the rest of this tutorial we'll assume a Triton container is available on the target device. It can be simply installed with:

                      docker pull nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3\n

                      We'll also need to install Kura\u2122's Triton bundles:

                      • Triton Server Component: for Kura-Triton integration
                      • AI Wire Component: for making the Triton Inference Server available through the Kura Wires as a Wire component.
                      "},{"location":"tutorials/AD-EdgeAI/#model-conversion","title":"Model conversion\u00b6","text":"

                      The first step in using Triton to serve your models is to place one or more models into a model repository i.e. a folder were the model are available for Triton to load. Depending on the type of the model and on what Triton capabilities you want to enable for the model, you may need to create a model configuration for the model. This configuration is a protobuf containing informations about runtime configuration and input/output shape accepted by the model.

                      For our autoencoder model we'll need three \"models\":

                      • A Preprocessor for performing the operations described in the \"Data processing\" section (Wire envelop translation, feature selection and scaling)
                      • The Autoencoder model we exported in the \"Model training\" section
                      • A Postprocessor for performing the operations described in the \"Model evaluation\" section (Reconstruction error computation)

                      To simplify the handling of these models and improve inference performance, we'll use an advanced feature of Triton wich is an Ensemble Model. From Triton official documentation:

                      An ensemble model represents a pipeline of one or more models and the connection of input and output tensors between those models. Ensemble models are intended to be used to encapsulate a procedure that involves multiple models, such as \"data preprocessing -> inference -> data postprocessing\". Using ensemble models for this purpose can avoid the overhead of transferring intermediate tensors and minimize the number of requests that must be sent to Triton.

                      "},{"location":"tutorials/AD-EdgeAI/#autoencoder","title":"Autoencoder\u00b6","text":"

                      As seen in the \"Model training\" section, our model is available as a Tensorflow SavedModel which can be simply loaded by the Triton Tensorflow backend. We just need to configure it properly.

                      We'll start by creating the following folder structure

                      tf_autoencoder_fp32\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.savedmodel\n\u2502       \u251c\u2500\u2500 assets\n\u2502       \u251c\u2500\u2500 keras_metadata.pb\n\u2502       \u251c\u2500\u2500 saved_model.pb\n\u2502       \u2514\u2500\u2500 variables\n\u2502           \u251c\u2500\u2500 variables.data-00000-of-00001\n\u2502           \u2514\u2500\u2500 variables.index\n\u2514\u2500\u2500 config.pbtxt\n

                      This can be done by copying the model we saved in the Model Training section:

                      "},{"location":"tutorials/AD-EdgeAI/#preprocessor","title":"Preprocessor\u00b6","text":"

                      As discussed in the \"Data processing\" section, before providing the incoming data to the autoencoder, we need to perform feature selection and scaling. In addition to these responsibilites, the Preprocessor will need to perform a sort of serialization of the data to comply to the input shape accepted by the Autoencoder. This is due to how Kura manages the data running on Wires. More details can be found here.

                      To perform all of this we'll use the Python backend available in Triton.

                      As described in the previous section we will need to provide the following folder structure:

                      preprocessor\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.py\n\u2514\u2500\u2500 config.pbtxt\n
                      "},{"location":"tutorials/AD-EdgeAI/#preprocessor-configuration","title":"Preprocessor Configuration\u00b6","text":"

                      As discussed in the official Kura documentation:

                      The AI wire component takes a WireEnvelope as an input, it processes its records and feeds them to the specified preprocessing or inference model.

                      ...

                      The models that manage the input and the output must expect a list of inputs such that:

                      • each input corresponds to an entry of the WireRecord properties
                      • the entry key will become the input name (e.g. in the case of an asset, the channel name becomes the tensor name)
                      • input shape will be [1]

                      Therefore for our input we'll have that each name corresponds to the names we've seen in the Data Collection section. The output needs to correspond to the input accepted by the model (i.e. INPUT0).

                      name: \"preprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"ACC_X\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"ACC_Y\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\n ...\ninput [\n  {\n    name: \"TEMP_PRESS\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"INPUT0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
                      "},{"location":"tutorials/AD-EdgeAI/#preprocessor-model","title":"Preprocessor Model\u00b6","text":"

                      As we've seen in the Data Processing section the Preprocessor is responsible for scaling the input features and serializing them in the tensor shape expected by the Autoencoder model.

                      This can be done with the following python script:

                      import numpy as np\nimport json\n\nimport triton_python_backend_utils as pb_utils\n\n\nclass TritonPythonModel:\n\n    def initialize(self, args):\n        self.model_config = model_config = json.loads(args['model_config'])\n\n        output0_config = pb_utils.get_output_config_by_name(\n            model_config, \"INPUT0\")\n\n        self.output0_dtype = pb_utils.triton_string_to_numpy(\n            output0_config['data_type'])\n\n    def execute(self, requests):\n        output0_dtype = self.output0_dtype\n\n        responses = []\n\n        for request in requests:\n            acc_x      = pb_utils.get_input_tensor_by_name(request, \"ACC_X\").as_numpy()\n            acc_y      = pb_utils.get_input_tensor_by_name(request, \"ACC_Y\").as_numpy()\n            acc_z      = pb_utils.get_input_tensor_by_name(request, \"ACC_Z\").as_numpy()\n            gyro_x     = pb_utils.get_input_tensor_by_name(request, \"GYRO_X\").as_numpy()\n            gyro_y     = pb_utils.get_input_tensor_by_name(request, \"GYRO_Y\").as_numpy()\n            gyro_z     = pb_utils.get_input_tensor_by_name(request, \"GYRO_Z\").as_numpy()\n            humidity   = pb_utils.get_input_tensor_by_name(request, \"HUMIDITY\").as_numpy()\n            pressure   = pb_utils.get_input_tensor_by_name(request, \"PRESSURE\").as_numpy()\n            temp_hum   = pb_utils.get_input_tensor_by_name(request, \"TEMP_HUM\").as_numpy()\n            temp_press = pb_utils.get_input_tensor_by_name(request, \"TEMP_PRESS\").as_numpy()\n\n            out_0 = np.array([acc_y, acc_x, acc_z, pressure, temp_press, temp_hum, humidity, gyro_x, gyro_y, gyro_z]).transpose()\n\n            #                  ACC_Y     ACC_X     ACC_Z    PRESSURE   TEMP_PRESS   TEMP_HUM   HUMIDITY    GYRO_X    GYRO_Y    GYRO_Z\n            min = np.array([-0.132551, -0.049693, 0.759847, 976.001709, 38.724998, 40.220890, 13.003981, -1.937896, -0.265019, -0.250647])\n            max = np.array([ 0.093099, 0.150289, 1.177543, 1007.996338, 46.093750, 48.355824, 23.506138, 1.923712, 0.219204, 0.671759])\n\n            # MinMax scaling\n            out_0_scaled = (out_0 - min)/(max - min)\n\n            # Create output tensor\n            out_tensor_0 = pb_utils.Tensor(\"INPUT0\",\n                                           out_0_scaled.astype(output0_dtype))\n\n            inference_response = pb_utils.InferenceResponse(\n                output_tensors=[out_tensor_0])\n            responses.append(inference_response)\n\n        return responses\n

                      Here there are two important things to note:

                      • The template we're using is taken from the Triton documentation and can be found here.
                      • The MinMax scaling must be the same we used in our training. For illustration purposes we wrote the min and max arrays we found in the Data Processing section but we could have serialized the MinMaxScaler using pickle instead.
                      "},{"location":"tutorials/AD-EdgeAI/#postprocessor","title":"Postprocessor\u00b6","text":"

                      As discussed in the \"Data processing\" section, to perform the anomaly detection step we need to compute the Mean Squared Error between the recontructed data and the actual input data. Due to this the configuration of the Postprocessor model will be somewhat more complicated than before: in addition to the output of the Autoencoder model we will need the output of the Preprocessor model.

                      To perform all of this we'll use the Python backend again.

                      As described in the previous section we will need to provide the following folder structure:

                      postprocessor\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.py\n\u2514\u2500\u2500 config.pbtxt\n
                      "},{"location":"tutorials/AD-EdgeAI/#postprocessor-configuration","title":"Postprocessor Configuration\u00b6","text":"
                      name: \"postprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"RECONSTR0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\ninput [\n  {\n    name: \"ORIG0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY_SCORE0\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY0\"\n    data_type: TYPE_BOOL\n    dims: [ 1 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n

                      As we can see we have two inputs and two outputs:

                      • The first input tensor is the reconstruction performed by the autoencoder model
                      • The second input tensor is the original data (already scaled and serialized by the Preprocessor model)
                      • The first output is the anomaly score i.e. the reconstruction error between the original and the reconstructed data.
                      • The second output is a boolean representing whether the data constitute an anomaly or not

                      Let's see how this is computed by the Python model.

                      "},{"location":"tutorials/AD-EdgeAI/#postprocessor-model","title":"Postprocessor Model\u00b6","text":"
                      import numpy as np\nimport json\n\nimport triton_python_backend_utils as pb_utils\n\ndef z_score(mse):\n    return (mse - MEAN_MSE)/STD_MSE\n\n\nclass TritonPythonModel:\n\n    def initialize(self, args):\n        self.model_config = model_config = json.loads(args['model_config'])\n\n        output0_config = pb_utils.get_output_config_by_name(\n            model_config, \"ANOMALY_SCORE0\")\n        output1_config = pb_utils.get_output_config_by_name(\n            model_config, \"ANOMALY0\")\n\n        self.output0_dtype = pb_utils.triton_string_to_numpy(\n            output0_config['data_type'])\n        self.output1_dtype = pb_utils.triton_string_to_numpy(\n            output1_config['data_type'])\n\n    def execute(self, requests):\n        output0_dtype = self.output0_dtype\n        output1_dtype = self.output1_dtype\n\n        responses = []\n\n        for request in requests:\n            # Get input\n            x_recon = pb_utils.get_input_tensor_by_name(request, \"RECONSTR0\").as_numpy()\n            x_orig = pb_utils.get_input_tensor_by_name(request, \"ORIG0\").as_numpy()\n\n            # Get Mean square error between reconstructed input and original input\n            reconstruction_score = np.mean((x_orig - x_recon)**2, axis=1)\n            \n            #\u00a0Z-Score of Mean square error must be inside [-2; 2]\n            anomaly = np.array([z_score(reconstruction_score) < -2.0 or z_score(reconstruction_score) > 2.0])\n\n            # Create output tensors\n            out_tensor_0 = pb_utils.Tensor(\"ANOMALY_SCORE0\",\n                                           reconstruction_score.astype(output0_dtype))\n            out_tensor_1 = pb_utils.Tensor(\"ANOMALY0\",\n                                           anomaly.astype(output1_dtype))\n\n            inference_response = pb_utils.InferenceResponse(\n                output_tensors=[out_tensor_0, out_tensor_1])\n            responses.append(inference_response)\n\n        return responses\n

                      As you can see the script is simple:

                      • It gets the input tensors
                      • It computes the Mean Squared Error between the inputs (which is what we called the reconstruction error)
                      • It computes the Z-Score of the MSE computed for the current sample and flags it as an anomaly if it is farther than 2 standard deviations away from the average MSE.

                      Note: MEAN_MSE and STD_MSE are the mean value and the standard deviation of the Mean Squared Error computed on the test set and correspond to the reconstruction_scores_pd.mean() and reconstruction_scores_pd.std() we used in the previous section. We didn't set them as they change for every training performed on the Autoencoder. Be sure to set it to their proper values before trying this model on the Triton server!

                      "},{"location":"tutorials/AD-EdgeAI/#ensemble-model","title":"Ensemble model\u00b6","text":"

                      To make things easier for ourselves and improve performance we'll consolidate the AI pipeline into an Ensemble Model.

                      We will need to provide the following folder structure:

                      ensemble_pipeline\n\u251c\u2500\u2500 1\n\u2514\u2500\u2500 config.pbtxt\n

                      Note that the 1 folder is empty. The ensemble model essentially describe how to connect the models that belong to the processing pipeline.

                      Therefore we'll need to focus on the configuration only.

                      name: \"ensemble_pipeline\"\nplatform: \"ensemble\"\nmax_batch_size: 0\ninput [\n  {\n    name: \"ACC_X\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"ACC_Y\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\n ...\ninput [\n  {\n    name: \"TEMP_PRESS\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY_SCORE0\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY0\"\n    data_type: TYPE_BOOL\n    dims: [ 1 ]\n  }\n]\nensemble_scheduling {\n  step [\n    {\n      model_name: \"preprocessor\"\n      model_version: -1\n      input_map{\n          key: \"ACC_X\"\n          value: \"ACC_X\"\n      }\n      input_map{\n          key: \"ACC_Y\"\n          value: \"ACC_Y\"\n      }\n       ...\n      input_map{\n          key: \"TEMP_PRESS\"\n          value: \"TEMP_PRESS\"\n      }\n      output_map {\n        key: \"INPUT0\"\n        value: \"preprocess_out\"\n      }\n    },\n    {\n      model_name: \"tf_autoencoder_fp32\"\n      model_version: -1\n      input_map {\n        key: \"INPUT0\"\n        value: \"preprocess_out\"\n      }\n      output_map {\n        key: \"OUTPUT0\"\n        value: \"autoencoder_output\"\n      }\n    },\n    {\n      model_name: \"postprocessor\"\n      model_version: -1\n      input_map {\n        key: \"RECONSTR0\"\n        value: \"autoencoder_output\"\n      }\n      input_map {\n        key: \"ORIG0\"\n        value: \"preprocess_out\"\n      }\n      output_map {\n        key: \"ANOMALY_SCORE0\"\n        value: \"ANOMALY_SCORE0\"\n      }\n      output_map {\n        key: \"ANOMALY0\"\n        value: \"ANOMALY0\"\n      }\n    }\n  ]\n}\n

                      The configuration is split in two main parts:

                      • The first is the usual configuration we've seen before: we describe what are the input and the output of our model. In this case the input will correspond to the input of the first model of the pipeline (the Preprocessor) and the output to the output of the last model of the pipeline (the Postprocessor)
                      • The second part describe how to map the input/output of the models within the pipeline

                      To better visualize the configuration we can look at the graph below.

                      "},{"location":"tutorials/AD-EdgeAI/#conversion-results","title":"Conversion results\u00b6","text":"

                      At this point we should have a folder structure that looks like this:

                      models\n\u251c\u2500\u2500 ensemble_pipeline\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u251c\u2500\u2500 postprocessor\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u251c\u2500\u2500 preprocessor\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u2514\u2500\u2500 tf_autoencoder_fp32\n    \u251c\u2500\u2500 1\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.savedmodel\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 assets\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 keras_metadata.pb\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 saved_model.pb\n    \u2502\u00a0\u00a0     \u2514\u2500\u2500 variables\n    \u2502\u00a0\u00a0         \u251c\u2500\u2500 variables.data-00000-of-00001\n    \u2502\u00a0\u00a0         \u2514\u2500\u2500 variables.index\n    \u2514\u2500\u2500 config.pbtxt\n
                      "},{"location":"tutorials/AD-EdgeAI/#kura-deployment","title":"Kura Deployment\u00b6","text":"

                      We can now move our pipeline to the target device for inference on the edge.

                      We want to perform anomaly detection in real time, directly within the edge device, using the same data we used to collect for our training.

                      "},{"location":"tutorials/AD-EdgeAI/#triton-component-configuration","title":"Triton component configuration\u00b6","text":"

                      To do so we need to copy the models folder on the target device. For this example we'll use the /home/pi/models path.

                      We can now move to the Kura web UI and create a new Triton Server Container Service component instance. The complete documentation can be found here.

                      In this example we'll call it TritonContainerService.

                      Then we'll need to configure it to run our models. Move to the TritonContainerService configuration interface and set the following parameters:

                      • Image name/Image tag: use the name and tag of the Triton container image you installed. We're using nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3 in this example.
                      • Local model repository path: in our example is /home/pi/models
                      • Inference Models: we'll need to load all the models of the pipeline so: preprocessor,postprocessor,tf_autoencoder_fp32,ensemble_pipeline
                      • Optional configuration for the local backends: tensorflow,version=2 since Tensorflow 2 is the only available Tensorflow backend in the Triton container image we're using.

                      You can leave everything else as default.

                      Once you press the \"Apply\" button Kura will create a new container from the Triton image we set and spin up the service with our models loaded.

                      pi@raspberrypi:~ $ docker ps\nCONTAINER ID   IMAGE                                              COMMAND                  CREATED          STATUS          PORTS                                                                                                                             NAMES\n4deae2857b6f   nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3   \"tritonserver --mode\u2026\"   13 seconds ago   Up 11 seconds   0.0.0.0:4000->8000/tcp, :::4000->8000/tcp, 0.0.0.0:4001->8001/tcp, :::4001->8001/tcp, 0.0.0.0:4002->8002/tcp, :::4002->8002/tcp   tritonserver-kura\n

                      Note: if no container is created check that the \"Container Orchestration Service\" is enabled in the Kura UI. Full documentation for the service can be found here.

                      Note: if you see an error in the logs like \"Internal: Unable to initialize shared memory key 'triton_python_backend_shm_region_2' to requested size (67108864 bytes). If you are running Triton inside docker, use '--shm-size' flag to control the shared memory region size. Each Python backend model instance requires at least 64MBs of shared memory.\", you can update the default shared memory size allocated by the Docker daemon. Go to /etc/docker/daemon.json, set \"default-shm-size\": \"200m\" and restart the Docker daemon with: sudo systemctl restart docker.

                      "},{"location":"tutorials/AD-EdgeAI/#wire-graph","title":"Wire Graph\u00b6","text":"

                      Finally we can move to the \"Wire Graph\" UI and create the AI component (in the Emitters/Receiver menu) for interfacing with the Triton instance we just created. We'll call it Triton in this example.

                      We just need to change two parameter in the configuration:

                      • InferenceEngineService Target Filter: we need to select the TritonContainerService we created at the step above
                      • inference.model.name: Since we're using an ensemble pipeline we need only that as our inference model.

                      The resulting wire graph is the following:

                      And that's it! We should now see the anomaly detection results coming to Kapua in addition to the SenseHat data.

                      "},{"location":"tutorials/AD-EdgeAI/#complete-example","title":"Complete Example\u00b6","text":"

                      A similar but more complete example of the feature presented in this notebook is available in the official Kura\u2122 repository containing all the code and the configuration needed to make it work.

                      Give it a try!

                      "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#welcome-to-the-eclipse-kuratm-documentation","title":"Welcome to the Eclipse Kura\u2122 Documentation","text":"

                      The emergence of an Internet of Thing (IoT) service gateway model running modern software stacks, operating on the edge of an IoT deployment as an aggregator and controller, has opened up the possibility of enabling enterprise level technologies to IoT gateways.

                      Advanced software frameworks, which abstract and isolate the developer from the complexity of the hardware and the networking sub-systems, re-define the development and re-usability of integrated hardware and software solutions.

                      Eclipse Kura is an Eclipse IoT project that provides a platform for building IoT gateways. It is a smart application container that enables remote management of such gateways and provides a wide range of APIs for allowing you to write and deploy your own IoT application.

                      Kura runs on top of the Java Virtual Machine (JVM) and leverages OSGi, a dynamic component system for Java, to simplify the process of writing reusable software building blocks. Kura APIs offer easy access to the underlying hardware including serial ports, GPS, watchdog, USB, GPIOs, I2C, etc. It also offer OSGI bundle to simplify the management of network configurations, the communication with IoT servers, and the remote management of the gateway.

                      Kura components are designed as configurable OSGi Declarative Service exposing service API and raising events. While several Kura components are in pure Java, others are invoked through JNI and have a dependency on the Linux operating system.

                      Kura comes with the following services:

                      • I/O Services
                        • Serial port access through javax.comm 2.0 API or OSGi I/O connection
                        • USB access and events through javax.usb, HID API, custom extensions
                        • Bluetooth access through javax.bluetooth or OSGi I/O connection
                        • Position Service for GPS information from an NMEA stream
                        • Clock Service for the synchronization of the system clock
                        • Kura API for GPIO/PWM/I2C/SPI access
                      • Data Services
                        • Store and forward functionality for the telemetry data collected by the gateway and published to remote servers.
                        • Policy-driven publishing system, which abstracts the application developer from the complexity of the network layer and the publishing protocol used. Eclipse Paho and its MQTT client provide the default messaging library used.
                      • Cloud Services
                        • Easy to use API layer for IoT application to communicate with a remote server. In addition to simple publish/subscribe, the Cloud Service API simplifies the implementation of more complex interaction flows like request/response or remote resource management. Allow for a single connection to a remote server to be shared across more than one application in the gateway providing the necessary topic partitioning.
                      • Configuration Service
                        • Leverage the OSGi specifications ConfigurationAdmin and MetaType to provide a snapshot service to import/export the configuration of all registered services in the container.
                      • Remote Management
                        • Allow for remote management of the IoT applications installed in Kura including their deployment, upgrade and configuration management. The Remote Management service relies on the Configuration Service and the Cloud Service.
                      • Networking
                        • Provide API for introspects and configure the network interfaces available in the gateway like Ethernet, Wifi, and Cellular modems.
                      • Watchdog Service
                        • Register critical components to the Watchdog Service, which will force a system reset through the hardware watchdog when a problem is detected.
                      • Web administration interface
                        • Offer a web-based management console running within the Kura container to manage the gateway.
                      • Drivers and Assets
                        • A unified model is introduced to simplify the communication with the devices attached to the gateway. The Driver encapsulates the communication protocol and its configuration parameters, while the Asset, which is generic across Drivers, models the information data channels towards the device. When an Asset is created, a Mirror of the device is automatically available for on-demand read and writes via Java APIs or via Cloud through remote messages.
                      • Wires
                        • Offers modular and visual data flow programming tool to define data collection and processing pipelines at the edge by simply selecting components from a palette and wiring them together. This way users can, for example, configure an Asset, periodically acquire data from its channels, store them in the gateway, filter or aggregate them using powerful SQL queries, and send the results to the Cloud. The Eclipse Kura Marketplace is a repository from which additional Wires components can be installed into your Kura runtime with a simple drag-and-drop.

                      "},{"location":"administration/application-management/","title":"Application Management","text":""},{"location":"administration/application-management/#package-installation","title":"Package Installation","text":"

                      After developing your application and generating a deployment package that contains the bundles to be deployed (refer to the Development section for more information), you may install it on the gateway using the Packages option in the System area of the Kura Gateway Administration Console as shown below.

                      Upon a successful installation, the new component appears in the Services list (shown as the Heater example in these screen captures). Its configuration may be modified according to the defined parameters as shown the Heater display that follows.

                      "},{"location":"administration/application-management/#eclipse-kura-marketplace","title":"Eclipse Kura Marketplace","text":"

                      Kura allows the installation and update of running applications via the Eclipse Kura Marketplace. The Packages page has, in the top part of the page a section dedicated to the Eclipse Kura Marketplace.

                      Dragging an application reference taken from the Eclipse Kura Marketplace to the specific area of the Kura Web Administrative Console will instruct Kura to download and install the corresponding package, as seen below:

                      Warning

                      If the installation from the Eclipse Marketplace fails, it can be for the lack of the correct certificates. In this case, import the certificate in the SSLKeystore from the Certificate List tab under the Security section. For more details about the procedure see here.

                      If the bundle is an official add-on for Eclipse Kura, the following certificate has to be imported:

                      -----BEGIN CERTIFICATE-----\nMIIHxzCCBq+gAwIBAgIQCCxCSNb4iszmNPNCflUcGTANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE\naWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMzA5MTEwMDAwMDBa\nFw0yNDEwMTEyMzU5NTlaMG8xCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdPbnRhcmlv\nMQ8wDQYDVQQHEwZPdHRhd2ExJTAjBgNVBAoTHEVjbGlwc2Uub3JnIEZvdW5kYXRp\nb24sIEluYy4xFjAUBgNVBAMMDSouZWNsaXBzZS5vcmcwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQC5hXH2cQoOQlXs5cQ5itZ1Dzct9R+bqr2HaF+imlgo\nxJ+Vw1ukfQPpSbmSO17A0hLgpSJyVgoPlpOKkg6LGTz8/2qB7DWHdQbg2p0IGQhr\ndm4oJN2qknnGNl/YYkjz2QJswr1M98raydmq0hqJi0M3q9JSO64O3wOMNduvNG+O\nrCBol7cbxLr7NNoFxZncZ9giP7QF0XYS6nA8dtIyXU3SARRSPn6y9OX1ttltveck\n41ocaU8ORiTF7i89t649XAbtsvxUWM+qVnvlMxpaXqbhnrXMQ/pV2yfdU/qiFQth\n+RqFgBYoX5roxvmjB14+2qlymn236N4KOGhvfr+Fp8C8Fv6N6wFyKZctXewQ6IsA\n3zDvJmF3QaCz6h88lg+IqbRjX5MOjhSkE7XDNKb+xAw5pYzkn9LP+QJLf0iYJw2D\nZ/X+InVPiZ5UdXyXWypN3q0W5vlz/TmWuVZv76/azZ3anoSPiKh+F3si1xZVEMZQ\nIkqsgUfq69M4KvHrdi4nGEOfdBHxjos9ul1AsJR57hrhIchsESthUK04e7d2LYOB\nhHAr0uJNdwFsFD2EBR25ogN83bZ8NaDrrdK2P6sV+hWWK+MY1qRuRub7/fYuR4AU\n82toms9p1usjuyMmuIGEpLwk7jZe6XITcbXQEXDA8JKSZrZ/mOA4yTfIGR/gXXB7\nwQIDAQABo4IDfTCCA3kwHwYDVR0jBBgwFoAUt2ui6qiqhIx56rTaD5iyxZV2ufQw\nHQYDVR0OBBYEFO8gL5LNWmSgCqbujR1qH0bUfrIrMCUGA1UdEQQeMByCDSouZWNs\naXBzZS5vcmeCC2VjbGlwc2Uub3JnMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYI\nKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8E\nBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMIGPBgNVHR8EgYcw\ngYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU1JT\nQVNIQTI1NjIwMjBDQTEtNC5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0\nLmNvbS9EaWdpQ2VydFRMU1JTQVNIQTI1NjIwMjBDQTEtNC5jcmwwfwYIKwYBBQUH\nAQEEczBxMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYI\nKwYBBQUHMAKGPWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRM\nU1JTQVNIQTI1NjIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAX4GCisGAQQB\n1nkCBAIEggFuBIIBagFoAHYAdv+IPwq2+5VRwmHM9Ye6NLSkzbsp3GhCCp/mZ0xa\nOnQAAAGKhcgXYgAABAMARzBFAiEApQsk19PxbsLa452EPaPCXe7SAtpbm5RHnrwj\nyKAjWx0CICli5A3XAGwmg7IEy4lVA5YBt+mhvlegWkXrKt+oc/CoAHUASLDja9qm\nRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGKhcgXWQAABAMARjBEAiAvx7lc\nMyKS6bbnsjbzYOLzJbcS2aAjCzQz4mFiuFA59AIgbt+rpE40/RO0JnFyLP9fsbUf\npUj16ZYinOLorqDk9r0AdwDatr9rP7W2Ip+bwrtca+hwkXFsu1GEhTS9pD0wSNf7\nqwAAAYqFyBc3AAAEAwBIMEYCIQDCrdQYGYA7BlsT5gXZmkutN15gDQDjlfJBxIRb\nZ0FAAgIhAIr0eNFvkpec6VJ5pPrNklFt78XP0NjEOJxjrCFTLKVdMA0GCSqGSIb3\nDQEBCwUAA4IBAQCvENXlAGP311/gV5rMD2frsK+hlcs/4wjRKUS+nwp3RLTRd3w4\ncZLHcsw9qCxeniuHsc/Wa6myr0kKdRc4V6movLq9vMdSjT9dDOZWtZgFaadB0+z2\nA/Jsq1/AFFWqWisF64627j/Wf7RwuasxM0dnkAl3m9Hli5xKPgjbovXiH/dCeMvS\nMTxD1p3ewIYITzV+1Q5FoFuGyIyuh1Kzo7A41xKPe+XfWHqt+hKL8MWkJ9ACD2b0\nZDlD2OaX7K+vI8aWprmwVdpp3deuUoHgBqa1PkHPRmP0bFbamBdB4H6goRX5+DEy\ncTW2rRm8jFiLm1kf0/iOL7/ddw0yZQAUMthU\n-----END CERTIFICATE-----\n

                      that has the following description:

                      Common Name: *.eclipse.org\nSubject Alternative Names: *.eclipse.org, eclipse.org\nOrganization: Eclipse.org Foundation, Inc.\nLocality: Ottawa\nState: Ontario\nCountry: CA\nValid From: September 10, 2023\nValid To: October 11, 2024\nIssuer: DigiCert TLS RSA SHA256 2020 CA1, DigiCert Inc Write review of DigiCert\nKey Size: 4096 bit\nSerial Number: 082c4248d6f88acce634f3427e551c19\n

                      If the bundle is not an official one and it is not hosted by Eclipse, retrieve the certificate with this command:

                      openssl s_client -showcerts -connect <download_link>:443\n

                      and import it in the SSLKeystore.

                      "},{"location":"administration/application-management/#package-signature","title":"Package Signature","text":"

                      Once the selected application deployment package (dp) file is installed, it will be listed in the Packages page and detailed with the name of the deployment package, the version and the signature status. The value of the signature field can be true if all the bundles contained in the deployment package are digitally signed, or false if at least one of the bundles is not signed.

                      "},{"location":"administration/directory-layout/","title":"Directory Layout","text":"

                      This section describes the default Kura directory layout that is created upon installation. The default installation directory is as follows:

                      /opt/eclipse\n

                      The kura sub-directory is a logical link on the actual Kura release directory:

                      kura -> /opt/eclipse/kura_3.0.0_raspberry-pi-2\nkura_3.0.0_raspberry-pi-2\n
                      "},{"location":"administration/directory-layout/#kura-file-structure","title":"Kura File Structure","text":"

                      The idea behind the Kura file and folder structure is to separate user and framework configuration files, that is files that can be modified by the user to customize Kura behavior and files that are used by Kura to persist configurations. Moreover, some files are generated at runtime by Kura (i.e. database) and they are kept in specific folders.

                      The user, console, log4j and packages directories contain files that can be modified by the user (i.e. the configuration for the logging or custom Kura properties). The framework folder keeps the configuration files used by Kura and that shouldn't be modified by the user. The data directory is used to persist files that are generated by Kura. Finally, the plugins folder contains all the jar files needed by Kura. All of the Kura files are located within /opt/eclipse/kura folder using the structure shown in the following table:

                      Directory Description bin Scripts that start Kura. console This folder contains files that are used to customise the Kura Web UI appearance. data Data files generated by the Kura runtime. data/persistance Embedded Database files. packages Deployment package files that are not part of the standard Kura framework, but are installed at a later time. framework Configuration data for Kura framework. The user shouldn't directly modify these files. log Log file from the latest Kura installation. log4j Logger framework configuration plugins All of the libraries and Kura specific jar files needed for the framework execution. user Configuration files generated by the user or by Kura at runtime. These files can be modified by the user to customise the Kura behavior. user/snapshots XML snapshot files; up to 10 successive configurations including the original. user/security Files used by Kura to configure security. .data Backup files needed to restore factory configuration"},{"location":"administration/directory-layout/#log-files","title":"Log Files","text":"

                      Kura regularly updates two log files in the /var/log directory:

                      • /var/log/kura-console.log - stores the standard console output of the application. This log file contains the eventual errors that are thrown upon Kura startup.

                      • /var/log/kura.log - stores all of the logging information from Kura bundles. The logger levels are defined in the log4j.xml configuration file as shown below:

                      /opt/eclpse/kura/user/log4j.xml\n

                      The default logger level in this file is set to INFO. This level may be modified for the whole application or for a specific package as shown in the following example:

                      <Loggers>\n    <Logger name=\"org.eclipse\" level=\"info\" additivity=\"false\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Logger>\n\n    <Logger name=\"org.eclipse.kura.net.admin\" level=\"debug\" additivity=\"false\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Logger>\n\n    <Root level=\"info\">\n        <AppenderRef ref=\"RollingFile\"/>\n    </Root>\n</Loggers>\n

                      In this example, the logger level is set to DEBUG only for the net.admin bundle. Additionally, more specific, properties may be defined as required for your particular logging needs. The logger levels are hierarchical, so that those in a deeper level of the hierarchy will apply; otherwise, the more general logger level will override them.

                      Once the logger levels are modified as needed and the log4j.xml configuration file is saved, Kura automatically loads the new configuration. By default Kura checks the file every 30 seconds.

                      "},{"location":"administration/remote-management-kapua/","title":"Remote Management with Eclipse Kapua","text":""},{"location":"administration/remote-management-kapua/#built-in-services-management","title":"Built-in Services Management","text":"

                      This section describes the remote management of devices running Kura via Eclipse Kapua Console. The Eclipse Kapua Web Console provides the administration tools used for the management of the built-in services exposed by Kura.

                      To remotely manage a device running Kura through the Eclipse Kapua Web Console, select the desired device from the Devices Table of the console and open the Configuration tab as shown in the screen capture below. Please refer to the Built-in Services section for a description of the available Services and their configuration parameters.

                      "},{"location":"administration/remote-management-kapua/#installation-of-a-new-application","title":"Installation of a New Application","text":"

                      As described in Application Management, a new application embedded in a deployment package can be deployed and configured using Eclipse Kapua Console.

                      To do so, select a connected device and click on the Packages tab. Then, click on Install/Upgrade. The Install New Package window opens allowing the deployment package to be installed from an URL as shown in the screen capture below. Once installed, the new application parameters may be modified in the same way as the Built-in Services. Click on the Configuration tab to see the service that corresponds to your application.

                      "},{"location":"administration/remote-management-kapua/#snapshots","title":"Snapshots","text":"

                      As described in Snapshot Management, the overall Kura configuration, including the new installed applications, is stored in a snapshot xml file. The Eclipse Kapua Console also provides options to Download, Upload and Apply, or Rollback snapshots as shown in the screen capture below.

                      "},{"location":"administration/remote-management-kapua/#remote-command-execution-from-eclipse-kapua-web-console","title":"Remote Command Execution from Eclipse Kapua Web Console","text":"

                      The Eclipse Kapua Console provides the ability to run system commands directly on the device. Refer to Command Service for details on how to configure this service in Kura.

                      It is also possible to send a script to execute using the File option of the Command tab in Eclipse Kapua Console as shown in the screen capture below. This script must be compressed into a zip file with the eventual associated resource files. Once the file is selected, click Execute.

                      The zip file is sent embedded in an MQTT message on the device. The Command Service in the device stores the file in /tmp, unzips it, and tries to execute a shell script if one is present. Note that in this case, the Execute parameter cannot be empty; a simple command, such as \"ls -l /tmp\", may be entered.

                      "},{"location":"administration/snapshot-management/","title":"Snapshot Management","text":"

                      The overall configuration of Kura is stored in an XML file called a snapshot. This file includes all of the parameters for every service running in Kura. The original configuration file is named snapshot_0.xml. This section describes how snapshots may be used.

                      Each time a configuration change is made to one of the Kura components, a new XML file is created using the naming convention snapshot_[time as a long integer].xml. The nine most recent snapshots are saved, as well as the original snapshot 0.

                      "},{"location":"administration/snapshot-management/#how-to-access-snapshots","title":"How to Access Snapshots","text":"

                      To display snapshots using the Gateway Administration Console, select Settings from the System area, and then click on the Snapshots tab. The following three operations are available: Download, Upload and Apply, and Rollback.

                      "},{"location":"administration/snapshot-management/#how-to-use-snapshots","title":"How to Use Snapshots","text":""},{"location":"administration/snapshot-management/#download","title":"Download","text":"

                      The Download option provides the ability to save a snapshot file onto your computer. This file may then be edited, uploaded back to the device, or transferred to another equivalent device.

                      Starting from Kura 5.1, the snapshot can be downloaded in two formats:

                      • XML: The original XML snapshot format.
                      • JSON: The JSON format used by the Configuration v2 REST APIs and CONF-V2 request handler. For example the downloaded snapshot can be used as is as a body for the PUT/configurableComponents/configurations/_update request. The takeSnapshot parameter specified by the CONF-V2 request is missing from the downloaded JSON file, if that parameter is not specified, a new snapshot will be created by default.

                      Pressing the Download button will trigger a dialog that allows choosing the desired format.

                      "},{"location":"administration/snapshot-management/#upload-and-apply","title":"Upload and Apply","text":"

                      The Upload and Apply option provides the ability to import an XML file from your computer and upload it onto the device. This function updates every service in Kura with the parameters defined in the XML file.

                      Warning

                      Carefully select the file to be uploaded. An incorrect file may crash Kura and make it unresponsive.

                      "},{"location":"administration/snapshot-management/#rollback","title":"Rollback","text":"

                      The Rollback option provides the ability to restore the system to a previous configuration.

                      "},{"location":"administration/system-component-inventory/","title":"System Component Inventory","text":"

                      The Framework has the capability to report locally and to the associated cloud platform the list of currently installed components and their associated properties.

                      This feature allows, locally and remotely, the system administrator to know which components are installed into the target device and the associated versions. The feature is particularly important for a system administrator because allows to identify vulnerable components and allows immediate actions in response.

                      From the local Kura Web UI, the list of system components is available in the System Packages tab of the Device section. Once selected, the user will get the list of all the system installed components.

                      The component's inventory list is available also via REST APIs and, with the same contract, from the cloud. The Mqtt contract defined for this component is available here

                      "},{"location":"cloud-api/app-dev-guide/","title":"Application developer guide","text":"

                      This guide will provide information on how an application developer can leverage the new Generic Cloud Services APIs, in order to be able to properly use the CloudPublisher/CloudSubscriber API, publish a message, being notified of message delivery and of connection status changes.

                      The Kura ExamplePublisher will be used as a reference.

                      The application should bind itself to a CloudPublisher or CloudSubscriber instance, this can be done in different ways, such as using OSGi ServiceTrackers or by leveraging the Declarative Service layer.

                      The recommended way to perform this operation is choosing the latter and allowing the user to customize the service references through component configuration.

                      If the component metatype and definition are structured as described below, the Kura Web UI will show a dedicated widget in component configuration that helps the user to pick compatible CloudPublisher or CloudSubscriber instances.

                      1. Write component definition

                        The first step involves declaring the Publisher or Subscriber references in component definition:

                          <scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    name=\"org.eclipse.kura.example.publisher.ExamplePublisher\"\n    activate=\"activate\"\n    deactivate=\"deactivate\"\n    modified=\"updated\"\n    enabled=\"true\"\n    immediate=\"true\"\n    configuration-policy=\"require\">\n  <implementation class=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n\n   <!-- If the component is configurable through the Kura ConfigurationService, it must expose a Service. -->\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n   <service>\n       <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n\n   <reference name=\"CloudPublisher\"\n           policy=\"static\"\n           bind=\"setCloudPublisher\"\n           unbind=\"unsetCloudPublisher\"\n           cardinality=\"0..1\"\n           interface=\"org.eclipse.kura.cloudconnection.publisher.CloudPublisher\"/>\n   <reference name=\"CloudSubscriber\"\n           policy=\"static\"\n           bind=\"setCloudSubscriber\"\n           unbind=\"unsetCloudSubscriber\"\n           cardinality=\"0..1\"\n           interface=\"org.eclipse.kura.cloudconnection.subscriber.CloudSubscriber\"/>\n  </scr:component>\n

                        The snipped above shows the definition of Kura ExamplePublisher, this component is capable of sending and receiving messages, and therefore defines two references, the first to a CloudPublisher and the second to a CloudSubscriber.

                        In order to allow the user to customize the bindings at runtime, the target attribute of the references should not be specified at this point in component definition, as it will be set by the Web UI.

                        Reference cardinality should be use the 0..1 or 0..n form, as it is not guaranteed that the references will point to a valid service instance during all the lifetime of the application component. For example, references can not be bound if the application has not been configured by the user yet or if the target service is missing.

                      2. Create component metatype

                        Application metatype should declare an AD for each Publisher/Subscriber reference declared in component definition:

                          <MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n      <OCD id=\"org.eclipse.kura.example.publisher.ExamplePublisher\"\n           name=\"ExamplePublisher\"\n           description=\"Example of a Configuring Kura Application.\">\n\n       <!-- ... -->\n\n        <AD id=\"CloudPublisher.target\"\n            name=\"CloudPublisher Target Filter\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"(kura.service.pid=changeme)\"\n            description=\"Specifies, as an OSGi target filter, the pid of the Cloud Publisher used to publish messages to the cloud platform.\">\n        </AD>\n\n        <AD id=\"CloudSubscriber.target\"\n            name=\"CloudSubscriber Target Filter\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"(kura.service.pid=changeme)\"\n            description=\"Specifies, as an OSGi target filter, the pid of the Cloud Subscriber used to receive messages from the cloud platform.\">\n        </AD>\n\n        <!-- ... -->\n\n        </OCD>\n    <Designate pid=\"org.eclipse.kura.example.publisher.ExamplePublisher\" factoryPid=\"org.eclipse.kura.example.publisher.ExamplePublisher\">\n        <Object ocdref=\"org.eclipse.kura.example.publisher.ExamplePublisher\"/>\n    </Designate>\n  </MetaData>\n

                        It is important to respect the following rules for some of the AD attributes:

                        • id

                        This attribute must have the following form:

                        <reference name>.target\n

                        where <reference name> should match the value of the name attribute of the corresponding reference in component definition.

                        • required must be set to true

                        • default must not be empty and must be a valid OSGi filter.

                        The Web UI will renderer a dedicated widget for picking CloudPublisher and CloudSubscriber instances:

                      3. Write the bind/unbind methods in applicaiton code

                        The last step involves defining some bind...()/unbind...() methods with a name that matches the values of the bind/unbind attributes of the references in component definition.

                        public void setCloudPublisher(CloudPublisher cloudPublisher) {\n...\n}\n\npublic void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n...\n}\n\npublic void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n...\n}\n\npublic void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n...\n}\n

                        As stated above, since reference cardinality is declared as 0.., the application must be prepared to handle the cases where references are not satisfied, and therefore CloudPublisher and CloudSubscriber instances are not available.

                      4. Publish a message

                        If a CloudPublisher instance is bound, the application can publish messages using its publish() method:

                          if (nonNull(this.cloudPublisher)) {\n    KuraMessage message = new KuraMessage(payload);\n    String messageId = this.cloudPublisher.publish(message);\n  }\n
                      5. Receiving messages using a CloudSubscriber

                        In order to receive messages from a CloudSubscriber, the application must implement and attach a CloudSubscriberListener to it.

                        This can be done for example during CloudSubscriber binding:

                          public class ExamplePublisher implements CloudSubscriberListener, ... {\n\n  ...\n\n   public void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber = cloudSubscriber;\n    this.cloudSubscriber.registerCloudSubscriberListener(ExamplePublisher.this);\n    ...\n  }\n\n  public void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber.unregisterCloudSubscriberListener(ExamplePublisher.this);\n    ...\n    this.cloudSubscriber = null;\n  }\n\n  ...\n\n  @Override\n  public void onMessageArrived(KuraMessage message) {\n    logReceivedMessage(message);\n  }\n\n  ...\n\n  }\n

                        The CloudSubscriber will invoke the onMessageArrived() method when new messages are received.

                      6. Receiving connection state notifications

                        If an application is interested in cloud connection status change events (connected, disconnected, etc), it can implement and attach a CloudConnectionListener to a CloudPublisher or CloudSubscriber instance.

                          public class ExamplePublisher implements CloudConnectionListener, ... {\n\n  ...\n\n  public void setCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher = cloudPublisher;\n    this.cloudPublisher.registerCloudConnectionListener(ExamplePublisher.this);\n    ...\n  }\n\n  public void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher.unregisterCloudConnectionListener(ExamplePublisher.this);\n    ...\n    this.cloudPublisher = null;\n  }\n\n  public void setCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    this.cloudSubscriber = cloudSubscriber;\n    ...\n    this.cloudSubscriber.registerCloudConnectionListener(ExamplePublisher.this);\n  }\n\n  public void unsetCloudSubscriber(CloudSubscriber cloudSubscriber) {\n    ...\n    this.cloudSubscriber.unregisterCloudConnectionListener(ExamplePublisher.this);\n    this.cloudSubscriber = null;\n  }\n\n  ...\n\n  @Override\n  public void onConnectionEstablished() {\n    logger.info(\"Connection established\");\n  }\n\n  @Override\n  public void onConnectionLost() {\n    logger.warn(\"Connection lost!\");\n  }\n\n  @Override\n  public void onDisconnected() {\n    logger.warn(\"On disconnected\");\n  }\n\n  ...\n\n  }\n
                      7. Receiving message delivery notifications

                        If an application is interested in message confirmation events and the underlying cloud connection supports it, it can implement and attach a CloudDeliveryListener to a CloudPublisher instance.

                          public class ExamplePublisher implements CloudDeliveryListener, ... {\n\n  ...\n\n  public void setCloudPublisher(CloudPublisher cloudPublisher) {\n    this.cloudPublisher = cloudPublisher;\n    ...\n    this.cloudPublisher.registerCloudDeliveryListener(ExamplePublisher.this);\n  }\n\n  public void unsetCloudPublisher(CloudPublisher cloudPublisher) {\n    ...\n    this.cloudPublisher.registerCloudDeliveryListener(ExamplePublisher.this);\n    this.cloudPublisher = null;\n  }\n\n  ...\n\n  @Override\n  public void onMessageConfirmed(String messageId) {\n    logger.info(\"Confirmed message with id: {}\", messageId);\n  }\n\n  ...\n\n  }\n

                        The CloudSubscriber will invoke the onMessageConfirmed() method when a published message is confirmed.

                        In order to determine which message has been confirmed, the provided messageId can be compared with the id returned by the publish() call that published the message.

                        Please note that if the underlying cloud connection is not able to provide message confirmation for the published message, the id returned by publish() will be null.

                      "},{"location":"cloud-api/built-in-cloud/","title":"Built-in Cloud Services","text":"

                      Eclipse Kura provides by default a set of services used to connect to a cloud platform. The following sections describe the services and how to configure them.

                      The CloudService API is deprecated since Kura 4.0. The functionalities provided by CloudService are now provided by the CloudEndpoint and CloudConnectionManager service interfaces. See the section describing the Kura 4.0 cloud connection model for more details.

                      The DataService and MqttDataTrasport APIs are not deprecated in Kura 4.0.

                      "},{"location":"cloud-api/built-in-cloud/#cloudservice","title":"CloudService","text":"

                      The CloudService provides an easy-to-use API layer for the M2M application to communicate with a remote server. It operates as a decorator for the DataService, providing add-on features over the management of the transport layer.

                      In addition to simple publish/subscribe, the CloudService API simplifies the implementation of more complex interaction flows like request/response or remote resource management. The CloudService abstracts the developers from the complexity of the transport protocol and payload format used in the communication.

                      The CloudService allows a single connection to a remote server to be shared across more than one application in the gateway providing the necessary topic partitioning. Its functions include:

                      • Adds application topic prefixes to allow for a single remote server connection to be shared across applications.

                      • Defines a payload data model and provides default encoding/decoding serializers.

                      • Publishes life-cycle messages when the device and applications start and stop.

                      To use this service, select the CloudServices option located in the System area and select the CloudService tab as shown in the screen capture below.

                      The CloudService provides the following configuration parameters:

                      • device.display-name - defines the device display name given by the system. (Required field.)
                      • device.custom-name - defines the custom device display name if the device.display-name parameter is set to \"Custom\".
                      • topic.control-prefix - defines the topic prefix for system messages.
                      • encode.gzip - defines if the message payloads are sent compressed.
                      • republish.mqtt.birth.cert.on.gps.lock - when set to true, forces a republish of the MQTT Birth Certificate when a GPS correct position lock is received. The device is then registered with its real coordinates. (Required field.)
                      • republish.mqtt.birth.cert.on.modem.detect - when set to true, forces a republish of the MQTT Birth Certificate when the service receives a modem detection event. (Required field.)
                      • enable.default.subscriptions - when set to true, the gateway will not be remotely manageable.
                      • payload.encoding - Specify the message payload encoding. The possible options are Kura Protobuf and Simple JSON.

                      The default CloudService implementations publishes the following lifecycle messages:

                      1. BIRTH message: sent immediately when device is connected to the cloud platform. The BIRTH message is published with priority 0 and QoS 1;
                      2. DISCONNECT message: sent immediately before device is disconnected from the cloud platform. The DISCONNECT message is published with priority 0 and QoS 0;
                      3. delayed BIRTH message: sent when new cloud application handler becomes available, a DP is installed or removed, GPS position is locked (can be disabled), or when modem status changes (can be disabled). These messages are cached for 30 seconds before sending. If no other message of such type arrives the message is sent; otherwise the BIRTH is cached and the timeout restarts. This is to avoid sending multiple messages when the framework starts. The message is published with priority 0 and QoS 1;
                      "},{"location":"cloud-api/built-in-cloud/#mqttdatatransport","title":"MqttDataTransport","text":"

                      The MqttDataTransport service provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. To use this service, select the MqttDataTransport option located in the System area and select the CloudService tab as shown in the screen capture below.

                      The MqttDataTransport service provides the following configuration parameters:

                      • broker-url - defines the URL of the MQTT broker to connect to. (Required field.)
                      • topic.context.account-name - defines the name of the account to which the device belongs.
                      • username and password - define the username and password that have been assigned to the device by the account administrator (generally username is account-name_broker). (Required field.)
                      • client-id - defines the identifier of the MQTT client representing the device when connecting to the MQTT broker. If left empty, it is automatically determined by the client software as the MAC address of the main network interface (in general numbers and uppercase letters without ':'). This identifier has to be unique within your account.
                      • keep-alive - defines the \"keep alive\" interval measured in seconds. It specifies the maximum amount of time that should pass without communication between the client and the server. The client will ensure that at least one message travels across the network within each keep alive period. In the absence of a data-related message during this time period, the client will send a very small MQTT \"ping\" message that the server will acknowledge. The keep alive interval enables the client to detect when the server is no longer available without having to wait for the long TCP/IP timeout. (Required field.)
                      • timeout - sets the timeout used for all interactions with the MQTT broker. (Required field.)
                      • clean-session - controls the behavior of both the client and the server at the time of connect and disconnect. When this parameter is set to true, the state information is discarded at connect and disconnect; when set to false, the state information is maintained. (Required field.)
                      • lwt parameters - define the MQTT \"Last Will and Testament\" (LWT) settings for the client. In the event that the client unexpectedly loses its connection to the server, the server publishes the LWT message (lwt.payload) to the LWT topic on behalf of the client. This allows other clients (subscribed to the LWT topic) to be made aware that the client has disconnected. LWT parameters that may be configured include:
                      • lwt.topic
                      • lwt.payload
                      • lwt.qos
                      • lwt.retain
                      • in-flight.persistence - defines the storage type where in-flight messages are persisted across reconnections. They may be stored in memory, or in a file on the disk. (Required field.)
                      • protocol-version - defines the MQTT Protocol version to be used. This value may be 3.1 or 3.1.1.
                      • ssl parameters - defines the SSL configuration. SSL parameters that may be configured include:
                      • ssl.hostname.verification
                      • ssl.default.cipherSuites
                      • ssl.certificate.alias
                      "},{"location":"cloud-api/cloud-conn-dev-guide/","title":"Cloud connection developer guide","text":"

                      This guide will provide information on how a cloud connection developer can leverage the new Generic Cloud Services APIs.

                      As reference, this guide will use the Eclipse IoT WG namespace implementation bundle available here

                      "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-cloudendpoint-and-cloudconnectionmanager","title":"Implement CloudEndpoint and CloudConnectionManager","text":"

                      In order to leverage the new APIs, and be managed by the Kura Web UI, the Cloud Connection implementation bundle must implement CloudEndpont and, if log-lived connections are supported, the CloudConnectionManager interface must be implemented as well.

                      The ending class should be something as follows:

                      public class CloudConnectionManagerImpl\n        implements CloudConnectionManager, CloudEndpoint, ... {\n\n    @Override\n    public boolean isConnected() {\n        ...\n    }\n\n    @Override\n    public void connect() throws KuraConnectException {\n        ...\n    }\n\n    @Override\n    public void disconnect() {\n        ...\n    }\n\n    @Override\n    public Map<String, String> getInfo() {\n        ...\n    }\n\n    @Override\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener) {\n        ...\n    }\n\n    @Override\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener) {\n        ...\n    }\n}\n

                      A corresponding component definition should be provided in the OSGI-INF folder exposing the implementation of CloudEndpoint and CloudConnectionManager interfaces.

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" activate=\"activate\" configuration-policy=\"require\" deactivate=\"deactivate\" enabled=\"true\" immediate=\"true\" modified=\"updated\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.CloudConnectionManagerImpl\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n      <provide interface=\"org.eclipse.kura.cloudconnection.CloudConnectionManager\"/>\n      <provide interface=\"org.eclipse.kura.cloudconnection.CloudEndpoint\"/>\n      <!-- ... -->\n   </service>\n\n   <!-- ... -->\n\n   <property name=\"kura.ui.service.hide\" type=\"Boolean\" value=\"true\"/>\n   <property name=\"kura.ui.factory.hide\" type=\"String\" value=\"true\"/>\n</scr:component>\n

                      In order to be fully compliant with the Web UI requirements, the CloudConnection component definition should provide two properties kura.ui.service.hide and kura.ui.factory.hide to hide the component from the left side part of the UI dedicated to display the services list.

                      "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-the-cloudconnectionfactory-interface","title":"Implement the CloudConnectionFactory interface","text":"

                      The CloudConnectionFactory is responsible to manage the cloud connection instance lifecycle by creating the CloudEndpoint instance and all the required services needed to publish or receive messages from the cloud platform.

                      As a reference, please have a look at the CloudConnectionFactory defined for the Eclipse IoT WG namespace implementation.

                      In particular, the getFactoryPid() method returns the PID of the CloudEndpoint factory. The createConfiguration() method receives a PID that will be used for the instantiation of the CloudEndpoint and for all the related services required to communicate with the cloud platform. In the example above, the factory creates the CloudEnpoint, and a DataService and MqttDataTransport instances internally needed to communicate with a remote cloud platform. As can be seen here, the CloudEndpoint instance configuration is enriched with the reference to the CloudConnectionFactory that generated it. This step is required by the Web UI in order to properly relate the instances with the corresponding factories.

                      The deleteConfiguration() method deletes from the framework the CloudEndpoint instance identified by the PID passed as argument and all the related services. In the Eclipse IOT WG example, it not only deletes the CloudEndpoint instance but also the corresponding DataService and MqttDataTransport instances.

                      The getStackComponentsPids() method return a List of String that represent the kura.service.pid of the configurable components that are part of a Cloud Connection instance. This method is used by the Web UI to get the list of configurable components that need to be displayed to the end user.

                      The getManagedCloudConnectionPids() method will return the list of kura.service.pid of all the CloudEndpoints managed by the factory.

                      The factory component definition should be defined as follows:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.DefaultCloudConnectionFactory\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.factory.DefaultCloudConnectionFactory\"/>\n   <reference bind=\"setConfigurationService\" cardinality=\"1..1\" interface=\"org.eclipse.kura.configuration.ConfigurationService\" name=\"ConfigurationService\" policy=\"static\" unbind=\"unsetConfigurationService\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory\"/>\n   </service>\n   <property name=\"osgi.command.scope\" type=\"String\" value=\"kura.cloud\"/>\n   <property name=\"osgi.command.function\" type=\"String\">\n      createConfiguration\n   </property>\n   <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n   <property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\-[a-zA-Z0-9]+)?$\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.DefaultCloudConnectionFactory\"/>\n</scr:component>\n

                      In particular, it should expose in the service section the fact that the factory implements org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory

                         <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory\"/>\n   </service>\n

                      Important properties that need to be specified to have a better Web UI experience are the following:

                      <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n<property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\-[a-zA-Z0-9]+)?$\"/>\n
                      those allow to specify the form of the expected PID that the end user should provide when creating a new cloud connection.

                      "},{"location":"cloud-api/cloud-conn-dev-guide/#provide-a-cloudpublisher-implementation","title":"Provide a CloudPublisher implementation","text":"

                      To provide a CloudPublisher implementation, other than implementing CloudPublisher API in a java class, the developer must provide a component definition in the OSGI-INF folder that should be like the following:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\" activate=\"activate\" configuration-policy=\"require\" deactivate=\"deactivate\" enabled=\"true\" immediate=\"true\" modified=\"updated\" name=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\">\n   <implementation class=\"org.eclipse.kura.internal.cloudconnection.eclipseiot.mqtt.cloud.publisher.CloudPublisherImpl\"/>\n   <service>\n      <provide interface=\"org.eclipse.kura.cloudconnection.publisher.CloudPublisher\"/>\n      <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n   <property name=\"cloud.connection.factory.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\"/>\n   <property name=\"kura.ui.service.hide\" type=\"Boolean\" value=\"true\"/>\n   <property name=\"kura.ui.factory.hide\" type=\"String\" value=\"true\"/>\n   <property name=\"kura.ui.csf.pid.default\" type=\"String\" value=\"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\"/>\n   <property name=\"kura.ui.csf.pid.regex\" type=\"String\" value=\"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher(\\-[a-zA-Z0-9]+)?$\"/>\n</scr:component>\n

                      As can be seen in the previous snippet, the Publisher exposes itself in the framework as a ConfigurableComponent and as a CloudPublisher.

                      The component definition must contain the following well-known properties:

                      • cloud.connection.factory.pid: this property must be set to the kura.service.pid of the factory that created the cloud connection which the publisher belongs. It is used by the Web UI to enforce that the correct cloud publisher implementation is used in a specific cloud endpoint.
                      • kura.ui.service.hide: as specified before for the Cloud Endpoint
                      • kura.ui.factory.hide: as specified before for the Cloud Endpoint
                      • kura.ui.csf.pid.default: as specified before for the Cloud Factory. It is an optional property.
                      • kura.ui.csf.pid.regex: as specified before for the Cloud Factory. It is an optional property.

                      The relation between the CloudPublisher instance and the CloudEndpoint is defined by a configuration property set by the Web UI at CloudPublisher creation.

                      "},{"location":"cloud-api/cloud-conn-dev-guide/#provide-a-cloudsubscriber-implementation","title":"Provide a CloudSubscriber implementation","text":"

                      The CloudSubscriber implementation and component definition is similar to the one described for the CloudPublisher.

                      "},{"location":"cloud-api/cloud-conn-dev-guide/#implement-requesthandler-support","title":"Implement RequestHandler support","text":"

                      In order to support Command and Control, the cloud connection bundle should provide a service that registers itself as RequestHandlerRegistry. In this way all the RequestHandler instances could be able to discover the different Registry and subscribe for command and control messages received from the cloud platform. As an example, for the Eclipse IoT WG bundle, the CloudEndpoint registers itself also as RequestHandlerRegistry.

                      "},{"location":"cloud-api/overview/","title":"Overview","text":"

                      This section describes the new cloud related concepts and APIs introduced in Kura 4.0.

                      "},{"location":"cloud-api/overview/#motivations","title":"Motivations","text":"

                      Before Kura 4.0, Cloud APIs were quite tied to Kapua messaging conventions and to the MQTT protocol. Defining custom stacks that support other cloud platforms was possible, but the resulting implementations were affected by the following limitations:

                      • The legacy APIs assume that the underlying messaging protocol is MQTT. This assumption spans across all API layers, from the low level MQTTDataTrasport to the high level CloudClient. This makes quite difficult to implement cloud stacks that use other protocols like AMQP or HTTP.

                      • The CloudClient API, which was the recommended way for applications to interface with a cloud stack, enforce the following MQTT topic structure:

                        #account-name/#device-id/#app-id/<app-topic>\n
                        This topic hierarchy, which is Kapua related, might be too restrictive or too loose for other cloud platforms, for example:

                        • The Eclipse IoT working group namespace allows authenticated devices to omit the accont-name and device-id parameters in the topic. Moreover, telemetry, alert and event message topics must start respectively the t/, a/ and e/ prefixes. Adhering to this specification is not possible for a cloud stack that implements the legacy APIs.
                        • The AWS cloud platform allows publishing on virtually any topic, using a CloudClient would be quite restrictive in this case. A way for overcoming this limitation for an application might be using the DataService layer directly, adversely affecting portability.
                        • The Cumulocity cloud platform allows publishing only on a limited set of topics, and most of the application generated information is placed in the payload encoded in CSV. Using CloudClient in this case makes difficult for the cloud stack to enforce that the messages are published on the correct topics. Moreover, the cloud stack in this case must also convert from KuraPayload to CSV, this can be currently achieved only by introducing rigid conversion rules, that might not be enough to support all message formats.
                      • Applications that use the current APIs are not portable across cloud platforms. For example if an appliaction intends to publish on Cumulocity or AWS, it should be probably aware of the underlying cloud platform.

                      "},{"location":"cloud-api/overview/#concepts","title":"Concepts","text":"

                      The main interfaces of the new set of APIs and their interactions are depicted in the diagram below:

                      As shown in the above diagram new APIs introduce the concept of Cloud Connection, a set of related services that allow to manage the communication to/from a remote cloud platform.

                      The services that compose a Cloud Connection can implement the following cloud-specific interfaces:

                      • CloudEndpoint (required): Each Cloud Connection is identified by a single instance of CloudEndpoint that implements low level specificities for the communication with the remote cloud platform.

                      • CloudConnectionManager (optional): Exposes methods that allow to manage long-lived connections. The implementor of CloudEndpoint can implement this interface as well if the cloud platform support long-lived connections (for example by using the MQTT protocol).

                      • RequestHandlerRegistry (optional): Manages the command and control functionalities if supported by the cloud platform.

                      • CloudPublisher (optional): Allows applications to publish messages to the cloud platform in a portable way.

                      • CloudSubscriber (optional): Allows applications to receive messages from the cloud platform in a portable way.

                      • CloudConnectionFactory (required): Manages the lifecycle of Cloud Connections.

                      A Cloud Connection can also include services that do not provide any of the interfaces above but compose the internal implementation.

                      "},{"location":"cloud-api/overview/#cloudendpoint","title":"CloudEndpoint","text":"

                      Every Cloud Connection must contain a single CloudEndpoint instance. The kura.service.pid of the CloudEndpoint identifies the whole Cloud Connection.

                      The CloudEndpoint provides some low level methods that can be used to interact with the remote cloud platform.

                      For example the interface provides the publish() and subscribe() methods that allow to publish or receive messages from the cloud platform in form of KuraMessages. Those methods are designed for internal use and are not intended to be used by end-user applications.

                      The format of the KuraMessage provided to/received from a CloudEndpoint is implementation specific: the CloudEndpoint expects some properties to be set in the KuraMessage to be able to correctly publish a message (e.g. MQTT topic). These properties are specified by the particular CloudEndpoint, and should be documented by the implementor.

                      The recommended way for applications to publish and receive messages involves using the Publisher and Subscriber APIs described below. If an application directly uses the methods above, it will lose portability and will be tied to the specific Cloud Connection implementation.

                      "},{"location":"cloud-api/overview/#cloudconnectionmanager","title":"CloudConnectionManager","text":"

                      If the messaging protocol implemented by a Cloud Connection supports long-lived connection, then its CloudEndpoint can also implement and provide the CloudConnectionManager interface.

                      This interface exposes some methods that can be used to manage the connection like connect(), disconnect() and isConnected(); it also supports monitoring connection state using the CloudConnectionListener interface.

                      "},{"location":"cloud-api/overview/#publishers-and-subscribers","title":"Publishers and Subscribers","text":"

                      The limitations of the current model described above are addressed by the introduction of the CloudPublisher and CloudSubscriber APIs, that replace the CloudClient as the recommended interface between applications and cloud stacks. CloudPublisher and CloudSubscriber are service interfaces defined as follows:

                      public interface CloudPublisher {\n\n    public String publish(KuraMessage message) throws KuraException;\n\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void registerCloudDeliveryListener(CloudDeliveryListener cloudDeliveryListener);\n\n    public void unregisterCloudDeliveryListener(CloudDeliveryListener cloudDeliveryListener);\n\n}\n
                      public interface CloudSubscriber {\n\n    public void registerCloudSubscriberListener(CloudSubscriberListener listener);\n\n    public void unregisterCloudSubscriberListener(CloudSubscriberListener listener);\n\n    public void registerCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n    public void unregisterCloudConnectionListener(CloudConnectionListener cloudConnectionListener);\n\n}\n
                      "},{"location":"cloud-api/overview/#cloudpublisher","title":"CloudPublisher","text":"

                      The CloudPublisher interface should be used by applications for publishing messages using the single publish() method. This method accepts a KuraMessage which is basically a KuraPayload that can be enriched with metadata.

                      The main difference with the CloudClient APIs is that the publish() method does not require the application to specify any information related to message destinations. This allows to write portable applications that are unaware of the low level details of the destination cloud platform, such as the message format and the transport protocol.

                      "},{"location":"cloud-api/overview/#cloudsubscriber","title":"CloudSubscriber","text":"

                      An application designed to receive messages from the cloud must now attach a listener (CloudSubscriberListener) to a CloudSubscriber instance.

                      In this case, the message source cannot be specified by the application but is defined by the subscriber instance, in the same way as the CloudPublisher defines destination for published messages.

                      The low level details necessary for message delivery and reception (e.g. the MQTT topic and the conversion between KuraMessage and the message format used on the wire) are managed by the publisher/subscriber, typically these details are stored in the service configuration.

                      While in the previous model an application was responsible to actively obtain a CloudClient instance from a CloudService, now the relation between the application and a CloudPublisher or CloudSubscriber instance is represented as an OSGi service reference. Applications should allow the user to modify this reference in configuration, making it easy to switch between different cloud publisher/subscriber instances and different cloud platforms.

                      Publisher/subscriber instances are now typically instantiated and configured by the end user using the Web UI.

                      Publisher/subscriber instances are related to a CloudEnpoint instance using an OSGi service reference encoded in well known configuration property specified in the APIs (CloudConnectionConstants.CLOUD_ENDPOINT_SERVICE_PID_PROP_NAME). This allows the user to create those instances in a dedicated section of the Web UI.

                      "},{"location":"cloud-api/overview/#command-and-control","title":"Command and control","text":"

                      Another field in which the current Kura cloud related APIs can be generalized is related to command and control. In the previous model this aspect was covered by the Cloudlet APIs that are now replaced by RequestHandler APIs

                      Legacy Cloudlet implementations are defined by extending a base class, Cloudlet, which takes care of handling the invocation of the doGet(), doPut(), doPost() ... methods, and of correlating request and response messages. Messages were sent and received through a CloudClient.

                      More explicitly, Cloudlet only works with control topics whose structure is

                      $EDC/<account-name>/<device-id>/<app-id>/<method>/<resource-path>\n
                      and also expects the identifier of the sender and the correlation identifier in the KuraPayload.

                      In the previous model, there is no way for a cloud stack implementor to customize the aspects above, which are hardcoded in the Cloudlet base class.

                      The new model delegates these aspects to some component of the cloud stack, and requires applications that want to support command and control to register themselves as RequestHandler to a RequestHandlerRegistry instance.

                      In order to ease porting old applications to the new model, some of the concepts of the old Cloudlet APIs are still present, this can be seen by looking at the RequestHandler interface definition:

                      public interface RequestHandler {\n\n    public KuraMessage doGet(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doPut(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doPost(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doDel(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n\n    public KuraMessage doExec(RequestHandlerContext context, KuraMessage reqMessage) throws KuraException;\n}\n

                      A RequestHandler invocation involves the following parameters:

                      Request parameters:

                      • method: (GET, PUT, POST, DEL, EXEC) that identifies the RequestHandler method to be called
                      • request message: A set of key-value pairs and/or binary body contained in the KuraPayload wrapped inside the KuraMessage.
                      • resources: A List<String> of positional parameters available under the well known args key in the provided KuraMessage properties.

                      Response parameters:

                      • response message: A set of key-value pairs and/or binary body contained in the KuraPayload wrapped inside the returned KuraMessage.
                      • status: A numeric code reporting operation completion state, determined as follows:
                      • 200, if RequestHandler methods returns without throwing exceptions.
                      • 400, if RequestHandler methods throws a KuraException with KuraErrorCode == BAD_REQUEST
                      • 404, if RequestHandler methods throws a KuraException with KuraErrorCode == NOT_FOUND
                      • 500, if RequestHandler methods throws a KuraException with other error codes.
                      • exception message: The message of the KuraException thrown by the RequestHandler methods, if any.
                      • exception stack trace: The stack trace of the KuraException thrown by the RequestHandler methods, if any.

                      The parameters above are the same involved in current Cloudlet calls. The request id and requester client id parameters are no longer part of the API, because are related to the to the way Kapua correlates requests and response. In the new API, request and response identifiers are not specified and not forwarded to the Cloudlet, this allows the CloudletService implementation to adopt the platform specific conventions for message correlation.

                      The Cloudlet parameters must be present in the request and response messages encoded in some format. A user that intends to call a Kura Cloudlet, for example through platform-specific REST APIs must be aware of these parameters. The user must supply request parameters in request message and must be able to extract response data from received message. The actual encoding of these parameters inside the messages depends on the particular platform.

                      The fact that set of Cloudlet parameters are roughly the same involved in current Cloudlet calls allows existing Cloudlet based applications to continue to work without changes to the protocol.

                      "},{"location":"cloud-api/overview/#cloud-connection-lifecycle","title":"Cloud Connection lifecycle","text":"

                      CloudEndpoint instance lifecycle is managed by a CloudConnectionFactory instance. A cloud connection implementor must register a CloudConnectionFactory instance in the framework that is responsible of creating and destroying the CloudEndpoint instances.

                      The CloudConnectionFactory will be typically invoked by the Web UI, and is defined as follows:

                      public interface CloudConnectionFactory {\n\n    public static final String KURA_CLOUD_CONNECTION_FACTORY_PID = \"kura.cloud.connection.factory.pid\";\n\n    public String getFactoryPid();\n\n    public void createConfiguration(String pid) throws KuraException;\n\n    public List<String> getStackComponentsPids(String pid) throws KuraException;\n\n    public void deleteConfiguration(String pid) throws KuraException;\n\n    public Set<String> getManagedCloudConnectionPids() throws KuraException;\n\n}\n

                      The createConfiguration() and deleteConfiguration() methods are responsible of creating/destroying a CloudEndpoint instance, specified by the provided kura.service.pid, and all the related services.

                      The getManagedCloudConnectionPids() returns the set of kura.service.pid managed by the factory.

                      The getStackComponentsPids(String pid) returns the list of the kura.service.pids of the ConfigurableComponents that are associated with the CloudEndpoint with the specified pid. The Web Ui will render the configuration of those components in separated tabs, in the dedicated CloudConnections section.

                      "},{"location":"cloud-api/overview/#backwards-compatibility","title":"Backwards compatibility","text":"

                      In order to ease the transition to the new model, legacy APIs like CloudService and CloudClient are still supported in Kura 4.0.0, even if deprecated.

                      The default Kapua oriented CloudService implementation is still available and can be used by legacy applications without changes. The default CloudService instance in Kura 4.0 also implements the new CloudEndpoint and CloudConnectionManager interfaces.

                      "},{"location":"cloud-api/user-guide/","title":"User guide","text":"

                      This guide will illustrate the steps required for configuring an application that uses the new Cloud Connection APIs to publish messages to the Kapua platform.

                      The involved steps are the following

                      1. Instantiation and configuration of the Cloud Connection.
                      2. Instantiation and configuration of a Publisher.
                      3. Binding an application to the Publisher.
                      "},{"location":"cloud-api/user-guide/#creating-a-new-cloud-connection","title":"Creating a new Cloud Connection","text":"
                      1. Open the Cloud Connections section of the Web UI:

                      2. Create a new Cloud Connection

                        1. Click on the New Connection button

                        2. Enter a new unique identifier in the Cloud Connection Service PID field. The identifier must be a valid kura.service.pid and, in case of a Kapua Cloud Connection, it must start with the org.eclipse.kura.cloud.CloudService- prefix. A valid identifier can be org.eclipse.kura.cloud.CloudService-KAPUA. As an alternative it is possible to reconfigure the existing org.eclipse.kura.cloud.CloudService Cloud Connection.

                        3. Configure the MQTTDataTrasport service.

                          Click on the MQTTDataTrasport-KAPUA tab and fill the parameters required for establishing the MQTT connection:

                          • Broker-url
                          • Topic Context Account-Name
                          • Username
                          • Password
                        4. Configure the DataService-KAPUA service.

                          In order to enable automatic connection, set the Connect Auto-on-startup parameter to true

                      "},{"location":"cloud-api/user-guide/#creating-and-configuring-a-new-publisher","title":"Creating and configuring a new Publisher","text":"
                      1. Select to the connection to be used from the list.

                      2. Click on the New Pub/Sub button.

                      3. Select the type of component to be created, from the Available Publisher/Subscriber factories drop down list, in order to create a Publisher select the org.eclipse.kura.cloud.publisher.CloudPublisher entry.

                      4. Enter an unique kura.service.pid identifier in the New Publisher/Subscriber PID field.

                      5. Click Apply, you should see the publisher configuration

                      6. Select and configure the newly created publisher instance, and then click Apply

                      "},{"location":"cloud-api/user-guide/#binding-an-application-to-a-publisher","title":"Binding an application to a publisher","text":"
                      1. Select the application instance configuration

                      2. Find the configuration entry that represents a Publisher reference.

                      3. Click on the Select available targets link and select the desired Publisher instance to bind to.

                      4. Click on Apply

                      "},{"location":"cloud-platform/kura-aws-cloud/","title":"Amazon AWS IoT\u2122 platform","text":""},{"location":"cloud-platform/kura-aws-cloud/#overview","title":"Overview","text":"

                      This section provides a guide on connecting an Eclipse Kura\u2122 device to the Amazon AWS IoT platform.

                      "},{"location":"cloud-platform/kura-aws-cloud/#prerequisites","title":"Prerequisites","text":"
                      • In order to connect a device to Amazon AWS IoT Kura version 1.3 or greater is required.
                      • An Amazon AWS account is also needed.
                      "},{"location":"cloud-platform/kura-aws-cloud/#device-registration","title":"Device registration","text":"

                      The first step involves the registration of the new device on AWS, this operation can be done using the AWS Web Console or with the AWS CLI command line tool, in this guide the Web based console will be used.

                      "},{"location":"cloud-platform/kura-aws-cloud/#1-access-the-aws-iot-management-console","title":"1. Access the AWS IoT management console.","text":"

                      This can be done by logging in the AWS console and selecting IoT Core from the services list, in the Internet of Things section.

                      "},{"location":"cloud-platform/kura-aws-cloud/#2-create-a-default-policy-for-the-device","title":"2. Create a default policy for the device.","text":"

                      This step involves creating a default policy for the new device, skip if an existing policy is already available.

                      Access the main screen of the console and select Secure -> Policies from the left side menu and then press the Create button, in the top right area of the screen.

                      Fill the form as follows and then press the Create button:

                      • Action -> iot:Connect, iot:Publish, iot:Subscribe, iot:Receive, iot:UpdateThingShadow, iot:GetThingShadow, iot:DeleteThingShadow
                      • Resource ARN -> *
                      • Effect -> Allow

                      This will create a policy that allows a device to connect to the platform, publish/subscribe on any topic and manage its thing shadow.

                      "},{"location":"cloud-platform/kura-aws-cloud/#3-register-a-new-device","title":"3. Register a new device.","text":"

                      Devices on the AWS IoT platform are called things, in order to register a new thing select Manage -> Things from the left side menu and then press the Create button, in the top right section of the screen. Select Create a single thing.

                      Enter a name for the new device and then press the Next button, from now on kura-gateway will be used as the device name.

                      "},{"location":"cloud-platform/kura-aws-cloud/#4-create-a-new-certificate-for-the-device","title":"4. Create a new certificate for the device.","text":"

                      The AWS IoT platform uses SSL mutual authentication, for this reason it is necessary to download a public/private key pair for the device and a server certificate. Click on Create certificate to quickly generate a new certificate for the new device.

                      Certificates can be managed later on by clicking on Secure -> Certificates, in the left part of the console.

                      "},{"location":"cloud-platform/kura-aws-cloud/#5-download-the-device-ssl-keys","title":"5. Download the device SSL keys.","text":"

                      You should see a screen like the following:

                      Download the 3 files listed in the table and store them in a safe place, they will be needed later, also copy the link to the root CA for AWS IoT in order to be able to retrieve it later from the device.

                      Press the Activate button, and then on Attach a policy.

                      "},{"location":"cloud-platform/kura-aws-cloud/#6-assign-the-default-policy-to-the-device","title":"6. Assign the default policy to the device.","text":"

                      Select the desired policy and then click on Register thing.

                      A policy can also be attached to a certificate later on perforiming the following steps:

                      Enter the device configuration section, by clicking on Manage -> Things and then clicking on the newly created device. Click on Security on the left panel and then click on the certificate entry (it is identified by an hex code), select Policies in the left menu, you should see this screen:

                      Click on Actions in the top left section of the page and then click on Attach policy, select the default policy previously created and then press the Attach button.

                      "},{"location":"cloud-platform/kura-aws-cloud/#device-configuration","title":"Device configuration","text":"

                      The following steps should be performed on the device, this guide is based on Kura 3.1.0 version and has been tested on a Raspberry PI 3.

                      "},{"location":"cloud-platform/kura-aws-cloud/#7-create-a-java-keystore-on-the-device","title":"7. Create a Java keystore on the device.","text":"

                      The first step for using the device keys obtained at the previous step is to create a new Java keystore containing the Root Certificate used by the Amazon IoT platform, this can be done executing the following commands on the device:

                      sudo mkdir /opt/eclipse/kura/security\n
                      cd /opt/eclipse/kura/security\n
                      curl https://www.amazontrust.com/repository/AmazonRootCA1.pem > /tmp/root-CA.pem\n
                      sudo keytool -import -trustcacerts -alias aws -file /tmp/root-CA.pem -keystore cacerts.ks -storepass changeit\n

                      If the last command reports that the certificate already exist in the system-wide store type yes to proceed. The code above will generate a new keystore with changeit as password, change it if needed.

                      "},{"location":"cloud-platform/kura-aws-cloud/#8-configure-the-ssl-parameters-using-the-kura-web-ui","title":"8. Configure the SSL parameters using the Kura Web UI.","text":"
                      1. Open the Kura Web Console and enter select the Settings entry in the left side menu and then click on SSL Configuration, you should see this screen:

                        Change the Keystore path parameter to /opt/eclipse/kura/security/cacerts.ks if needed.

                        Change the settings in the form to match the screen above, set Default protocol to TLSv1.2, enter changeit as Keystore Password (or the password defined at step 7).

                        Warning

                        Steps from 8.2 to 8.6 will not work on Kura 3.2.0 due to a known issue. On this version, private key and device certificate need to be manually added to the keystore using the command line. If you are running Kura 3.2.0, proceed with step 8.7.

                      2. Open the Kura Web Console and enter select the Settings entry in the left side menu and then click on Device SSL Certificate, you should see this screen:

                        Enter aws-ssl in the Storage Alias field.

                      3. The private key needs to be converted to the PKCS8 format, this step can be performed executing the following command on a Linux or OSX based machine:

                        openssl pkcs8 -topk8 -inform PEM -outform PEM -in xxxxxxxxxx-private.pem.key -out outKey.pem -nocrypt\n

                        where xxxxxxxxxx-private.pem.key is the file containing the private key downloaded at step 4.

                      4. Paste the contents of the obtained outKey.pem in the \"Private Key\" field.

                      5. Paste the contents of xxxxxxxxxx-certificate.pem.crt in the Certificate field.

                        You should see a screen like this

                      6. Click the Apply button to confirm.

                      7. Kura 3.2.0 only - manually import device certificate and private key into keystore.

                        On the host machine, open a terminal window in the folder containing the files downloaded at step 5 and execute the following command:

                        openssl pkcs12 -export -in xxxxxxxxxx-certificate.pem.crt -inkey xxxxxxxxxx-private.pem.key -name aws-ssl -out aws-ssl.p12\n

                        where xxxxxxxxxx-certificate.pem.crt is the original certificate downloaded from AWS and xxxxxxxxxx-private.pem.key is the private key.

                        The command will ask for a password, define a new password.

                        Copy the obtained aws-ssl.p12 file to the device into the /tmp folder using scp:

                        scp ./aws-ssl.p12 pi@<device-address>:/tmp\n

                        Replacing <device-address> with the hostname or ip address of the device.

                        Open a ssh connection to the device and enter the following command:

                        sudo keytool -importkeystore -deststorepass changeit -destkeystore /opt/eclipse/kura/security/cacerts.ks -srckeystore /tmp/aws-ssl.p12 -srcstoretype PKCS12\n

                        The command will ask for a password, enter the password defined when creating the aws-ssl.p12 file.

                        Restart Kura to reload the keystore.

                      "},{"location":"cloud-platform/kura-aws-cloud/#9-setup-a-new-cloud-connection","title":"9. Setup a new cloud connection","text":"
                      1. Click on Cloud Connections in the left panel, and setup a new cloud connection

                      2. Click on the New Connection button at the top of the page and set the following parameters in the dialog:

                        • Cloud Connection Factory PID -> org.eclipse.kura.cloud.CloudService
                        • Cloud Connection Service PID -> org.eclipse.kura.cloud.CloudService-AWS

                        Press the Create button to confirm and then select the newly created CloudService instance from the list.

                      3. Set the broker URL in the MqttDataTransport-AWS tab, it can be obtained from the AWS IoT Web Console clicking on the Settings entry in the bottom left section of the page, the URL will look like the following:

                        a1rm1xxxxxxxxx.iot.us-east-1.amazonaws.com\n

                        The mqtts protocol must be used, the value for the broker-url field derived from the URL above is the following:

                        mqtts://a1rm1xxxxxxxxx.iot.us-east-1.amazonaws.com:8883/\n
                      4. Clear the value of the username and password fields.

                      5. Set a value for the topic.context.account-name and client-id.

                        • Assign an arbitrary account name to topic.context.account-name (for example aws-test), this will be used by the CloudClient instances for building the topic structure.

                        • Enter the thing name in the client-id field (in this example kura-gateway).

                      6. In order for the previously added keys to be used for the SSL connection with the broker enter the Storage Alias defined in step 8.2 (e.g aws-ssl) as value for the ssl.certificate.alias field.

                      7. The setting lwt.topic under MqttDataTransport-AWS needs to be updated as well by entering a value not containing the $ character. This is required because of the fact that AWS IoT does not support topic names starting with $ (except for the $aws/ hierarchy).

                      8. Press the Apply button in the top left section to commit the changes to the MqttDataTransport-AWS.

                      9. Enter a name without the $ character for the topic.control-prefix setting in the CloudService-AWS tab, for example aws-control.

                      10. The Kura CloudService uses some well-known topics to allow remote device management and to report device state information, this features are not supported by default by AWS IoT, the following settings can be applied in the CloudService-AWS tab in order to avoid sending unnecessary messages:

                        • republish.mqtt.birth.cert.on.gps.lock -> false
                        • republish.mqtt.birth.cert.on.modem.detect -> false
                        • enable.default.subscriptions -> false
                      11. Click the Apply button to save the changes.

                      "},{"location":"cloud-platform/kura-aws-cloud/#10-connect-to-the-cloud-platform","title":"10. Connect to the cloud platform","text":"

                      Make sure the AWS CloudService instance is selected from the list in the top section of the page and click on the Connect button, if the connection to AWS IoT platform succeeds the Status of the instance will be reported as Connected.

                      "},{"location":"cloud-platform/kura-azure/","title":"Azure IoT Hub\u2122 platform","text":"

                      Starting from release 3.0, Eclipse Kura can connect to the Azure IoT Hub using the MQTT protocol. When doing so, Kura applications can send device-to-cloud messages. More information on the Azure IoT Hub and its support for the MQTT protocol can be found here. This document outlines how to configure and connect a Kura application to the Azure IoT Hub.

                      "},{"location":"cloud-platform/kura-azure/#get-azure-iot-hub-information","title":"Get Azure IoT Hub information","text":"

                      In order to properly configure Kura to connect to IoT Hub, some information are needed. You will need the hostname of the Azure IoT Hub, referred below as {iothubhostname}, the Id and the SAS Token of the device, referred as {device_id} and {device_SAS_token}. The hostname is listed on the \"Overview\" tab on the IoT Hub main page, while the device ID is shown on the \"Device Explorer\" tab. Finally, the SAS token can be generated using the iothub-explorer application that can be found here. To install the application, type on a shell:

                      npm install -g iothub-explorer\n

                      Then start a new session on your IoT Hub instance (it will expire in 1 hour):

                      iothub-explorer login \"{your-connection-string}\"\n

                      where {your-connection-string} is the connection string of your IoT Hub instance. It can be found on the \"Shared access policies\" tab under \"Settings\". Select the \"iothubowner\" policy and a tab will appear with the \"Connection string\u2014primary key\" option. Then list your devices:

                      iothub-explorer list\n

                      and get the SAS token for the {device-name} device:

                      iothub-explorer sas-token {device-name}\n

                      Be aware that the SAS token will expire in 1 hour by default, but using \"-d\" option it is possible to set a custom expiration time.

                      "},{"location":"cloud-platform/kura-azure/#ssl-certificates","title":"SSL certificates","text":"

                      In order to connect to your IoT Hub instance, Kura should trust the remote broker through a SSL certificate. The simpler way to get the IotHub certificate is to run the following command on a shell:

                      openssl s_client -showcerts -tls1 -connect {iothubhostname}:8883\n

                      The result is the SSL certificate chain. Copy all the certificates in the format:

                      -----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n

                      and paste them in to the \"Server SSL Certificate\" tab under \"Settings\" in Kura. Then click the Apply button and restart Kura to update the keystore.

                      "},{"location":"cloud-platform/kura-azure/#configuring-a-kura-cloud-stack-for-azure-iot-hub","title":"Configuring a Kura Cloud Stack for Azure IoT Hub","text":"

                      The Kura Gateway Administrative Console exposes all services necessary to configure a connection to the Azure IoT Hub. You can follow the steps outlined below to configure the connection to the Azure IoT Hub.

                      The first step is to create a new Kura Cloud stack. From the Kura Gateway Administrative Console:

                      • Select Cloud Connections in the navigation on the left and click New Connection to create a new Cloud connection
                      • In the dialog, select org.eclipse.kura.cloud.CloudService as the Cloud Connection Factory PID
                      • Enter a Cloud Connection Service PID name like org.eclipse.kura.cloud.CloudService-Azure
                      • Press the Create button to create the new Cloud stack

                      Now review and update the configuration of each Kura Cloud stack component as outline below.

                      • MqttDataTransport
                      • DataService
                      • CloudService
                      "},{"location":"cloud-platform/kura-azure/#mqttdatatransport","title":"MqttDataTransport","text":"

                      Modify the service configuration parameters as follows:

                      • broker-url - defines the URL of the Azure IoT MQTT broker. The URL value should be set as mqtts://{iothubhostname}:8883/

                        Note

                        An SSL connection (mqtts on port 8883) is required to connect to Azure IoT Hub\u2122.

                      • topic.context.account-name - insert devices as the MQTT topic prefix for the device-to-cloud and cloud-to-device messages

                      • username - insert {iothubhostname}/{device_id} as username for the MQTT connection
                      • password - insert {device_SAS_token} as password for the MQTT connection

                        Note

                        The format of the SAS Token is like:

                        SharedAccessSignature sig={signature-string}&se={expiry}&sr={URL-encoded-resourceURI}\n

                      • client-id - insert {device_id} as Client ID for the MQTT connection

                      • clean-session - make sure it is set to true
                      • lwt.topic - set the Will Topic to something #account-name/#client-id/messages/events/LWT

                      You can keep the default values of the remaining parameters, so save your changes by clicking the Apply button. A screen capture of the MqttDataTransport configuration is shown below.

                      "},{"location":"cloud-platform/kura-azure/#dataservice","title":"DataService","text":"

                      The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below.

                      In order for Kura to connect to Azure IoT Hub on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

                      Note

                      Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

                      "},{"location":"cloud-platform/kura-azure/#cloudservice","title":"CloudService","text":"

                      The default settings for the CloudService should be modified as follow to allow a connection to Azure IoT Hub .

                      • topic.control-prefix - insert devices as the MQTT topic prefix for the device-to-cloud and cloud-to-device messages
                      • encode.gzip - should be set false to avoid compression of the message payloads
                      • republish.mqtt.birth.cert.on.gps.lock - should be set false to avoid sending additional messages on GPS position lock
                      • republish.mqtt.birth.cert.on.modem.detect - should be set false to avoid sending additional messages on cellular modem update
                      • enable.default.subscriptions - should be set false to avoid subscriptions on Kura control topics for cloud-to-device
                      • payload.encoding - should be set to Simple JSON

                      The screen capture shown below displays the default settings for the CloudService.

                      "},{"location":"cloud-platform/kura-azure/#how-to-connect-and-disconnect-from-the-cloud-platform","title":"How to connect and disconnect from the cloud platform","text":"

                      The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

                      Note

                      Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

                      "},{"location":"cloud-platform/kura-azure/#kura-application-connecting-to-azure-iot-hub","title":"Kura Application Connecting to Azure IoT Hub","text":"

                      The Kura example publisher can be used to publish to the IoT Hub. The example configuration should be modified as follows.

                      • cloud.service.pid - insert the name of the new Kura Cloud stack, org.eclipse.kura.cloud.CloudService-Azure in this tutorial
                      • app.id - insert messages as application id
                      • publish.appTopic - insert events/ as publish topic

                      This configuration allows the publication on the default messages/events endpoint on the IoT Hub\u2122.

                      "},{"location":"cloud-platform/kura-ec-cloud/","title":"Eurotech Everyware Cloud\u2122 platform","text":"

                      Everyware Cloud provides an easy mechanism for connecting cloud-ready devices to IT systems and/or applications; therefore, connecting to Everyware Cloud is an important step in creating and maintaining a complete M2M application. Information on Everyware Cloud and its features can be found here. This document outlines how to connect to Everyware Cloud using the Kura Gateway Administrative Console.

                      "},{"location":"cloud-platform/kura-ec-cloud/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

                      The Kura Gateway Administrative Console exposes all services necessary for connecting to Everyware Cloud. The reference links listed below outline each service involved in the Everyware Cloud connection. It is recommended that each section be reviewed.

                      • CloudService
                      • DataService
                      • MqttDataTransport
                      "},{"location":"cloud-platform/kura-ec-cloud/#cloudservice","title":"CloudService","text":"

                      The default settings for the CloudService are typically adequate for connecting to Everyware Cloud. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

                      Warning

                      The \"Simple JSON\" payload encoding is not supported by Everyware Cloud. Use the default \"Kura Protobuf\" encoding instead.

                      "},{"location":"cloud-platform/kura-ec-cloud/#dataservice","title":"DataService","text":"

                      The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

                      In order for Kura to connect to Everyware Cloud on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

                      Note

                      Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

                      "},{"location":"cloud-platform/kura-ec-cloud/#mqttdatatransport","title":"MqttDataTransport","text":"

                      While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

                      • broker-url - defines the MQTT broker URL that was provided when the Eurotech Everyware Cloud account was established. Information on how to obtain the broker URL can be found here. In the MqttDataTransport configuration screen capture shown below, the broker-url is mqtt://broker-sbx.everyware.io:1883
                      • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
                      • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

                      Note

                      When connecting to Everyware Cloud, the username must have proper permissions. Information on users and permissions can be found here.

                      For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

                      "},{"location":"cloud-platform/kura-ec-cloud/#connectdisconnect","title":"Connect/Disconnect","text":"

                      The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

                      Note

                      Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

                      "},{"location":"cloud-platform/kura-hono/","title":"Eclipse Hono\u2122 platform","text":"

                      Eclipse Hono\u2122 provides remote service interfaces for connecting large numbers of IoT devices to a back end and interacting with them in a uniform way regardless of the device communication protocol. More information can be found here. This document outlines how to connect to Eclipse Hono using the Kura Gateway Administrative Console.

                      "},{"location":"cloud-platform/kura-hono/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

                      The Kura Gateway Administrative Console exposes all services necessary for connecting to Eclipse Hono. First of all, in the Cloud Connections section, a new Hono-enabled connection needs to be setup.

                      From the Cloud Connections section,

                      the user needs to create a new connection:

                      by specifying a valid PID:

                      The result should be like the one depicted in the following image:

                      The reference links listed below outline each service involved in the cloud connection. It is recommended that each section be reviewed.

                      • CloudService
                      • DataService
                      • MqttDataTransport
                      "},{"location":"cloud-platform/kura-hono/#cloudservice","title":"CloudService","text":"

                      The default settings for the CloudService are typically adequate for connecting to a Hono instance. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

                      "},{"location":"cloud-platform/kura-hono/#dataservice","title":"DataService","text":"

                      The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

                      In order for Kura to connect to Eclipse Hono on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

                      Note

                      Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

                      "},{"location":"cloud-platform/kura-hono/#mqttdatatransport","title":"MqttDataTransport","text":"

                      While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

                      • broker-url - defines the MQTT broker URL that was provided when the Eurotech Everyware Cloud account was established. In the MqttDataTransport configuration screen capture shown below, the broker-url is mqtt://broker-url:1883
                      • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
                      • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

                      For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

                      "},{"location":"cloud-platform/kura-hono/#connectdisconnect","title":"Connect/Disconnect","text":"

                      The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

                      Note

                      Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

                      "},{"location":"cloud-platform/kura-kapua/","title":"Eclipse Kapua\u2122 platform","text":"

                      Eclipse Kapua\u2122 is a modular platform providing the services required to manage IoT gateways and smart edge devices. Kapua provides a core integration framework and an initial set of core IoT services including a device registry, device management services, messaging services, data management, and application enablement. More information can be found here. This document outlines how to connect to Eclipse Kapua using the Kura Gateway Administrative Console.

                      "},{"location":"cloud-platform/kura-kapua/#using-the-kura-gateway-administrative-console","title":"Using the Kura Gateway Administrative Console","text":"

                      The Kura Gateway Administrative Console exposes all services necessary for connecting to Eclipse Kapua. The reference links listed below outline each service involved in the cloud connection. It is recommended that each section be reviewed.

                      • CloudService
                      • DataService
                      • MqttDataTransport
                      "},{"location":"cloud-platform/kura-kapua/#cloudservice","title":"CloudService","text":"

                      The default settings for the CloudService are typically adequate for connecting to a Kapua instance. The screen capture shown below displays the default settings for the CloudService. For details about each setting, please refer to CloudService.

                      Warning

                      The \"Simple JSON\" payload encoding is not supported by Kapua. Use the default \"Kura Protobuf\" encoding instead.

                      "},{"location":"cloud-platform/kura-kapua/#dataservice","title":"DataService","text":"

                      The majority of default settings in the DataService can be left unchanged. A screen capture of the DataService configuration is shown below. For complete details about the DataService configuration parameters, please refer to DataService.

                      In order for Kura to connect to Eclipse Kapua on startup, the connect.auto-on-startup option must be set to true. If this value is changed from false to true, Kura will immediately begin the connection process. It is recommended that the CloudService and MqttDataTransport are configured before setting the connect.auto-on-startup option to true.

                      Note

                      Changing the value of connect.auto-on-startup from true to false will not disconnect the client from the broker. This setting simply implies that Kura will not automatically connect on the next start of Kura.

                      "},{"location":"cloud-platform/kura-kapua/#mqttdatatransport","title":"MqttDataTransport","text":"

                      While the majority of default settings in the MqttDataTransport can be left unchanged, the following parameters must be modified:

                      • broker-url - defines the MQTT broker URL that was provided when the Kapua account was established.
                      • topic.context.account-name - defines the account name of the account to which the device is attempting to connect. In the MqttDataTransport configuration screen capture shown below, the account name is account-name
                      • username - identifies the user to be used when creating the connection. In the MqttDataTransport configuration screen capture shown below, the username is username.

                      For complete details about the MqttDataTransport configuration parameters, please refer to MqttDataTransport.

                      "},{"location":"cloud-platform/kura-kapua/#connectdisconnect","title":"Connect/Disconnect","text":"

                      The status panel can be used to manually connect or disconnect the client while Kura is running. The main button toolbar has a connect and disconnect button that may be used to control connectivity.

                      Note

                      Connecting or disconnecting the client via the status panel has no impact on Kura automatically connecting at startup. This capability is only controlled via the connect.auto-on-startup DataService setting.

                      "},{"location":"cloud-platform/kura-sparkplug/","title":"Eclipse Sparkplug\u00ae Cloud Connector","text":"

                      The org.eclipse.kura.cloudconnection.sparkplug.mqtt.provider package provides a Eclipse Kura Cloud Connection that implements the Eclipse Sparkplug\u00ae v3.0.0 specification.

                      Minimum requirements

                      This addon is compatible with Kura 5.2+.

                      "},{"location":"cloud-platform/kura-sparkplug/#introduction-to-eclipse-sparkplug","title":"Introduction to Eclipse Sparkplug","text":"

                      from Eclipse Sparkplug

                      Sparkplug is an open software specification that provides MQTT clients the framework to seamlessly integrate data from their applications, sensors, devices, and gateways within the MQTT Infrastructure. It is specifically designed for use in Industrial Internet of Things (IIoT) architectures to ensure a high level of reliability and interoperability.

                      The specification aims fulfill the following 3 goals:

                      1. Define a common MQTT topic namespace.
                      2. Define a common MQTT state management.
                      3. Define a common MQTT payload.

                      To achieve that, the Eclipse Sparkplug specification defines an architecture (see picture below) and 4 main actors:

                      • Device: a collection of related data points, which may represent a physical device (like a PLC, a set of sensors, etc.) and that notify the value or quality change of their data points. In this cloud connection, a Device is represented by the attached Cloud Publishers.
                      • Edge Node: the gateway that is responsible of the interaction with the MQTT broker and that estabilished sessions with the data-consuming Host Applications. This Cloud Connection assumes the role of Edge Node.
                      • MQTT Server: a MQTT server that supports the v3.1.1 (or v5.0) version of the protocol.
                      • Host Application: the data-consuming application that subscribes to the MQTT messages generated by the Edge Nodes. A Primary Host Application is responsible of controlling and monitoring Edge Nodes, which can be configured to modify their behavior based on the state of the Primary Host Application (for example, the application goes offline). You can imagine the Primary Host Application for Eclipse Kura being Eclipse Kapua.

                      Image from https://sparkplug.eclipse.org/specification/version/3.0/documents/sparkplug-specification-3.0.0.pdf

                      The main principles upon which the Specification is based on can be summarized as follows:

                      1. PubSub Protocol: this is the main topology upon which the architecture is developed.
                      2. Report by Exception (RBE): messages need to be sent by the Edge Node only when values at the edge change, and the message should contain only the value/metrics that changed. There is no need for continuos polling; although it is higly discouraged, it is not mandatory to have Edge Nodes apply the RBE.
                      3. Continuos Session Awareness: Host Applications are aware of the state of the Edge Nodes, and Edge Nodes are aware of the state of the Host Applications. This continuos session awareness is achieved by the means of birth and death certificates and state messages (continue the reading to find out more) and is the key to allow reporting by exception.
                      4. Birth and Death certificates: these messages represent the state of Edge Nodes and Devices (online/offline + data that will be reported). The birth messages are always the first ones that are sent from the Edge Node, and the delivery of death certificates is ensured through the MQTT Will message, even if the connection is lost ungracefully. The birth messages always contain all the metrics that the Device or Edge Node will ever report on. If a new metric is added or a metric gets removed, then a new session needs to be estabilished.
                      5. Connection Persistence: with the mechanisms above, the connection does not need to be persistent. For example, an Edge Node that disconnects gracefully with a MQTT DISCONNECT packet will not be seen as \"dead\" from the host application because no death certificate has been triggered (Will messages are sent only on failures). Hence, the Edge Node can implement a logic where it remains connected only during the timeframe needed for sending the new data.

                        Warning

                        This Cloud Connection maintains a persistent connection to the MQTT server.

                      "},{"location":"cloud-platform/kura-sparkplug/#eclipse-sparkplug-topic-namespace","title":"Eclipse Sparkplug Topic Namespace","text":"

                      All clients using the specification must adhere to the following topic namespace:

                      namespace/group_id/message_type/edge_node_id/[device_id]\n

                      where:

                      • namespace: defines the structure of the remaining elements and the encoding for the payload. With Sparkplug v3.0.0 the namespace to utilize is spBv1.0.
                      • group_id: some Edge Nodes can be related to each other identifying a group (for example, Edge Nodes in a given plant). This element is the identifier for the group.
                      • message_type: defines how to interpret and handle the payload. It encapsulates the semantic of the message and can be one of the following elements:
                        • NBIRTH/NDEATH: Edge Node birth and death certificates.
                        • DBIRTH/DDEATH: Device birth and death certificates.
                        • NDATA/DDATA: message containing data reported by the Edge Node or Device.
                        • NCMD/DMCD: message containing commands for the Edge Node or Device.
                        • STATE: message from the Primary Host Application indicating the state (offline/online) of the main consumer.
                      • edge_node_id: the identifier for the Edge Node.
                      • device_id (optional): the identifier for the Device. Must be unique under the same edge_node_id and must be always present on messages belonging to Devices (D- type messages).

                      The combination of group_id and edge_node_id must be unique and is called Edge Node Descriptor.

                      Tip

                      It is advised to have group_id, edge_node_id, and device_id as small but as descriptive as possible, for better efficiency.

                      "},{"location":"cloud-platform/kura-sparkplug/#operational-behavior","title":"Operational Behavior","text":"

                      This introduction will focus more on the Edge Node and Device, as they are of more interest for this Cloud Connection.

                      "},{"location":"cloud-platform/kura-sparkplug/#session-management","title":"Session Management","text":"

                      The session estabilishment procedure ensures the Edge Node to be subscribed to command-type messages for receiveing commands from the Host Application.

                      An Edge Node can (optionally, but incouraged) specify to be aware of a primary host application state and, in such case, it needs to subscribe to the relative STATE messages. The Edge Node will send the birth certificate (and thus completing the session init procedure) only after receiving the STATE message denoting the Primary Host Application is online. After connection, if the Edge Node receives a STATE message denoting the Primary Host Application is offline, it must restart the session estabilishment procedure.

                      On connection, the Edge Node sets a MQTT Will message containing a NDEATH certificate. Doing so, if the MQTT broker does not receive any communication within the Keep Alive period (client lost connection), it will send the Edge Node NDEATH certificate on all subscribers. A birth/death sequence number bdSeq is maintained in the Edge Node to match NBIRTH with NDEATH messages in the Host Application. Each bdSeq in the NDEATH message is matched with the corresponding bdSeq of the previous NBIRTH message. This allows the Host Application tracking the state of the Edge Nodes and mark not up-to-date metrics as STALE.

                      A Device can send a device DBIRTH message after a Edge Node NBIRTH has been sent. The DBIRTH message contains all the metrics that the device will ever report on. If a new metric is added or an existing one removed, then the Device session needs to be re-estabilished. If the Edge Node looses the connection to some of its Devices, then it needs to send a Device DDEATH certificate on his behalf. The data-consuming Host Application will then mark that particular Device as offline and mark its metrics as STALE. Once the session is estabilished, the Device can publish the changed metrics using the DDATA message type.

                      "},{"location":"cloud-platform/kura-sparkplug/#multiple-mqtt-server-topologies","title":"Multiple MQTT Server Topologies","text":"

                      A Primary Host Application must publish its STATE message every time it connects to the MQTT broker. This ensures the Edge Nodes to be aware of its status as long as they remain connected to the MQTT server.

                      At any point in time, an Edge Node can be connected to at most one MQTT server. If multiple MQTT servers are defined each time the Edge Node receives an offline STATE message from its Primary Host Application it needs to terminate the session and estabilish a new one with the next MQTT broker.

                      "},{"location":"cloud-platform/kura-sparkplug/#further-resources","title":"Further Resources","text":"
                      • Eclipse Sparkplug v3.0.0 specification
                      • HiveMQ Sparkplug guides collection
                      • Eclipse Sparkplug FAQ
                      "},{"location":"cloud-platform/kura-sparkplug/#cloud-connection-configuration","title":"Cloud Connection Configuration","text":""},{"location":"cloud-platform/kura-sparkplug/#cloud-endpoint-layer-configuration","title":"Cloud Endpoint Layer Configuration","text":"

                      The cloud endpoint layer allows to attach CloudPublishers and CloudSubscribers to publish/subscribe messages on Sparkplug topics.

                      "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-device","title":"Sparkplug Device","text":"

                      Each CloudPublisher attached to this cloud connection acts as a Sparkplug Device. The corresponding configuration is shown in the picture below.

                      The parameter specified as device.id will dictate the Sparkplug device identifier used to publish messages from this cloud publisher. A device DBIRTH message is immediately sent from this publisher when the first publish occurs or when a the set of published metrics is changed. The subsequent messages will be published as Sparkplug device data (DDATA message type).

                      The Sparkplug Device implemented by this publisher does not support the following features (optional in the Eclipse Sparkplug specification):

                      • tck-id-operational-behavior-device-ddeath: Device death messages (DDEATH message type) since the usual way to publish data from the Wire Graph using a WireAsset attached to a CloudPublisher has no implementation for reporting error states (see WireAsset.onWireReceive)
                      • tck-id-payloads-alias-uniqueness: Sparkplug aliases for metrics
                      • tck-id-message-flow-device-dcmd-subscribe: writing to outputs, hence it will not subscribe to device command messages (DCMD message type)
                      "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-device-payload","title":"Sparkplug Device Payload","text":"

                      The payload of DDATA message will be encoded using the Sparkplug B Protobuf definition converting the KuraPayload into Sparkplug payload as follows:

                      • Metrics from KuraPayload.metric() become Sparkplug metrics. Only the name, timestamp, datatype and value components are added. The timestamp is set to the publishing instant. The datatype is inferred from the Java type as follows:

                        Java Type Sparkplug DataType Boolean DataType.Boolean byte[] DataType.Bytes Double DataType.Double Float DataType.Float Byte DataType.Int8 Short DataType.Int16 Integer DataType.Int32 Long DataType.Int64 String DataType.String Date DataType.DateTime BigInteger DataType.UInt64

                        All other Java types will cause the application to throw an Exception.

                      • KuraPayload.getBody(), if non null, will be copied into the body of the Sparkplug payload

                      • KuraPayload.getPosition(), if not null, will be used to create the following metrics from the KuraPosition object, if the value in there is not null (with the corresponding Sparkplug data types):

                        • kura.position.altitude: DataType.Double
                        • kura.position.heading: DataType.Double
                        • kura.position.latitude: DataType.Double
                        • kura.position.longitude: DataType.Double
                        • kura.position.precision: DataType.Double
                        • kura.position.satellites: DataType.Int32
                        • kura.position.status DataType.Int32
                        • kura.position.speed: DataType.Double
                        • kura.position.timestamp: DataType.DateTime
                      • KuraPayload.getTimestamp(), if not null, it will be used as the timestamp metric of the Sparkplug payload

                      "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-subscriber","title":"Sparkplug Subscriber","text":"

                      This cloud connections allows creating a Cloud Subscriber that will subscribe to a generic set of topics. The configuration is shown in the picture below.

                      It is assumed that the payloads received by this Cloud Connection are encoded using the Sparkplug B Protobuf definition. Users of this Cloud Subscriber should expect to receive KuraMessages containing a KuraPayload such that:

                      • If non null, the Sparkplug body is used to set KuraPayload.setBody()
                      • If non null, the Sparkplug seq Metric is converted into a Kura metric with name seq
                      • If non null, the Sparkplug timestamp is used as Eclipse Kura's payload timestamp
                      • All Sparkplug Metrics are converted into Kura metrics with the same name and the following conversion rules:

                        Sparkplug ValueCase Java Type Boolean boolean Bytes byte[] Dataset byte[] Double double Extension byte[] Float float Integer int Long long String String Template byte[] default null

                        The metric timestamp is not supported in Eclipse Kura, therefore this information is lost at conversion.

                      "},{"location":"cloud-platform/kura-sparkplug/#data-service-layer-configuration","title":"Data Service Layer Configuration","text":"

                      The DataService layer used in this component is the org.eclipse.kura.data.DataService implementation. Please refer to the Data Service Configuraion page for further details.

                      "},{"location":"cloud-platform/kura-sparkplug/#data-transport-layer-configuration","title":"Data Transport Layer Configuration","text":"

                      The Sparkplug Data Transport layer bridges the incoming requests to the underlying Eclipse Paho MQTT v3.1.1 client following the Sparkplug specification. In particular, the Data Transport Layer ensures the following.

                      • Edge Node Sparkplug Session Estabilishment: the Edge Node (this Cloud Connection) estabilishes a new Sparkplug session upon connection. An optional Primary Host Application ID can be specified (see picture below) to make the Cloud Connection wait until the Primary Host Application is online before publishing NBIRTH and DBIRTH messages.
                      • Edge Node Sparkplug Session Termination: upon disconnection, the Edge Node follows the required specification statements. In particular, multiple space-separated Server URIs can be specified in the component's configuration (see picture below). When a Primary Host Application ID is defined and it receives a STATE message denoting that the configured primary application is offline, then reconnection attempts are made cycling through the Server URIs list. When the last server fails to connect, then the traversal is restarted from the start of the list.
                      • Edge Node NCMD handler: upon reception of a valid NCMD message, the Transport layer checks if it contains a Node Control/Rebirth metric, and, if set to true, restarts the session estabilishment procedure without sending an MQTT CONNECT packet (client connection is not closed, only BIRTH messages are re-sent).

                      The Sparkplug Data Transport layer is configured to wait for a random period of time between 0sec and 5sec before each connection attempt. This is to ensure that, on large deployments, the target MQTT servers and Host Applications will dilute session estabilishment requests by some margin. This behavior is not part of the Sparkplug specification.

                      This cloud connection supports SSL connections to the connecting broker. The option SslManagerService.target allows, as an OSGi target filter, to specify the pid of the SslManagerService instance to use for creating SSL connections for broker URIs that use the ssl:// protocol. The default socket factory is used otherwise.

                      "},{"location":"cloud-platform/kura-sparkplug/#sparkplug-implementation-details","title":"Sparkplug Implementation Details","text":""},{"location":"cloud-platform/kura-sparkplug/#edge-node","title":"Edge Node","text":"
                      • The NBIRTH message sent by this cloud connection will not contain any metrics, except for the mandatory ones required by the specification.
                      • This cloud connection does not send any NDATA messages in its default implementation.
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/","title":"Apache Camel\u2122 as a Kura application","text":"

                      As of 2.1.0 Kura provides a set of different ways to implement an application backed by Camel:

                      • Simple XML route configuration
                      • Custom XML route configuration
                      • Custom Java DSL definition
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-cloud-endpoint","title":"Kura cloud endpoint","text":"

                      Kura provides a special \"Kura cloud endpoint\" which allows to publish or subscribe to the Kura Cloud API. The default component name for this component is kura-cloud but it may be overridden in the following use cases.

                      The default component will only be registered once the default Kura Cloud API is registered with OSGi. This instance is registered with the OSGi property kura.service.pid=org.eclipse.kura.cloud.CloudService.

                      If you want to publish to a different cloud service instance you can either manually register a new instance of this endpoint, or e.g. use a functionality like the Simple XML router provides: also see selecting a cloud service.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#endpoint-uri","title":"Endpoint URI","text":"

                      The URI syntax of the endpoint is (assuming the default component name): kura-cloud:appid/topic. Where appid is the application ID registered with the Cloud API and topic is the topic to use.

                      The following URI parameters are supported by the endpoint:

                      Name Type Default Description applicationId String From URI path The application ID used with the Cloud API topic String From URI path The default topic name to publish/subscribe to when no header value is specified qos Integer 0 The QoS value when publishing to MQTT retain Boolean false The default retain flag when publishing to MQTT priority Integer 5 The default priority value control Boolean false Whether to publish/subscribe on the control or data topic hierarchy deviceId String empty The default device ID when publishing/subscribing to control topics

                      The following header fields are supported. If a value is not set when publishing it is taken from the endpoint configuration:

                      Name Type Description CamelKuraCloudService.topic String The name of the topic to publish to or from which the message was received CamelKuraCloudService.qos Integer The QoS to use when publishing to MQTT CamelKuraCloudService.retain Boolean The value of the retain flag when publishing to MQTT CamelKuraCloudService.control Boolean Whether to publish/subscribe on the control or data topic hierarchy CamelKuraCloudService.deviceId String The device ID when publishing to control topics"},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#cloud-to-cloud-messaging","title":"Cloud to cloud messaging","text":"

                      As already described, header values override the endpoint settings. This allows for a finer grained control with Camel messaging. However this can cause unexpected behavior when two Cloud API endpoints are bridged. Camel can received from a Cloud endpoint but also publish to it. Now it is possible to write Camel routes with exchange messages, receiving from one Cloud API, pushing to another.

                      -------------------             -------------------\n| Cloud Service A |    <--->    | Cloud Service B |\n-------------------             -------------------\n

                      Which could result in a Camel route XML like:

                      <route id=\"bridgeLocalToRemote\">\n  <from uri=\"local-cloud:sensor/sensor1\" />\n  <to   uri=\"upstream-cloud:gateway1/all-sensors\" />\n</route>\n

                      However the Consumer (from) would set the topic header value with the topic name it received the message from. And the Producer (to) would get its topic from the URI overriden by that header value.

                      In order to fix this behavior the header field has to be cleared before publishing:

                      <route id=\"bridgeLocalToRemote\">\n  <from uri=\"local-cloud:sensor/sensor1\" />\n  <removeHeaders pattern=\"CamelKuraCloudService.topic\"/>\n  <to   uri=\"upstream-cloud:gateway1/all-sensors\" />\n</route>\n
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#simple-xml-routes","title":"Simple XML routes","text":"

                      Eclipse Kura 2.1.0 introduces a new \"out-of-the-box\" component which allows to configure a set of XML based routes. The component is called \"Camel XML router\" and can be configured with a simple set of XML routes.

                      The following example logs all messages received on the topic foo/bar to a logger named MESSAGE_FROM_CLOUD:

                      <routes xmlns=\"http://camel.apache.org/schema/spring\">\n  <route id=\"cloudConsumer\">\n    <from uri=\"kura-cloud:foo/bar\"/>\n    <to uri=\"log:MESSAGE_FROM_CLOUD\"/>\n  </route>\n</routes>\n

                      But it is also possible to generate data and push to upstream to the cloud service:

                      <route id=\"route1\">\n  <from uri=\"timer:foo\"/>\n  <setBody>\n    <method ref=\"payloadFactory\" method=\"create('random',${random(10)})\"/>\n  </setBody>\n  <bean ref=\"payloadFactory\" method=\"append('foo','bar')\"/>\n  <to uri=\"stream:out\"/>\n  <to uri=\"kura-cloud:myapp/test\"/>\n</route>\n

                      This example to run a timer named \"foo\" every second. It uses the \"Payload Factory\" bean, which is pre-registered, to create a new payload structure and then append a second element to it.

                      The output is first sent to a logger and then to the cloud source.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#defining-dependencies-on-components","title":"Defining dependencies on components","text":"

                      It is possible to use the Web UI to define a list of Camel components which must be present in order for this configuration to work. For example if the routes make use of the \"milo-server\" adapter for providing OPC UA support then \"milo-server\" can be added and the setup will wait for this component to be registered with OSGi before the Camel context gets started.

                      The field contains a list of comma separated component names: e.g. milo-server, timer, log

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#selecting-a-cloud-service","title":"Selecting a cloud service","text":"

                      It is also possible to define a map of cloud services which will be available for upstream connectivity. This makes use of Kura's \"multi cloud client\" feature. CloudService instances will get mapped from either a Kura Service PID (kura.service.pid, as shown in the Web UI) or a full OSGi filter. The string is a comma seperated, key=value string, where the key is the name of the Camel cloud the instance will be registered as and the value is the Kura service PID or the OSGi filter string.

                      For example: cloud=org.eclipse.kura.cloud.CloudService, cloud-2=foobar

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#custom-camel-routers","title":"Custom Camel routers","text":"

                      If a standard XML route configuration is not enough then it is possible to use XML routes in combination with a custom OSGi bundle or a Java DSL based Camel approach. For this to work a Kura development setup is required, please see Getting started for more information.

                      The implementation of such Camel components follow the standard Kura guides for developing components, like, for example, the ConfigurableComponent pattern. This section only describes the Camel specifics.

                      Of course it is also possible to follow a very simple approach and directly use the Camel OSGi functionalities like org.apache.camel.core.osgi.OsgiDefaultCamelContext.

                      Note

                      Kura currently doesn't support the OSGi Blueprint approach

                      Kura support for Camel is split up in two layers. There is a more basic support, which helps in running a raw Camel router. This is CamelRunner which is located in the package org.eclipse.kura.camel.router. And then there are a few abstract basic components in the package org.eclipse.kura.camel.component which help in creating Kura components based on Camel.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#camel-components","title":"Camel components","text":"

                      The base classes in org.eclipse.kura.camel.component are intended to help creating new OSGi DS components base on Camel.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#xml-based-component","title":"XML based component","text":"

                      For an XML based approach which can be configured through the Kura ConfigurationService the base class AbstractXmlCamelComponent can be used. The constructor expectes the name of a property which will contain the Camel XML router information when it gets configured through the configuration service. It will automatically parse and apply the Camel routes.

                      The method void beforeStart(CamelContext camelContext) may be used in order to configure the Camel context before it gets started.

                      Every time the routes get updated using the modified(Map<String, Object>) method, the route XML will be re-parsed and routes will be added, removed or updated according to the new XML.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#java-dsl-based-component","title":"Java DSL based component","text":"

                      In order to create a Java DSL based router setup the base class AbstractJavaCamelComponent may be used, which implements and RouteBuilder class, a simple setup might look like:

                      import org.eclipse.kura.camel.component.AbstractJavaCamelComponent;\n\nclass MyRouter extends AbstractJavaCamelComponent {\n   public void configure() throws Exception {\n      from(\"direct:test\")\n         .to(\"mock:test\");\n   }\n}\n
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#using-the-camelrunner","title":"Using the CamelRunner","text":"

                      The CamelRunner class is not derived from any OSGi or Kura base class and can be used in scenarios where more flexibility is required. It allows to define a set of pre-requisites for the Camel context. It is for example possible to define a dependency on a Kura cloud service instance and a Camel component provider. Once the runner is started it will listen for OSGi services resolving those dependencies and then starting up the Camel context. The following example shows how to set up a Camel context using the CamelRunner:

                      // create a new camel Builder\n\nBuilder builder = new CamelRunner.Builder();\n\n// add service dependency\n\nbuilder.cloudService(\"kura.service.pid\", \"my.cloud.service.pid\");\n\n// add Camel component dependency to 'milo-server'\n\nbuilder.requireComponent(\"milo-server\");\n\nCamelRunner runner = builder.build();\n\n// set routes\n\nrunner.setRoutes ( new RouteBuilder() {\n  public void configure() throws Exception {\n    from(\"direct:test\")\n      .to(\"mock:test\");\n  }\n} );\n\n// set routes\n\nrunner.start ();\n

                      It is also possible to later update routes with a call to setRoutes:

                      // maybe update routes at a later time\n\nrunner.setRoutes ( /* different routes */ );\n
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#examples","title":"Examples","text":"

                      The following examples can help in getting started.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-example-publisher","title":"Kura Camel example publisher","text":"

                      The Camel example publisher (org.eclipse.kura.example.camel.publisher) can be used as an reference for starting. The final OSGi bundle can be dropped into a Kura application an be started. It allows to configure dynamically during runtime and is capable of switching CloudService instances dynamically.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-quickstart","title":"Kura Camel quickstart","text":"

                      The Camel quickstart project (org.eclipse.kura.example.camel.quickstart) shows two components, Java and XML based, working together. The bundle can also be dropped into Kura for testing.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-app/#kura-camel-aggregation","title":"Kura Camel aggregation","text":"

                      The Camel quickstart project (org.eclipse.kura.example.camel.aggregation) shows a simple data aggregation pattern with Camel by processing data and publishing the result.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel-cloud/","title":"Apache Camel\u2122 as a Kura cloud service","text":"

                      The default way to create a new cloud service instance backed by Camel is to use the new Web UI for cloud services. A new cloud service instance of the type org.eclipse.kura.camel.cloud.factory.CamelFactory has to be created. In addition to that a set of Camel routes have to be provided.

                      The interface with the Kura application is the Camel vm component. Information set \"upstream\" from the Kura application can be received by the Camel cloud service instance of the following endpoint vm:camel:example. Where camel is the application id and example is the topic.

                      The following code snippet writes out all of the Kura payload structure received on this topic to the logger system:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<routes xmlns=\"http://camel.apache.org/schema/spring\">\n\n  <route id=\"camel-example-topic\">\n    <from uri=\"vm:camel:example\"/>\n    <split>\n      <simple>${body.metrics().entrySet()}</simple>\n      <setHeader headerName=\"item\">\n        <simple>${body.key()}</simple>\n      </setHeader>\n      <setBody>\n        <simple>${body.value()}</simple>\n      </setBody>\n      <toD uri=\"log:kura.data.${header.item}\"/>\n    </split>\n  </route>\n\n</routes>\n

                      The snippet splits up the incoming KuraPayload structure and creates a logger called kura.data.<metric> for each metric and writes out the actual value to it. The output in the log file should look like:

                      2016-11-14 16:14:34,539 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:34,566 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.226808617581144]\n2016-11-14 16:14:35,575 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:35,602 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.27218775669447]\n2016-11-14 16:14:36,539 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.intValue - Exchange[ExchangePattern: InOnly, BodyType: Long, Body: 19]\n2016-11-14 16:14:36,567 [Camel (camel-10) thread #18 - vm://camel:example] INFO  k.d.doubleValue - Exchange[ExchangePattern: InOnly, BodyType: Double, Body: 10.314456684208022]\n
                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel/","title":"Apache Camel\u2122 integration overview","text":"

                      Note

                      This document describes the Camel integration for Kura 2.1.0

                      Kura provides two main integration points for Camel:

                      • Camel as a Kura application
                      • Camel as a Kura cloud service

                      The first allows one to configure Camel to provide data and receive commands from any CloudService instance which is configured in Kura. For example the default CloudService instance which is backed by MQTT.

                      The second approach allows one to create a custom CloudService implementation and route data coming from other Kura applications with the routes provided by this Camel context.

                      "},{"location":"cloud-platform/apache-camel-integration/kura-camel/#deploying-additional-camel-components","title":"Deploying additional Camel components","text":"

                      Kura comes with the following Camel components pre-installed:

                      • camel-core
                      • camel-core-osgi
                      • camel-stream

                      If additional Camel components are required, they can be installed using deployment packages (DP), as common with Kura.

                      There are pre-packaged DPs available for e.g. AMQP, OPC UA, MQTT and other Camel components outside of the Kura project.

                      "},{"location":"connect-field-devices/IO-apis/","title":"I/O APIs","text":"

                      The full Eclipse Kura API reference is available here.

                      In this page, the developer can find a synthetic grouping of the I/O APIs added starting from Kura 3.1.0.

                      • Drivers

                        • ChannelDescriptor
                        • ChannelListener
                        • ChannelRecord
                        • ConnectionException
                      • Assets

                        • AssetConfiguration
                        • Channel
                      "},{"location":"connect-field-devices/asset-implemetation/","title":"Asset implementation","text":"

                      An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

                      Assets can be used as Wire Components to access the resources referenced by the defined channels inside a Wire Graph, see the Assets as Wire Components guide for more details.

                      "},{"location":"connect-field-devices/asset-implemetation/#channel-example","title":"Channel Example","text":"

                      To further describe the concept of Channel and Asset, the following table shows a set of PLC register addresses as provided in a typical PLC documentation.

                      Name Entity Address LED1 COILS 2049 LED2 COILS 2050 LED3 COILS 2051 LED4 RED COILS 2052 LED4 GREEN COILS 2053 LED4 BLUE COILS 2054 Counter 3 INPUT REGISTERS 515 Quad Counter INPUT REGISTERS 520 Toggle 4 DISCRETE INPUTS 2052 Toggle 5 DISCRETE INPUTS 2053 Toggle 6 DISCRETE INPUTS 2054 Reset Counter 3 COILS 3075 Reset Quad Counter COILS 3084

                      The corresponding Channels definition in the Asset is as follows:

                      As shown in the previous image, the Channel definition in an Asset results easily mappable to what available in a generic PLC documentation.

                      Once defined the Channels in an Asset, a simple Java application that leverages the Asset API can easily communicate with the Field device by simply referring to the specific Channel of interest.

                      "},{"location":"connect-field-devices/asset-implemetation/#channel-definition","title":"Channel Definition","text":"
                      • enabled: each channel can be separately enabled using this flag.
                      • name: unique user-friendly name for a channel
                      • type: represents the type of operation supported. Possible values are: READ, WRITE, READ/WRITE
                      • value.type: represents the data type that will be used when creating the Wire Envelope for the connected components and the output value for READ channel.
                      • scale: an optional scaling factor to be applied only to the numeric values retrieved from the field. It is parsed as a double. See below for more details.
                      • offset: an optional offset value that will be added only to the numeric values retrieved from the field. It is parsed as a double. See below for more details.
                      • scaleoffset.type: Allows to customise the way scale and offset is applied. See below for more details.
                      • unit: an optional string value that will be added to the asset channel read to represent the unit of measure associated to that specific channel.
                      • listen: if supported by the associated driver, allows to receive notifications by the driver on events. This flag currently has effect only inside Kura Wires.

                      For the READ and READ/WRITE channels, the Asset typically asks the driver to provide a value based on the value.type. If the scaleoffset.type is LONG or DOUBLE, the type requested will be one of these and the final value (after the scale and offset operation) will be transformed into the data type expected by value.type.

                      "},{"location":"connect-field-devices/asset-implemetation/#arithmetic-with-scale-and-offset","title":"Arithmetic with scale and offset","text":"

                      The Asset supports applying a scale and offset to the values obtained by the attached Driver during read operations and to the values received in listen mode.

                      Warning

                      Application of scale and offset for write operations is not supported.

                      The following modes are implemented for computing scale and offset:

                      • DOUBLE
                      • INTEGER
                      • LONG
                      • FLOAT

                      The values of the scale and offset configuration parameters and the value obtained from the Driver are all converted to the mode type using the Java casting and then the following operation is performed: channel_value * scale + offset. The operation result is casted again to value.type to produce the final channel value.

                      The values of the scale and offset parameters are parsed from channel configuration as doubles.

                      The mode can be selected in the following way:

                      • If the scaleoffset.type is set to DOUBLE or LONG the corrisponding mode will be used. The largest representable LONG is 2^53
                      • If the scaleoffset.type value is DEFINED_BY_VALUE_TYPE the operation mode is determinated by value.type.

                      Example of DOUBLE mode: channel configured with:

                      • value.type: INTEGER
                      • scaleoffset.type: DOUBLE
                      • scale: 3.25
                      • offset: 1.5

                      Since scaleoffset.type is DOUBLE, the scale and offset mode is forced to DOUBLE.

                      If channel_value is 5 (INTEGER) the result is: (int) ((double) channel_value * (double) 3.25d + (double) 1.5d) = 17.

                      Example of INTEGER mode: channel configured with:

                      • value.type: INTEGER
                      • scaleoffset.type: DEFINED_BY_VALUE_TYPE
                      • scale: 3.25
                      • offset: 1.5

                      Since scaleoffset.type is DEFINED_BY_VALUE_TYPE, the mode determined by the value.type parameter (INTEGER) will be used.

                      If channel_value is 5 (INTEGER) the result is: (int) (channel_value * (int) 3.25d + (int) 1.5d) = 16.

                      Example of DOUBLE mode: channel configured with:

                      • value.type: DOUBLE
                      • scaleoffset.type: DEFINED_BY_VALUE_TYPE
                      • scale: 3.25
                      • offset: 1.5

                      If channel_value is 5 (DOUBLE) the result is: (double) (channel_value * (double) 3.25d + (double) 1.5d) = 17.75.

                      Warning

                      If the mode is LONG or INTEGER the decimal part of the scale and offset value will be discarded.

                      As the examples show the final result can be different depending on the used mode and value.type.

                      "},{"location":"connect-field-devices/asset-implemetation/#driver-specific-parameters","title":"Driver specific parameters","text":"

                      The parameters that are not included in list of driver independent parameters above are driver specific. These parameters are used to identify the resource addressed by the channel.

                      Driver specific parameters are described in the driver documentation.

                      "},{"location":"connect-field-devices/asset-implemetation/#other-asset-configurations","title":"Other Asset Configurations","text":"
                      • asset.desc: a user friendly description of the asset
                      • emit.all.channels: specifies whether the values of all READ or READ_WRITE channels should be emitted in case of a channel event. If set to true, the values for all channels will be read and emitted, if set to false, only the value for the channel related to the event will be emitted.
                      • timestamp.mode: if set to PER_CHANNEL, the component will emit a driver-generated timestamp per channel property. If set to SINGLE_ASSET_GENERATED, the component will emit a single timestamp per request, generated by the Asset itself before emitting the envelope. If set to SINGLE_DRIVER_GENERATED_MAX or SINGLE_DRIVER_GENERATED_MIN, the component will emit a single driver generated timestamp being respectively the max (most recent) or min (oldest) among the timestamps of the channels.
                      • emit.errors: Specifies whether errors should be included or not in the emitted envelope. Default is false.
                      • emit.on.change: If set to true, this component will include a channel value in the output emitted in Kura Wires only if it is different than the one from the previous read operation or event. Channel errors will always be emitted if emit.errors is set to true.
                      • emit.empty.envelopes: If set to false, this component will not emit empty envelopes. This property can be useful if combined with emit.on.change.
                      "},{"location":"connect-field-devices/asset-implemetation/#channels-download","title":"Channels download","text":"

                      The creation of the channels is a process that could be very time and effort consuming. For this reason, once the user has created the desired channels, it is possible to download the entire list from the web UI, by clicking the Download Channels button:

                      The list is downloaded in csv format and is represented in the form of a table, whose columns represent the entries for the options in the channel definition, while each row is equivalent to a specific channel. For example, the resulting csv file retrieved from downloading the list of channels in the image above is:

                      The resulting table is composed by some static, pre-defined options (the ones mentioned in Channel Definition section above, from enabled to listen) that are the same for each component and the ones introduced by the specific bundle under analisys. In this case, the modbus driver introduces these driver-specific options:

                      • unit.id
                      • primary.table
                      • memory.address
                      • data.order
                      • data.type
                      • array.data.length

                      The download tool is extremely useful in all those situations where the user wants to quickly load a long list of channels at once: for example, it can easily clone the same list in multiple assets, export it to another device, or simply save it locally to always have a copy of the channels ready.

                      "},{"location":"connect-field-devices/asset-implemetation/#csv-upload","title":"CSV upload","text":"

                      The Upload Channels button allows the user to load a local csv file to create the channel list in one shot: the uploading file must implement the same table-structure described in the \"Channels download\" section above, so with each rows representing a channels, and each column one entry of the channel's options.

                      Once the user clicks on the button, will be shown a pop-up in which there are a button to locate the file in the user's filesystem and two checkboxes to allow the uploading phase customization. After clicking on Upload, if the process is succesful, the new channels list will be shown, and the user is just asked to click on the Apply button to save the asset variation.

                      "},{"location":"connect-field-devices/asset-implemetation/#force-import-empty-string-policy","title":"Force import empty string policy","text":"

                      The Force import empty string policy checkbox forces the parsing of all empty values present in the csv as empty strings, for all those channel's options that are typed to String and \"not required\" by the metatype.

                      So, this box must be checked when the user wants that all empty values from the csv are considered as empty strings (\"\") when parsed to driver-specific channel option, defined by the metatype as String type and not required. So, those values that are null in the csv, will be set as \"\" in the channel options. The user must be cautious, because the framework supposes that it's consciously leaving empty values in the csv.

                      Otherwise, if the box is unchecked, all the csv empty values will be considered as null strings and parsed to the default value set in the metatype of the driver.

                      If, for example, the user downloads the channels list following the steps described in the precious section, it could upload the same csv file into an other asset, or another device, to get the exactly same asset configuration.

                      "},{"location":"connect-field-devices/asset-implemetation/#replace-current-channels","title":"Replace current channels","text":"

                      The Replace current channels must be checked when the user wants to replace all the channels currently present in the asset, with the ones that will be created by the csv uploading.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/","title":"ASSET-V1 MQTT Namespace","text":"

                      The ASSET-V1 namespace allows to perform remote operations on the assets defined in an Kura-powered device. The requests and responses are represented as JSON arrays placed in the body of the MQTT payload.

                      The namespace includes the following topics.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#getassets","title":"GET/assets","text":"

                      This topic is used to retrieve metadata describing the assets defined on a specific device and their channel configuration.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format","title":"Request format","text":"

                      The request can contain JSON array containing a list of asset names for which the metadata needs to be returned. The request JSON must have the following structure:

                      [\n  {\n    \"name\": \"asset1\"\n  },\n  {\n    \"name\": \"otherAsset\"\n  }\n]\n

                      The request JSON is an array with all elements of the type object. The array object has the following properties:

                      • name (string, required): the name of the Asset for which the metadata needs to be returned.

                      If the provided array is empty or the request payload is empty, the metadata describing all assets present on the device will be returned.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format","title":"Response format","text":"

                      The response payload contains a JSON array with the following structure:

                      [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"mode\": \"READ\"\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"mode\": \"READ_WRITE\"\n      },\n      {\n        \"name\": \"other_channel\",\n        \"type\": \"STRING\",\n        \"mode\": \"WRITE\"\n      }\n    ]\n  },\n  {\n    \"name\": \"otherAsset\",\n    \"channels\": []\n  },\n  {\n    \"name\": \"nonExistingAsset\",\n    \"error\": \"Asset not found\"\n  }\n]\n

                      All elements of the array are of the type object. The array object has the following properties:

                      • name (string, required): the name of the asset
                      • error (string): this property is present only if the metadata for a not existing asset was explicitly requested, it contains an error message.
                      • channels (array): the list of channels defined on the asset, it can be empty if no channels are defined. This property and the error property are mutually exclusive. This object is an array with all elements of the type object and they have the following properties:
                        • name (string, required): the name of the channel.
                        • mode (string, required): the mode of the channel. The possible values are READ, WRITE or READ_WRITE.
                        • type (string, required): the value type of the channel. The possible values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#execread","title":"EXEC/read","text":"

                      This topic is used to perform a read operation on a specified set of assets and channels.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format_1","title":"Request format","text":"

                      The request can contain a JSON array with the following structure:

                      [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"channel1\"\n      },\n      {\n        \"name\": \"channel2\"\n      },\n      {\n        \"name\": \"otherChannel\"\n      }\n    ]\n  },\n  {\n    \"name\": \"otherAsset\"\n  }\n]\n

                      The request JSON is an array with all elements of the type object. If the list is empty or if the request payload is empty all channels of all assets will be read. The array object has the following properties:

                      • name (string, required): the name of the asset involved in the read operation
                      • channels (array): the list of the names of the channels to be read, if this property is not present or if its value is an empty array, all channels for the specified asset will be read. The object is an array with all elements of the type object. The array object has the following properties:
                      • name (string, required): the name of the channel to be read
                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format_1","title":"Response Format","text":"

                      The response is returned as a JSON array placed in the body of the response:

                      [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"value\": \"432\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"value\": \"true\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"other_channel\",\n        \"error\": \"Read failed\",\n        \"timestamp\": 1234550\n      },\n      {\n        \"name\": \"binary_channel\",\n        \"type\": \"BYTE_ARRAY\",\n        \"value\": \"dGVzdCBzdHJpbmcK\",\n        \"timestamp\": 1234550\n      }\n    ]\n  },\n  {\n    \"name\": \"nonExistingAsset\",\n    \"error\": \"Asset not found\"\n  }\n]\n

                      The response JSON is an array with all elements of the type object. The array object has the following properties:

                      • name (string, required): the name of the asset.
                      • error (string): an error message. This property is present only if a read operation for a not existing asset was explicitly requested.
                      • channels (array): the object is an array with all elements of the type object. The array object has the following properties:
                      • name (string, required): the name of the channel.
                      • timestamp (integer, required): the device timestamp associated with the result in milliseconds since the Unix Epoch.
                      • type (string): the type of the result. This property is present only if the operation succeeded. The possible values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
                      • value (string): the result value of the read request encoded as a String. This property is present only if the operation succeeded. If the channel type is BYTE_ARRAY, the result will be represented using the base64 encoding.
                      • error (string): an error message. This property is present only if the operation failed.
                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#execwrite","title":"EXEC/write","text":"

                      Performs a write operation on a specified set of channels and assets.

                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#request-format_2","title":"Request format","text":"

                      The request must contain a JSON array with the following structure:

                      [\n  {\n    \"name\": \"asset1\",\n    \"channels\": [\n      {\n        \"name\": \"first_channel\",\n        \"type\": \"INTEGER\",\n        \"value\": \"432\",\n      },\n      {\n        \"name\": \"second_channel\",\n        \"type\": \"BOOLEAN\",\n        \"value\": \"true\",\n      },\n      {\n        \"name\": \"binary_channel\",\n        \"type\": \"BYTE_ARRAY\",\n        \"value\": \"dGVzdCBzdHJpbmcK\",\n      }\n    ]\n  }\n]\n

                      The array object has the following properties:

                      • name (string, required): the name of the asset.
                      • channels (array, required): the list of channel names and values to be written. The object is an array with all elements of the type object. The array object has the following properties:
                        • name (string, required): the name of the channel.
                        • type (string, required): the type of the value to be written. The allowed values are BOOLEAN, BYTE_ARRAY, DOUBLE, INTEGER, LONG, FLOAT, STRING.
                        • value (string, required): the value to be written encoded as a String. If the channel type is BYTE_ARRAY, the base64 encoding must be used.
                      "},{"location":"connect-field-devices/asset-v1-mqtt-namespace/#response-format_2","title":"Response format","text":"

                      The response uses the same format as the EXEC/read request, in case of success the type and value properties in the response will report the same values specified in the request.

                      "},{"location":"connect-field-devices/driver-and-assets/","title":"Drivers, Assets and Channels","text":"

                      Eclipse Kura introduces a model based on the concepts of Drivers and Assets to simplify the communication with the field devices attached to a gateway.

                      A Driver encapsulates the communication protocol and its configuration parameters, dealing with the low-level characteristics of the field protocol. It opens, closes and performs the communication with the end field device. It also exposes field protocol specific information that can be used by upper levels of abstraction to simplify the interaction with the end devices.

                      An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

                      "},{"location":"connect-field-devices/driver-and-assets/#channel-example","title":"Channel Example","text":"

                      To further describe the concept of Channel and Asset, the following table shows a set of PLC register addresses as provided in a typical PLC documentation.

                      Name Entity Address LED1 COILS 2049 LED2 COILS 2050 LED3 COILS 2051 LED4 RED COILS 2052 LED4 GREEN COILS 2053 LED4 BLUE COILS 2054 Counter 3 INPUT REGISTERS 515 Quad Counter INPUT REGISTERS 520 Toggle 4 DISCRETE INPUTS 2052 Toggle 5 DISCRETE INPUTS 2053 Toggle 6 DISCRETE INPUTS 2054 Reset Counter 3 COILS 3075 Reset Quad Counter COILS 3084

                      The corresponding Channels definition in the Asset is as follows:

                      As shown in the previous image, the Channel definition in an Asset results easily mappable to what available in a generic PLC documentation.

                      Once defined the Channels in an Asset, a simple Java application that leverages the Asset API can easily communicate with the Field device by simply referring to the specific Channel of interest.

                      "},{"location":"connect-field-devices/driver-and-assets/#drivers-and-assets-in-kura-administrative-ui","title":"Drivers and Assets in Kura Administrative UI","text":"

                      Kura provides a specific section of the UI to allow users to manage the different instances of Drivers and Assets. Using the Kura Web UI the user can instantiate and manage Drivers

                      but also can manage Assets instances based on existing drivers.

                      The user interface allows also to perform specific reads on the configured Assets' channels clicking on the Data tab for the selected Asset.

                      "},{"location":"connect-field-devices/driver-implemetation/","title":"Driver implementation","text":"

                      A Driver encapsulates the communication protocol and its configuration parameters.

                      The Driver API abstracts the specificities of the end Fieldbus protocols providing a clean and easy to use set of calls that can be used to develop end-applications.

                      Using the Driver APIs, an application can simply use the connect and disconnect methods to open or close the connection with the Field device. Furthermore, the read and write methods allow exchanging data with the Field device.

                      A Driver instance can be associated with an Asset to abstract even more the low-level specificities and allow an easy and portable development of the Java applications that need to interact with sensors, actuators, and PLCs.

                      The Asset will use the Driver's protocol-specific channel descriptor to compose the Asset Channel description.

                      "},{"location":"connect-field-devices/driver-implemetation/#driver-configuration","title":"Driver Configuration","text":"

                      Generally, a Driver instance is a configurable component which parameters can be updated in the Drivers and Assets section of the Kura Administrative User Interface.

                      "},{"location":"connect-field-devices/driver-implemetation/#supported-field-protocols-and-availability","title":"Supported Field Protocols and Availability","text":"

                      Drivers will be provided as add-ons available in the Eclipse IoT Marketplace. Please see here for a complete list.

                      "},{"location":"connect-field-devices/driver-implemetation/#driver-specific-optimizations","title":"Driver-Specific Optimizations","text":"

                      The Driver API provides a simple method to read a list of Channel Records:

                      public void read(List<ChannelRecord> records) throws ConnectionException;\n

                      Typically, since the records to read do not change until the Asset configuration is changed by the user, a Driver can perform some optimisations to efficiently read the requested records at once. For example, a Modbus driver can read a range of holding registers using a single request.

                      Since these operations are costly, the Kura API adds methods to ask the driver to prepare reading a given list of records and execute the prepared read:

                      public PreparedRead prepareRead(List<ChannelRecord> records);\n

                      Invocation of the preparedRead method will result in a PreparedRead instance returned.

                      On a PreparedRead, the execute method will perform the optimized read request.

                      "},{"location":"connect-field-devices/eddystone-driver/","title":"Eddystone\u2122 Driver","text":"

                      Eclipse Kura offers support for Eddystone\u2122 protocol via a specific driver. It can be used into the Wires framework, the Asset model or directly using the Driver itself.

                      "},{"location":"connect-field-devices/eddystone-driver/#features","title":"Features","text":"

                      The Eddystone\u2122 driver is designed to listen for incoming beacon packets and to recognise the specific protocol. Of course it's not possible to write data to the beacons, since this is outside the protocol specification. The frame format to be filtered can be chosen from the channel definition. For more information about Eddystone\u2122 frame format, see here.

                      "},{"location":"connect-field-devices/eddystone-driver/#installation","title":"Installation","text":"

                      As the others Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/eddystone-driver/#instance-creation","title":"Instance creation","text":"

                      A new Eddystone Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.eddsytone factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

                      "},{"location":"connect-field-devices/eddystone-driver/#channel-configuration","title":"Channel configuration","text":"

                      The Eddystone Driver channel can be configured with the following parameters:

                      • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.
                      • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
                      • eddystone.type: the type of the frame. Currently only UID and URL typed are supported.
                      "},{"location":"connect-field-devices/field-protocols/","title":"Field Protocols","text":"

                      Eclipse Kura provides support for field protocol implementations as add-ons deployable directly from the Eclipse Marketplace for IoT site. Moreover, several devices are supported using the Kura Driver model.

                      Currently, the following field protocols and devices are supported and downloadable from the Eclipse Marketplace in form of Kura Drivers:

                      Protocol/Device Kura 3.x Kura 4.x Kura 5.x OPC-UA link link link S7 link link link iBeacon N.A. link link Eddystone N.A. link link TiSensorTag link link link GPIO link link link SenseHat link link link"},{"location":"connect-field-devices/gpio-driver/","title":"GPIO Driver","text":"

                      The GPIO Driver manages the General Purpose IOs on a gateway using the Driver model. Based on the GPIO Service, the driver can be used in the Wires framework, the Asset model or directly using the Driver itself.

                      "},{"location":"connect-field-devices/gpio-driver/#features","title":"Features","text":"

                      The GPIO Driver includes the following features:

                      • support for digital input and output
                      • support for unsolicited inputs
                      • the trigger event can be configured directly through the Driver
                      "},{"location":"connect-field-devices/gpio-driver/#installation","title":"Installation","text":"

                      As the other Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/gpio-driver/#instance-creation","title":"Instance creation","text":"

                      A new GPIO Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.gpio factory must be selected and a unique name must be provided for the new instance. Once instantiated, the GPIO Driver is ready to use and no configuration is needed.

                      "},{"location":"connect-field-devices/gpio-driver/#channel-configuration","title":"Channel configuration","text":"

                      The GPIO Driver channel can be configured with the following parameters:

                      • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type. Conversely, in write operations the Driver will accept value of this kind.
                      • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
                      • resource.name: the name of the GPIO resource as reported by the GPIO Service. The #select resource selection has no effect on the channel.
                      • resource.direction: the direction of the GPIO. Possible values are INPUTand OUTPUT. The #select direction selection has no effect on the channel.
                      • resource.trigger: the type of event that triggers the listener, if selected. Possible values are:

                      • NONE: no event will trigger the listener.

                      • RISING_EDGE, FALLING_EDGE, BOTH_EDGES: the listeners will be triggered respectively by a low-high transition, a high-low transition or both.
                      • HIGH_LEVEL,LOW_LEVEL, BOTH_LEVELS: the listeners will be triggered respectively by the detection of a high, low or both levels. Please note that these options aren't supported by all the devices.
                      "},{"location":"connect-field-devices/gpio-driver/#drive-a-led-using-the-gpio-driver","title":"Drive a LED using the GPIO Driver","text":"

                      In this section a simple example on the GPIO Driver using a RaspberryPi will be presented. Before configuring the Driver, arrange a setup as shown in the following picture, using a breadboard, a led, a 120-ohm resistor and some wires. Connect the yellow wire to a ground pin on the RasperryPi connector (i.e. pin 6) and the red one to pin 40 (a.k.a. gpio21).

                      From the Drivers and Assets tab, create a new GPIO Driver, call it GPIODriver and add an Asset as shown in the following picture.

                      The asset is configured to manage a gpio, called LED, as an output and drives it writing a boolean value. The LED channel is attached to the gpio21 on the RaspberryPi. In the Data tab, fill the Value form with true and press Apply: the green led will switch on. Writing a false will switch off the led.

                      "},{"location":"connect-field-devices/ibeacon-driver/","title":"iBeacon\u2122 Driver","text":"

                      Eclipse Kura provides a driver specifically developed to manage iBeacon\u2122 protocol. They can be used into the Wires framework, the Asset model or directly using the Driver itself.

                      "},{"location":"connect-field-devices/ibeacon-driver/#features","title":"Features","text":"

                      The iBeacon\u2122 driver is designed to listen for incoming beacon packets and to recognise the specific protocol. Of course it's not possible to write data to the beacons, since this is outside the protocol specification.

                      "},{"location":"connect-field-devices/ibeacon-driver/#installation","title":"Installation","text":"

                      As the others Drivers supported by Eclipse Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/ibeacon-driver/#instance-creation","title":"Instance creation","text":"

                      A new iBeacon instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ibeacon factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

                      "},{"location":"connect-field-devices/ibeacon-driver/#channel-configuration","title":"Channel configuration","text":"

                      The iBeacon Driver channel can be configured with the following parameters:

                      • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.
                      • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
                      "},{"location":"connect-field-devices/opcua-driver/","title":"OPC UA Driver","text":"

                      This Driver implements the client side of the OPC UA protocol using the Driver model. The Driver can be used to interact as a client with OPC UA servers using different abstractions, such as the Wires framework, the Asset model or by directly using the Driver itself.

                      The Driver is distributed as a deployment package on Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/opcua-driver/#features","title":"Features","text":"

                      The OPC UA Driver features include:

                      • Support for the OPC UA protocol over TCP.
                      • Support for reading and writing OPC UA variable nodes by node ID.
                      "},{"location":"connect-field-devices/opcua-driver/#instance-creation","title":"Instance creation","text":"

                      A new OPC UA instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.opcua factory must be selected and a unique name must be provided for the new instance.

                      "},{"location":"connect-field-devices/opcua-driver/#channel-configuration","title":"Channel configuration","text":"

                      The OPC UA Driver channel configuration is composed of the following parameters:

                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value type: the Java type of the channel value.
                      • node.id: The node id of the variable node to be used, the format of the node id depends on the value of the node.id.type property.
                      • node.namespace.index: The namespace index of the variable node to be used.
                      • opcua.type: The OPC-UA built-in type of the attribute to be read/written. If set to DEFINED_BY_JAVA_TYPE (default), the driver will attempt to determine the OPC-UA type basing on the value type parameter value. If the read/write operation fails, it may be necessary to use one of the other values of this configuration parameter to explicitly select the type.

                        This parameter also lists the OPC-UA types currently supported by the driver.

                        Not all value type and opcua.type combinations are valid, the allowed ones are the following:

                        opcua.type Allowed value.types Recommended value.type BOOLEAN BOOLEAN BOOLEAN SBYTE INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT16 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT32 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER INT64 INTEGER, LONG, FLOAT, DOUBLE, STRING LONG BYTE INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER UINT16 INTEGER, LONG, FLOAT, DOUBLE, STRING INTEGER UINT32 INTEGER, LONG, FLOAT, DOUBLE, STRING LONG UINT64 INTEGER, LONG, FLOAT, DOUBLE, STRING STRING FLOAT FLOAT, STRING FLOAT DOUBLE DOUBLE, STRING DOUBLE STRING STRING STRING BYTE_STRING BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY BYTE_ARRAY SBYTE_ARRAY BYTE_ARRAY BYTE_ARRAY

                        Using a non allowed value.type will result in read/write operation failures. It should be noted that there is not a one to one match between the opcua.type and Java value.type. It is recommended to compare the allowed ranges for numeric types specified in OPC-UA Reference and Java reference for selecting the best match.

                      • node.id.type: The type of the node id (see the Node Id types section)

                      • attribute: The attribute of the referenced variable node.

                      If the listen flag is enabled for an OPC-UA channel, the driver will request the server to send notifications if it detects changes in the referenced attribute value.

                      In order to enable this, the driver will create a global subscription (one per Driver instance), and a monitored item for each channel. See [1] for more details. The Subscription publish interval global configuration parameter can be used to tune the subscription publishing interval.

                      • listen.sampling.interval: The sampling interval for the monitored item. See the Sampling interval section of [1] for more details.
                      • listen.queue.size: The queue size for the monitored item. See the Queue parameters section of [1] for more details.
                      • listen.discard.oldest: The value of the discardOldest flag for the monitored item. See the Queue parameters section of [1] for more details.

                      The listen.subscribe.to.children parameter can be used to enable the Subtree Subscription feature.

                      [1] MonitoredItem model

                      "},{"location":"connect-field-devices/opcua-driver/#node-id-types","title":"Node ID types","text":"

                      The Driver supports the following node id types:

                      Node ID Type Format of node.id NUMERIC node.id must be parseable into an integer STRING node.id can be any string OPAQUE Opaque node ids are represented by raw byte arrays. In this case node.id must be the base64 encoding of the node id. GUID node.id must be a string conforming to the format described in the documentation of the java.util.UUID.toString() method."},{"location":"connect-field-devices/opcua-driver/#certificate-setup","title":"Certificate setup","text":"

                      In order to use settings for Security Policy different than None, the OPCUA driver must be configured to trust the server certificate and a new client certificate - private key pair must be generated for the driver. These items can be placed in a Java keystore that can be referenced from driver configuration. The keystore does not exist by default and without it connections that use Security Policy different than None will fail.

                      The following steps can be used to generate the keystore:

                      1. Download the certificate used by the server, usually this can be done from server management UI.
                      2. Copy the downloaded certificate to the gateway using SSH.
                      3. Copy the following example script to the device using SSH, it can be used to import the server certificate and generate the client key pair. It can be modified if needed:

                        #!/bin/bash\n\n# the alias for the imported server certificate\nSERVER_ALIAS=\"server-cert\"\n\n# the file name of the generated keystore\nKEYSTORE_FILE_NAME=\"opcua-keystore.ks\"\n# the password of the generated keystore and private keys, it is recommended to change it\nKEYSTORE_PASSWORD=\"changeit\"\n\n# server certificate to be imported is expected as first argument\nSERVER_CERTIFICATE_FILE=\"$1\"\n\n# import existing certificate\nkeytool -import \\\n        -alias \"${SERVER_ALIAS}\" \\\n        -file \"${SERVER_CERTIFICATE_FILE}\" \\\n        -keystore \"${KEYSTORE_FILE_NAME}\" \\\n        -noprompt \\\n        -storepass \"${KEYSTORE_PASSWORD}\"\n\n# alias for client certificate\nCLIENT_ALIAS=\"client-cert\"\n# client certificate distinguished name, it is recommended to change it \nCLIENT_DN=\"CN=MyCn, OU=MyOu, O=MyOrganization, L=Amaro, S=UD, C=IT\"\n# the application id, must match the corresponding parameter in driver configuration\nAPPLICATION_ID=\"urn:kura:opcua:client\"\n\n# generate the client private key and certificate\nkeytool -genkey \\\n        -alias \"${CLIENT_ALIAS}\" \\\n        -keyalg RSA \\\n        -keysize 4096 \\\n        -keystore \"${KEYSTORE_FILE_NAME}\" \\\n        -dname \"${CLIENT_DN}\" \\\n        -ext ku=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment \\\n        -ext eku=clientAuth \\\n        -ext \"san=uri:${APPLICATION_ID}\" \\\n        -validity 1000 \\\n        -noprompt \\\n        -storepass ${KEYSTORE_PASSWORD} \\\n        -keypass ${KEYSTORE_PASSWORD}\n
                      4. Update the following parameters in driver configuration:

                      5. Keystore Path : Set the absolute path of the opcua-keystore.ks file created at step 3.
                      6. **Security Policy ** -> Set the desired option
                      7. Client Certificate Alias -> Set the value of the CLIENT_ALIAS script variable (the default is client-cert)
                      8. Enable Server Authentication -> true
                      9. Keystore type -> JKS
                      10. Keystore Password -> Set the value of the KEYSTORE_PASSWORD script variable (the default value is changeit)
                      11. Application URI -> Set the value of the APPLICATION_ID script variable (default value should be already ok).

                      12. Configurare the server to trust the client certificate generated at step 3. The steps required to do this vary depending on the server. Usually the following steps are needed:

                      13. Make a connection attempt using OPC-UA driver, this will likely fail because the server does not trust client certificate.
                      14. After the failed connection attempt, the server should display the certificate used by the driver in the administration UI. The server UI should allow to set it as trusted.
                      15. Make another connection attempt once the certificate has been set to trusted, this connection attempt should succeed.
                      "},{"location":"connect-field-devices/opcua-driver/#substree-subscription","title":"Substree Subscription","text":"

                      The driver can be configured to recursively visit the children of a folder node and create a Monitored Item for the value of each discovered variable node with a single channel in Asset configuration.

                      Warning: This feature should be used with care since it can cause high load on both the gateway and the server if the referenced folder contains a large number of nodes and/or the notification rate is high.

                      "},{"location":"connect-field-devices/opcua-driver/#channel-configuration_1","title":"Channel configuration","text":"

                      In order to configure the driver to perform the discovery operation, a single channel can be defined with the following configuration:

                      • type: READ
                      • value.type: STRING (see below)
                      • listen: true
                      • node.id: the node id of the root of the subtree to visit
                      • node.namespace.index: the namespace index of the root of the subtree to visit
                      • node.id.type the node id type of the root of the subtree to visit
                      • listen.subscribe.to.children (NEW): true

                      The rest of the configuration parameters can be specified in the same way as for the single node subscription use case.

                      The listen.sampling.interval, listen.queue.size and listen.discard.oldest parameters of the root will be used for all subscriptions on the subtree.

                      "},{"location":"connect-field-devices/opcua-driver/#discovery-procedure","title":"Discovery procedure","text":"

                      The driver will consider as folders to visit all nodes that whose type definition is FolderType, or more precisely all nodes with the following reference:

                      HasTypeDefinition: * namespace index: 0 * node id: 61 (numeric) * URN: http://opcfoundation.org/UA/

                      The driver will subscribe to all the variable nodes found.

                      "},{"location":"connect-field-devices/opcua-driver/#event-reporting","title":"Event reporting","text":"

                      If the Driver is used by a Wire Asset, it will emit on the wire a single message per received event.

                      All emitted events will contain a single property. Depending on the value of the Subtree subscription events channel name format global configuration parameter, the name of this property is the node id or the browsed path of the source OPCUA node relative to the root folder defined in the channel configuration.

                      "},{"location":"connect-field-devices/opcua-driver/#type-conversion","title":"Type conversion","text":"

                      The current version of the driver tries to convert the values received for all the events on a subtree to the type defined in the value.type configuration parameter.

                      Since the value types of the discovered nodes are heterogeneous, the conversion might fail if the types are not compatible (e.g. if value.type is set to INTEGER and the received value is a string).

                      Setting value.type to STRING should allow to perform safe conversions for most data types.

                      "},{"location":"connect-field-devices/s7-driver/","title":"S7comm Driver","text":"

                      This Driver implements the s7comm protocol and can be used to interact with Siemens S7 PLCs using different abstractions, such as the Wires framework, the Asset model or by directly using the Driver itself.

                      The Driver is distributed as a deployment package on the Eclipse Marketplace for Kura 3.x and 4.x/5.x. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/s7-driver/#features","title":"Features","text":"

                      The S7comm Plc Driver features include:

                      • Support for the s7comm protocol over TCP.
                      • Support for reading and writing data from the Data Blocks (DB) memory areas.
                      • The driver is capable of automatically aggregating reads/writes for contiguous data in larger bulk requests in order to reduce IO times.
                      "},{"location":"connect-field-devices/s7-driver/#instance-creation","title":"Instance creation","text":"

                      A new S7comm driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.s7plc factory must be selected and a unique name must be provided for the new instance.

                      "},{"location":"connect-field-devices/s7-driver/#channel-configuration","title":"Channel configuration","text":"

                      The S7 Driver channel configuration is composed of the following parameters:

                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value type: the Java type of the channel value.
                      • s7.data.type: the S7 data type involved in channel operation.
                      • data.block.no: the data block number involved in channel operation.
                      • offset: the start address of the data.
                      • byte.count: the size in bytes of the transferred data. This parameter is required only if the value type parameter is set to STRING or BYTE_ARRAY. In the other cases, this parameter is ignored and the data size is automatically derived from the s7.data.type.
                      • bit.index: the index of the bit involved in channel operation inside its containing byte, index 0 is the least significant bit. This parameter is required only if the value type parameter is set to BOOLEAN and s7.data.type is set to BOOL. In the other cases, this parameter is ignored.
                      "},{"location":"connect-field-devices/s7-driver/#data-types","title":"Data Types","text":"

                      When performing operations that deal with numeric data, two data types are involved:

                      1. The Java primitive type that is used in the ChannelRecords exchanged between the driver and Java applications. (the Java type of the value received/supplied by external applications from/to the Driver in case of a read/write operation). This value type is specified by the value type configuration property.

                      2. The S7 type of the data on the PLC. This value type is specified by the s7.data.type configuration property. The following S7 data types are supported:

                        S7 Data Type Size Sign INT 16 bits signed DINT 32 bits signed WORD 16 bits unsigned DWORD 32 bits unsigned REAL 32 bits signed BOOL 1 bit n.d. BYTE 1 byte unsigned CHAR 1 byte (only supported as char arrays using the String Java data type) n.d.

                      The Driver automatically adapts the data type used by external applications and the S7 device depending on the value of the two configuration properties mentioned above.

                      The adaptation process involves the following steps:

                      • Each device data type is internally converted by the driver from/to a Java type large enough to represent the value of the device data without losing precision. The type mappings are the following:

                        S7 Data Type Java Type INT int DINT int WORD int DWORD long REAL float BOOL boolean BYTE int
                      • If the value type of the channel does not match the Java type specified in mapping above, a conversion is performed by the Driver to convert it to/from the matching type, choosing appropriately between the Number.toInt(), Number.toLong(), Number.toFloat() or Number.toDouble() methods. Precision losses may occur if the Java type used by the external application is not suitable to represent all possible values of the device data type.

                      "},{"location":"connect-field-devices/s7-driver/#array-data","title":"Array Data","text":"

                      The driver supports transferring data as raw byte arrays or ASCII strings:

                      • Byte arrays: For transferring data as byte arrays the channel value type property must be set to BYTE_ARRAY, the data.type configuration property must be set to BYTE and the byte.count property must be set to the data length in bytes.
                      • Strings: For transferring data as ASCII strings the channel value type property must be set to STRING, the data.type configuration property must be set to CHAR and the array.data.length property must be set to the data length in bytes.
                      "},{"location":"connect-field-devices/s7-driver/#writing-single-bits","title":"Writing Single Bits","text":"

                      The Driver supports setting the value of single bits without overwriting the other bits of the same byte. This operation can be performed defining a channel having BOOLEAN as value type, BOOL as s7.data.type and the proper index set to the bit.index property.

                      The Driver will fetch the byte containing the bit to be written, update its contents and then write back the obtained value. If multiple bits on the same byte need to be modified, the driver will perform only one read and write for that byte. If bits that need to be changed are located in contiguous bytes, the driver will perform only one bulk read and one bulk write transferring all the required data in a single request.

                      "},{"location":"connect-field-devices/sensehat-driver/","title":"RaspberryPi SenseHat driver","text":"

                      The SenseHat driver allows to interact to a RaspberryPi SenseHat device using Kura Driver, Asset and Wires frameworks. The driver allows access to the following resources:

                      • Sensors
                      • Joystick
                      • LED Matrix

                      The driver-specific channel configuration contains a single parameter, resource, which allows to select the specific device resource that the channel is addressing (a sensor, a joystick event, etc).

                      Note about running on OpenJDK

                      If some exceptions reporting Locked by other application are visible in the log and the driver fails to start, try switching to the Oracle JVM by installing the oracle-java8-jdk package. For more information on the problem, please see this GitHub issue.

                      "},{"location":"connect-field-devices/sensehat-driver/#installation","title":"Installation","text":"

                      As the others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

                      In order to use the driver, the Sensehat Support Library Bundle for Eclipse Kura needs to be installed as a prerequisite dependency. It is available from Eclipse Marketplace here.

                      "},{"location":"connect-field-devices/sensehat-driver/#sensors","title":"Sensors","text":"

                      The following values of the resource parameters refer to device sensors:

                      Resource Unit Description ACCELERATION_X, ACCELERATION_Y, ACCELERATION_Z G The proper acceleration for each axis GYROSCOPE_X, GYROSCOPE_Y, GYROSCOPE_Z rad/S The angular acceleration for each axis MAGNETOMETER_X, MAGNETOMETER_Y, MAGNETOMETERE_Z uT The magnetometer value for each axis HUMIDITY %rH The relative humidity PRESSURE mbar The pressure value TEMPERATURE_FROM_HUMIDITY \u00b0C The temperature obtained from the humidity sensor TEMPERATURE_FROM_PRESSURE \u00b0C The temperature obtained from the pressure sensor

                      The channels referencing sensor resources can only be used for reads and only in polling mode. The driver will attempt to convert the value obtained from the sensor into the data type selected using the value.type parameter

                      Example sensors Asset configuration:

                      "},{"location":"connect-field-devices/sensehat-driver/#joystick","title":"Joystick","text":"

                      The SenseHat joystick provides four buttons:

                      • UP
                      • DOWN
                      • LEFT
                      • RIGHT
                      • ENTER

                      For each button, the driver allows to listen to the following events:

                      • PRESS: Fired once when a button is pressed
                      • RELEASE: Fired once when a button is released
                      • HOLD: Fired periodically every few seconds if the button is kept pressed

                      The values of the resource parameter related to joystick have the following structure:

                      JOYSTICK_{BUTTON}_{EVENT}

                      Channels referencing joystick events must use LONG as value.type. The channel value supplied by the driver is the Java timestamp in milliseconds of the Joystick event, a value of 0 signifies that no events have been observed yet.

                      Joystick related channels can be only used for reading and both in polling and event-driven mode.

                      Example joystick Asset configuration:

                      "},{"location":"connect-field-devices/sensehat-driver/#led-matrix","title":"LED Matrix","text":"

                      The driver allows accessing the SenseHat LED matrix in two ways:

                      • Using a monochrome framebuffer
                      • Using a RGB565 framebuffer
                      "},{"location":"connect-field-devices/sensehat-driver/#coordinates","title":"Coordinates","text":"

                      The coordinate system used by the driver defines the x coordinate as increasing along the direction identified by the joystick RIGHT button and the y coordinate increasing along the direction identified by the DOWN joystick button.

                      "},{"location":"connect-field-devices/sensehat-driver/#monochrome-framebuffer","title":"Monochrome framebuffer","text":"

                      In monochrome mode, only two colors will be used: the front color and the back color.

                      "},{"location":"connect-field-devices/sensehat-driver/#front-and-back-colors","title":"Front and back colors","text":"

                      Front and back colors can be configured using channels having the following values for the resource parameter:

                      • LED_MATRIX_FRONT_COLOR_R
                      • LED_MATRIX_FRONT_COLOR_G
                      • LED_MATRIX_FRONT_COLOR_B
                      • LED_MATRIX_BACK_COLOR_R
                      • LED_MATRIX_BACK_COLOR_G
                      • LED_MATRIX_BACK_COLOR_B

                      These channel types allow to set the rgb components of the front and back color and can only be used in write mode. The supplied value must be a floating point number between 0 (led turned off) and 1 (full brightness). Note: Front and back colors are internally represented using the RGB565 format. The available colors are only the ones that can be represented using RGB565 and are supported by the device. Front and back color are retained for successive draw operations, the initial value for both colors is black (led turned off).

                      "},{"location":"connect-field-devices/sensehat-driver/#drawing","title":"Drawing","text":"

                      The following resources can be used for modifying framebuffer contents:

                      • LED_MATRIX_FB_MONOCHROME:

                        A channel of this type allows to set the framebuffer contents in monochrome mode. It can only be used in write mode and its value.type must be BYTE_ARRAY.

                        The supplied value must be a byte array of length 64 that represent the state of the 8x8 led matrix. The offset in the array of a pixel having coordinates (x, y) is y*8 + x. The back/front color will be used for a pixel if the corresponding byte in the array is zero/non-zero.

                      • LED_MATRIX_CHARS:

                        A channel of this type allows showing a text message using the LED matrix. It can only be used for writing and its value.type must be STRING. The characters of the message will be rendered using the front color and the background using the back color.

                      "},{"location":"connect-field-devices/sensehat-driver/#rgb565-framebuffer","title":"RGB565 framebuffer","text":"

                      The following resource allows writing the framebuffer using the RGB565 format:

                      • LED_MATRIX_FB_RGB565:

                        A channel of this type allows to set the framebuffer contents in RGB565 mode. It can only be used in write mode and its value.type must be BYTE_ARRAY.

                        The supplied value must be a byte array of length 128 that represents the state of the 8x8 led matrix. Each pixel is represented by two consecutive bytes in the following way:

                        |    MSB    |    LSB    |\n|  RRRRRGGG |  GGGBBBBB |\n|  15 ... 8 |  7 ... 0  |\n

                        The LSB must be stored first in the array, the offset of the LSB and MSB for a pixel at coordinates (x, y) is the following:

                        • LSB: 2*(y*8 + x)
                        • MSB: 2*(y*8 + x) + 1
                      "},{"location":"connect-field-devices/sensehat-driver/#mode-independent-resources","title":"Mode independent resources","text":"
                      • LED_MATRIX_CLEAR:

                        Writing anything to a LED_MATRIX_CLEAR channel will clear the framebuffer turning off all leds.

                      • LED_MATRIX_ROTATION:

                        Allows to rotate the framebuffer of 0, 90, 180, and 170 degrees clockwise. Rotation setting will be retained for successive draw operations. The default value is 0. Writes to a LED_MATRIX_ROTATION channel can be performed using any numeric type as value.type.

                      Example framebuffer Asset configuration:

                      "},{"location":"connect-field-devices/sensehat-driver/#examples","title":"Examples","text":"

                      This section contains some examples describing how to use the driver using Kura Wires and the Wires Script filter.

                      "},{"location":"connect-field-devices/sensehat-driver/#moving-a-pixel-using-the-joystick","title":"Moving a pixel using the Joystick","text":"
                      1. Open the Wires section of the Kura Web UI.
                      2. Create an Asset that emits joystick events like the one described in the Joystick section. Only left_release, right_release, up_release and down_release channels will be used. Make sure to enable the listen flag for the channels.
                      3. Create the Asset described in the LED Matrix section.
                      4. Create a Script Filter and paste the code below in the script field.
                      5. Connect the Joystick Asset to the input port of the Script Filter, and the output port of the Script filter to the framebuffer Asset.
                      6. Open the Drivers and Assets section of the Kura Web UI, select the framebuffer Asset, click on the Data tab and select front and back colors. For example for using green as front color and red as back color, write 1 as Value for the front_g and back_r channels.
                      7. You should now be able to move the green pixel with the joystick.
                      var FB_SIZE = 8\n\nif (typeof(state) === 'undefined') {\n  // framebuffer as byte array (0 -> back color, non zero -> front color)\n  var fb = newByteArray(FB_SIZE*FB_SIZE|0)\n  // record to be emitted for updating the fb\n  var outRecord = newWireRecord()\n  // property in emitted record containing fb data\n  // change the name if needed\n  // should match the name of a channel configured as LED_MATRIX_FB_MONOCHROME\n  outRecord['fb_mono'] = newByteArrayValue(fb)\n\n  state = {\n    fb: fb,\n    outRecord: outRecord,\n    x: 0, // current pixel position\n    y: 0,\n    dx: 0, // deltas to be added to pixel position\n    dy: 0,\n  }\n}\n\nif (typeof(actions) === 'undefined') {\n  // associations between input property names\n  // and position update actions,\n  // input can be supplied using an Asset\n  // with joystick event channels\n  actions = {\n    'up_release' : function () {\n      // decrease y coordinate\n      state.dy = -1\n    },\n    'down_release' : function () {\n      // increase y coordinate\n      state.dy = 1\n    },\n    'left_release' : function () {\n      // decrease x coordinate\n      state.dx = -1\n    },\n    'right_release' : function () {\n      // increase x coordinate\n      state.dx = 1\n    }\n  }\n}\n\nif (input.records.length) {\n  var input = input.records[0]\n  var update = false\n\n  for (var prop in input) {\n    var action = actions[prop]\n    // if there is an action associated with the received property, execute it\n    if (action) {\n      action()\n      // request framebuffer update\n      update = true\n    }\n  }\n\n  if (update) {\n    // framebuffer update requested\n    // clear old pixel\n    state.fb[state.y*FB_SIZE + state.x] = 0\n    // compute new pixel position\n    state.x = (state.x + state.dx + FB_SIZE) % FB_SIZE\n    state.y = (state.y + state.dy + FB_SIZE) % FB_SIZE\n    // set new pixel\n    state.fb[state.y*FB_SIZE + state.x] = 1\n    // clear deltas\n    state.dx=0\n    state.dy=0\n    // emit record\n    output.add(state.outRecord)\n  }\n}\n
                      "},{"location":"connect-field-devices/sensehat-driver/#using-rgb565-framebuffer","title":"Using RGB565 framebuffer","text":"
                      1. Open the Wires section of the Kura Web UI.
                      2. Create the Asset described in the LED Matrix section.
                      3. Create a Script Filter and paste the code below in the script field.
                      4. Create a Timer that ticks every 16 milliseconds.
                      5. Connect the Timer to the input port of the Script Filter, and the output port of the Script filter to the framebuffer Asset.
                      6. The framebuffer should now display an animation, the animation can be changed by modifying the wave parameters and setting script.context.drop to false.
                      var FB_SIZE = 8\nvar BYTES_PER_PIXEL = 2\n\nif (typeof(state) === 'undefined') {\n  // framebuffer as RGB565 byte array\n  var fb = newByteArray(FB_SIZE * FB_SIZE * BYTES_PER_PIXEL | 0)\n  // record to be emitted for updating the fb\n  var outRecord = newWireRecord()\n  // property in emitted record containing fb data\n  // change the name if needed\n  // must match the name of a channel configured as LED_MATRIX_FB_565\n  outRecord['fb_rgb565'] = newByteArrayValue(fb)\n\n  // framebuffer array and output record\n  state = {\n    fb: fb,\n    outRecord: outRecord,\n  }\n\n  RMASK = ((1 << 5) - 1)\n  GMASK =  ((1 << 6) - 1)\n  BMASK = RMASK\n\n  // converts the r, g, b values provided as a floating point\n  // number between 0 and 1 to RGB565 and stores the result\n  // inside the output array\n  function putPixel(off, r, g, b) {\n    var _r = Math.floor(r * RMASK) & 0xff\n    var _g = Math.floor(g * GMASK) & 0xff\n    var _b = Math.floor(b * BMASK) & 0xff\n\n    var b0 = (_r << 3 | _g >> 3)\n    var b1 = (_g << 5 | _b)\n\n    state.fb[off + 1] = b0\n    state.fb[off] = b1\n  }\n\n  // parameters for 3 sin waves, one per color component\n  RED_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 0\n  }\n\n  GREEN_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 1\n  }\n\n  BLUE_WAVE_PARAMS = {\n    a: 5,\n    b: 10,\n    c: 10,\n    d: 2\n  }\n\n  function wave(x, y, t, params) {\n    return Math.abs(Math.sin(2*Math.PI*(t + x/params.b + y/params.c + params.d)/params.a))\n  }\n}\n\nvar t = new Date().getTime() / 1000\n\nvar off = 0\nfor (var y = 0; y < FB_SIZE; y++)\n  for (var x = 0; x < FB_SIZE; x++) {\n    var r = wave(x, y, t, RED_WAVE_PARAMS)\n    var g = wave(x, y, t, GREEN_WAVE_PARAMS)\n    var b = wave(x, y, t, BLUE_WAVE_PARAMS)\n    putPixel(off, r, g, b)\n    off += 2\n  }\n\noutput.add(state.outRecord)\n
                      "},{"location":"connect-field-devices/sensortag-driver/","title":"TI SensorTag Driver","text":"

                      Eclipse Kura provides a specific driver that can be used to interact with Texas Instruments SensorTag devices. It can be used in the Wires framework, the Asset model or directly using the Driver itself.

                      Warning

                      The SensorTag driver can be used only with TI SensorTags with firmware version >1.20. If your device has an older firmware, please update it.

                      "},{"location":"connect-field-devices/sensortag-driver/#features","title":"Features","text":"

                      The SensorTag Driver can be used to get the values from all the sensor installed on the tag (both in polling mode and notification):

                      • ambient and target temperature
                      • humidity
                      • pressure
                      • three-axis acceleration
                      • three-axis magnetic field
                      • three-axis orientation
                      • light
                      • push buttons

                      Moreover, the following resources can be written by the driver: - read and green leds - buzzer

                      When a notification is enabled for a specific channel (sensor), the notification period can be set.

                      "},{"location":"connect-field-devices/sensortag-driver/#installation","title":"Installation","text":"

                      As the other Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here and here. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/sensortag-driver/#instance-creation","title":"Instance creation","text":"

                      A new TiSensorTag Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ble.sensortag factory must be selected and a unique name must be provided for the new instance. Once instantiated, the SensorTag Driver has to be configured setting the Bluetooth interface name (i.e. hci0) that will be used to connect to the device.

                      "},{"location":"connect-field-devices/sensortag-driver/#channel-configuration","title":"Channel configuration","text":"

                      The SensorTag Driver channel can be configured with the following parameters:

                      • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).
                      • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type. Conversely, in write operations the Driver will accept value of this kind.
                      • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
                      • sensortag.address: the address of the specific SensorTag (i.e. aa:bb:cc:dd:ee:ff)
                      • sensor.name: the name of the sensor. It can be selected from a drop-down list
                      • notification.period: the period in milliseconds used to receive notification for a specific sensor. The value will be ignored it the listen option is not checked.
                      "},{"location":"connect-field-devices/xdk-driver/","title":"Xdk Driver","text":"

                      Eclipse Kura provides a specific driver that can be used to interact with Bosch Xdk110 devices. The driver is available only for gateways that support the new Bluetooth LE APIs. It can be used in to the Wires framework, the Asset model or directly using the Driver itself.

                      Info

                      The Xdk driver can only be used with Xdk110 with VirtualXdkDemo installed and with firmware version > 3.5.0. If your device has an older firmware, please update it.

                      "},{"location":"connect-field-devices/xdk-driver/#features","title":"Features","text":"

                      The Xdk Driver can be used to get the values from all the sensor provider by the xdk110 (both in polling mode and notification):

                      • three-axis acceleration (or four-axis quaternion rappresentation)
                      • three-axis gyroscope (or four-axis quaternion rappresentation)
                      • light
                      • noise
                      • pressure
                      • ambient temperature
                      • humidity
                      • sd-card detect status
                      • push buttons
                      • three-axis magnetic field
                      • magnetometer resistence
                      • led status
                      • rms voltage of LEM sensor
                      Resource Unit Description ACCELERATION_X, ACCELERATION_Y, ACCELERATION_Z G The proper acceleration for each axis* GYROSCOPE_X, GYROSCOPE_Y, GYROSCOPE_Z rad/S The angular acceleration for each axis* LIGHT lux The light value NOISE DpSpl The acustic pressure value PRESSURE Pa The pressure value TEMPERATURE C The temperature value HUMIDITY %rH The relative humidity SD_CARD_DETECTION_STATUS boolean SD-Card detect status PUSH_BUTTONS bit Button status, encoded as bit field: Bit 0, Button 1; Bit 1, Button 2 MAGNETOMETER_X, MAGNETOMETER_Y, MAGNETOMETERE_Z uT The magnetometer value for each axis MAGNETOMETER_RESISTENCE ohm The magnetometer resistence value LED_STATUS bit Led status, encoded as a bit field: Bit 0: yellow Led; Bit 1: orange Led; Bit 2: red Led VOLTAGE_LEM mV RMS voltage of LEM sensor

                      *If the Quaternion rappresentation is selected, then:

                      Resource Unit Description QUATERNION_M, QUATERNION_X, QUATERNION_Y, QUATERNION_Z number The rotation-quaternion for each axis

                      When a notification is enabled for a specific channel (sensor), the notification period can't be set. Use the Timer in Wire Graph to set polling.

                      "},{"location":"connect-field-devices/xdk-driver/#documentation","title":"Documentation","text":"

                      All the information regarding the Xdk110 is available in the Xdk Bosch Connectivity here website. The XDK-Workbench is the tool that can be used to develop software for the Xdk110. It can be downloaded from here. XDK-Workbench is required to install VirtualXdkDemo.

                      Warning

                      We found connection problems with Xdk, probably due to issues with XDK-Workbench version 3.6.0. We recommend installing version 3.5.0.

                      The Virtual XDK application user guide contains all the information regarding the XDK110 UUIDs and data formats here.

                      Info

                      To switch between quaternion and sensor representation, the XDK110 needs to be instructed using the 55b741d5-7ada-11e4-82f8-0800200c9a66 UUID. Set it to 0x01 to enable Quaternions.

                      If quaternion representation is enabled, the data format is as follows:

                      Bytes Data Type 0,1,2 & 3 Rotation Quaternion M float 4,5,6 & 7 Rotation Quaternion X float 8,9,10 & 11 Rotation Quaternion Y float 12,13,14 & 15 Rotation Quaternion Z float"},{"location":"connect-field-devices/xdk-driver/#installation","title":"Installation","text":"

                      As the others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace here. It can be installed following the instructions provided here.

                      "},{"location":"connect-field-devices/xdk-driver/#instance-creation","title":"Instance creation","text":"

                      A new Xdk Driver instance can be created either by clicking the New Driver button in the dedicated Drivers and Assets Web UI section or by clicking on the + button under Services. In both cases, the org.eclipse.kura.driver.ble.xdk factory must be selected and a unique name must be provided for the new instance. Once instantiated, the Xdk Driver has to be configured setting the Bluetooth interface name (i.e. hci0). Other options available are related to the quaternion/sensor representation and the sensor sampling rate (in Hz).

                      "},{"location":"connect-field-devices/xdk-driver/#channel-configuration","title":"Channel configuration","text":"

                      The Xdk Driver channel can be configured with the following parameters:

                      • enabled: it allows to enable/disable the channel. If it isn't selected the channel will be ignored.
                      • name: the channel name.
                      • type: the channel type, (READ, WRITE, or READ_WRITE).

                        Warning

                        The Xdk driver can only be used with READ.

                      • value.type: the Java type of the channel value. The value read by the Driver will be converted to the value.type.

                      • listen: when selected, a listener will be attached to this channel. Any event on the channel will be reported using a callback and the value will be emitted.
                      • xdk.address: the address of the specific xdk (i.e. aa:bb:cc:dd:ee:ff)
                      • sensor.name: the name of the sensor. It can be selected from a drop-down list.
                      "},{"location":"core-services/active-mq-artemis-broker/","title":"ActiveMQ Artemis MQTT Broker","text":"

                      Apart from using the simple ActiveMQ-7 MQTT instance available in the Simple Artemis MQTT Broker Service, this service allows to configure, in a more detailed way, the characteristics of the ActiveMQ-7 broker instance running in Kura.

                      This service exposes the following configuration parameters:

                      • Enabled - (Required) - Enables the broker instance
                      • Broker XML - Broker XML configuration. An empty broker configuration will disable the broker.
                      • Required protocols - A comma seperated list of all required protocol factories (e.g. AMQP or MQTT)
                      • User configuration - (Required) - User configuration in the format: user=password|role1,role2,...
                      • Default user name - The name of the default user

                      Please refer to the official documentation for more details on how to configure the ActiveMQ broker service.

                      "},{"location":"core-services/active-mq-artemis-broker/#service-usage-example","title":"Service Usage Example","text":"

                      Setting the Broker XML field as follows:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration xmlns=\"urn:activemq\"\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\nxsi:schemaLocation=\"\nurn:activemq https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-server/src/main/resources/schema/artemis-server.xsd\nurn:activemq:core https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-server/src/main/resources/schema/artemis-configuration.xsd\nurn:activemq:jms https://raw.githubusercontent.com/apache/activemq-artemis/master/artemis-jms-server/src/main/resources/schema/artemis-jms.xsd\n\">\n\n    <core xmlns=\"urn:activemq:core\">\n        <persistence-enabled>false</persistence-enabled>\n\n        <acceptors>\n            <acceptor name=\"netty-acceptor\">tcp://localhost:61616</acceptor>\n            <acceptor name=\"amqp-acceptor\">tcp://localhost:5672?protocols=AMQP</acceptor>\n            <acceptor name=\"mqtt-acceptor\">tcp://localhost:1883?protocols=MQTT</acceptor>\n        </acceptors>\n\n        <resolve-protocols>false</resolve-protocols>\n\n        <security-settings>\n        <!-- WARNING: this is only for testing and completely insecure -->\n            <security-setting match=\"#\">\n                <permission type=\"createDurableQueue\" roles=\"guest\"/>\n                <permission type=\"deleteDurableQueue\" roles=\"guest\"/>\n                <permission type=\"createNonDurableQueue\" roles=\"guest\"/>\n                <permission type=\"deleteNonDurableQueue\" roles=\"guest\"/>\n                <permission type=\"consume\" roles=\"guest\"/>\n                <permission type=\"send\" roles=\"guest\"/>\n            </security-setting>\n        </security-settings>\n\n    </core>\n\n    <jms xmlns=\"urn:activemq:jms\">\n    <topic name=\"TEST.T.1\" />\n    </jms>\n\n</configuration>\n

                      the User configuration to:

                      guest=test12|guest\n

                      while setting the Default user name to:

                      guest\n

                      will determine that the TCP ports 1883, 5672 and 61616 are now open (you can verify that via netstat -antup).

                      Configuring the MqttDataTransport in System -> Cloud Services -> MqttDataTransport to use:

                      • broker-url - mqtt://localhost:1883
                      • username - guest
                      • password - test12

                      Clicking on the Connect button will result in a successful connection of Kura cloud service to the ActiveMQ-7 MQTT broker.

                      Note

                      The XML configuration above only allows connections originating from the gateway itself. In order to allow external connection the bind URLs specified using the acceptor tag must be modified by specifying an external accessible address instead of localhost. If the bind address is set to 0.0.0.0, the broker will listen on all available addresses.

                      "},{"location":"core-services/clock-service/","title":"Clock Service","text":"

                      The ClockService handles the date and time management of the system. If enabled, it tries to update the system date and time using a Network Time Protocol (NTP) server. NTP can use NTS as authentication mechanism through chrony.

                      "},{"location":"core-services/clock-service/#service-configuration","title":"Service Configuration","text":"

                      To manage the system date and time, select the ClockService option located in the Services area as shown in the screen capture below.

                      The ClockService provides the following configuration parameters:

                      • enabled: sets whether or not this service is enabled or disabled (Required field).

                      • clock.set.hwclock: defines if the hardware clock of the gateway must be synced after the system time is set. If enabled, the service calls the Linux command hwclock --utc --systohc.

                      • clock.provider: specifies one among Java NTP client (java-ntp), Linux chrony command (chrony-advanced), Linux ntpdate command (ntpd) (Required field). If chrony-advanced is used, Kura will not change system and/or hardware clock directly, delegating these operations to chrony.

                      • clock.ntp.host: sets a valid NTP server host address.

                      • clock.ntp.port: sets a valid NTP port number (not supported by ntpd, use the port 123 in that case).

                      • clock.ntp.timeout: specifies the NTP timeout in milliseconds.

                      • clock.ntp.max-retry: defines the number of retries when a sync fails (retry at every minute). Subsequently, the next retry occurs on the next refresh interval.

                      • clock.ntp.retry.interval: defines the interval in seconds between each retry when a sync fails. If the clock.ntp.refresh-interval parameter is less than zero, there is no update. If the clock.ntp.refresh-interval parameter is equal to zero, there is only one try at startup (Required field).

                      • clock.ntp.refresh-interval: defines the frequency (in seconds) at which the service tries to sync the clock. Note that at the start of Kura, when the ClockService is enabled, it tries to sync the clock every minute until it is successful. After a successful sync, this operation is performed at the frequency defined by this parameter. If the value is less than zero, there is no update. If the value is equal to zero, syncs only once at startup.

                      • chrony.advanced.config: specifies the content of the chrony configuration file. If this field is left blank, the default system configuration will be used. All fields above will be ignored. To obtain the hardware clock synchronization the directive rtcsync could be used. The rtcsync directive provides the hardware clock synchronization made by the linux kernel every 11 minutes. For further information reference the chrony website.

                      Two example configuration are shown below.

                      "},{"location":"core-services/clock-service/#nts-secure-configuration-example","title":"NTS Secure configuration example","text":"
                      server time.cloudflare.com iburst nts\nserver nts.sth1.ntp.se iburst nts\nserver nts.sth2.ntp.se iburst nts\n\nsourcedir /etc/chrony/sources.d\n\ndriftfile /var/lib/chrony/chrony.drift\n\nlogdir /var/log/chrony\n\nmaxupdateskew 100.0\n\nrtcsync\n\nmakestep 1 -1\n\nleapsectz right/UTC\n

                      Note

                      If the system stays disconnected from the network for a long time or if the backup battery is not working properly or is depleted, there is the possibility of a synchronization failure due to the client inability to verify the server certificates.

                      If this happens and no counter action has been taken in the chrony configuration file, the risk is that the gateway will be unable to synchronise again its date and therefore will not be able to connect to the cloud and/or be fully operational.

                      A possible way to prevent this issue is to temporary disable the certificate verification using the directive nocerttimecheck. This directory will disable the security checks of the activation and expiration times of certificates for the specified number of clock updates and should be used with caution due to the important security implications.

                      As reported by the official Chrony documentation, disabling the time checks has important security implications and should be used only as a last resort, preferably with a minimal number of trusted certificates. The default value is 0, which means the time checks are always enabled.

                      An example of the directive is nocerttimecheck 1 This would disable the time checks until the clock is updated for the first time, assuming the first update corrects the clock and later checks can work with correct time.

                      "},{"location":"core-services/clock-service/#simple-configuration-example","title":"Simple configuration example","text":"
                      # Use public NTP servers from the pool.ntp.org project.\npool pool.ntp.org iburst\n\n# Record the rate at which the system clock gains/losses time.\ndriftfile /var/lib/chrony/drift\n\n# Allow the system clock to be stepped in the first three updates\n# if its offset is larger than 1 second.\nmakestep 1 -1\n\n# Enable kernel synchronization of the real-time clock (RTC).\nrtcsync\n
                      "},{"location":"core-services/command-service/","title":"Command Service","text":"

                      The Command Service provides methods for running system commands from the Kura web console or from Kapua. In the Kura web console, the service is available clicking on the Command tab under the Device section, while for the cloud platform please refer to the official documentation.

                      To run a command simply fill the Execute field with the command and click the Execute button. The service also provides the ability for a script to execute using the File option of the Command tab in the Kura web console or the Kapua Cloud Console. This script must be compressed into a zip file with the eventual, associated resource files.

                      Once the file is selected and Execute is clicked, the zip file is sent embedded in an MQTT message on the device. The Command Service in the device stores the file in /tmp, unzips it, and tries to execute a shell script if one is present in the file. Note that in this case, the Execute parameter cannot be empty; a simple command, such as ls -l /tmp, may be entered.

                      Warning

                      When decompressed, the script loses its executable attribute. To fix this problem, if you plan to execute a script, the command entered into the Execute line must trigger the execution: ** bash [name of the script] **.

                      The configuration of the service is in the CommandService tab located in the Services area as shown in the screen capture below.

                      The Command Service provides the following configuration parameters:

                      • Command Enable: sets whether this service is enabled or disabled in the cloud platform (Required field).

                      • Command Password Value: sets a password to protect this service.

                      • Command Working Directory: specifies the working directory where the command execution is performed.

                      • Command Timeout: sets the timeout (in seconds) for the command execution.

                      • Command Environment: supplies a space-separated list of environment variables in the format key=value.

                      • Privileged/Unprivileged Command Service Selection: sets the modality of the command service. When set to privileged, the commands are run using the (privileged) user that started Kura, tipically kurad or root. When set to unprivileged, a standard user will run the commands.

                      When a command execution is requested in the cloud platform, it sends an MQTT control message to the device requesting that the command be executed. On the device, the Command Service opens a temporary shell in the command.working.directory, sets the command.environment variables (if any), and waits command.timeout seconds to get command response.

                      "},{"location":"core-services/configuration-service/","title":"Configuration Service","text":"

                      The Configuration Service is responsible to manage the framework configuration by creating and persisting the framework snapshot. Built on top of the OSGi Configuration Admin and Metatype services, it is also responsible to track and manage the creation and deletion of service instances as well as OSGi component factories.

                      The ConfigurationService is accessible using the following REST APIs and cloud request handlers:

                      • Configuration v1 REST APIs (deprecated) and CONF-V1 MQTT Request Handler (deprecated)
                      • Configuration v2 REST APIs and CONF-V2 MQTT Request Handler
                      "},{"location":"core-services/container-orchestration-image-auth/","title":"Eclipse Eclipse Kura Container Image Authenticity and Allowlist Enforcement feature","text":"

                      Containerized applications are becoming increasingly popular in the software ecosystem as a way to deploy and distribute applications. As a result, ensuring the security of software supply chains has become a critical concern. Implementing best practices, such as signing and verifying images to mitigate man-in-the-middle (MITM) attacks and validating their authenticity and freshness, play a pivotal role in safeguarding the integrity of the software supply chain.

                      To ensure the authenticity and integrity of the code within the container images, binding the image to a specific entity or organization via signature verification is crucial and increasingly common.

                      The purpose of this document is to provide a high-level understanding of the Eclipse Kura container authenticity and allowlist enforcement feature introduced with Eclipse Kura version 5.5.0.

                      "},{"location":"core-services/container-orchestration-image-auth/#high-level-flow","title":"High-level flow","text":"

                      The Eclipse Kura container authenticity and allowlist enforcement feature is designed to address container authenticity concerns by providing a mechanism to perform the signature verification of container images and restricting which container images can be deployed on the Eclipse Kura instance.

                      This is achieved by implementing the following flow:

                      Description of the flow:

                      • Monitoring: Eclipse Kura monitors the container engine events. When a \"container start\" event is triggered it intercepts the information regarding the image used to spin up the container and starts performing the container authenticity checks.
                      • Allowlist: Firstly, it checks if the digest of the image used to run the container can be found in the Eclipse Kura allowlist. If so, the container is allowed to run. If not, Eclipse Kura proceeds with the image signature verification.
                      • Signature verification: If no trust anchor (i.e. a public key or a X509 certificate depending on the signature mechanism) is available for the verification process to happen, the container is not allowed to run. If one is available, the image signature is verified and the allowlist is updated accordingly.

                      This flow applies to both containers managed by Eclipse Kura itself and containers ran directly by the user via CLI, allowing for a fine-grained control over the container images that can be deployed on the Eclipse Kura instance.

                      • Unmanaged containers: Eclipse Kura allows the user to define a list of container images that can be deployed on the platform. This is done by adding the image digests to the allowlist. When a container is started, Eclipse Kura checks if the image digest is in the allowlist and, if so, allows the container to run. This is meant to be used by users who, for any reason, need to run a container outside the Eclipse Kura framework but still want the safety guarantees provided by pinning the images to a specific digest.
                      • Managed containers: For containers ran by the framework, Eclipse Kura also allows the user to provide a trust anchor to be used for the signature verification process. This allows the user to use a mutable tag when specifying the container image, without giving up the required authenticity checks. Once the image is verified, its digest is stored within the Eclipse Kura allowlist, permitting it to be ran both from Eclipse Kura and the CLI without the need for an Internet connection. When a new image is published, the user can simply trigger the pull. The verification process will update the digest automatically.

                      Note that the user can still directly provide an image digest when running an Eclipse Kura-managed container. This will prevent the Eclipse Kura automatic signature verification process to run. This feature is meant to be used when:

                      • the user already ensured the image authenticity and does not want to go through the verification process again
                      • the image was not signed but the user still wants the enforcement trust guarantees provided by Eclipse Kura
                      "},{"location":"core-services/container-orchestration-image-auth/#unmanaged-containers","title":"Unmanaged containers","text":"

                      Regarding the containers not managed by Eclipse Kura, the Container Orchestration service provides a security feature which enforces the containers allowed to run on the system. This feature can be enabled or disabled through the Allowlist Enforcement Enabled option.

                      This mechanism leverages the concept of digest, which is a unique and immutable identifier for a container image: it is therefore an excellent mean to identify the image from which a container was created.

                      Given the above, Eclipse Kura allows the user to provide a list of container image digests in the Container Image Allowlist field, in the form of newline-separated strings: when a container is started on the host system (whether it is launched by Eclipse Kura or by a terminal CLI), Eclipse Kura retrieves the image from which the container was created, extracts the digest and verifies that it is present in the allowlist provided during service configuration.

                      If the image digest is present and matches the container's one, it is then allowed to run without interference. Otherwise, it is immediately stopped and deleted from the host system.

                      A similar behaviour is performed at Eclipse Kura startup or when the enforcement is enabled at runtime: if there are already containers on the device (whether they are started or stopped) and the enforcement is activated, Eclipse Kura extracts the digests from the containers and compares them with those in the allowlist. Containers that have been created from images whose digests are in the allowlist will be left running, otherwise they will be stopped and deleted from the descriptors list.

                      The verification is performed by intersecting the list of digests extracted by the containers and the one provided in the configuration: in this way, an empty intersection will result in a failing verification, while a non-empty one means that the image digests is equal to one of the allowlist entries.

                      "},{"location":"core-services/container-orchestration-image-auth/#example-scenarios","title":"Example scenarios","text":"

                      A user wants to leverage the container enforcement in order to let only docker containers started from an image named foo_image to be run on the device. To do this, they should enable the Container Enforcement by setting the Allowlist Enforcement Enabled to true, and fill the Container Image Allowlist field with the digest of the foo_image docker image (i.e.sha256:0000000000000000000000000000000000000000000000000000000000000000 in the example below).

                      "},{"location":"core-services/container-orchestration-image-auth/#startup-of-the-enforcement-feature","title":"Startup of the Enforcement Feature","text":"

                      Let's suppose that on the device there are already two containers running, one created from the foo_image an one from an image named unwanted_image with digest sha256:9999999999999999999999999999999999999999999999999999999999999999. Once the enforcement starts with the configuration previously described, it extracts the digests of both containers and checks if they're included in the provided allowlist: in this case, the container originating from the foo_image will be allowed to run, while the one created from the unwanted_image will be stopped and deleted, because its digest is not included in the allowlist.

                      The same happens also for already stopped containers: if the digests is verified, they'll be left in the descriptors list in the Stopped state, otherwise they'll be deleted.

                      "},{"location":"core-services/container-orchestration-image-auth/#running-container-after-enforcement-feature-startup","title":"Running Container After Enforcement Feature Startup","text":"

                      After the starting phase just described, Eclipse Kura will continuously monitor the activity of the docker engine. Whenever a container is started on the device, it will check the image digest from which the container was created, comparing it to the ones inside the allowlist: if the container is created from the foo_image docker image, it will be allowed to start, otherwise it will be stopped and deleted. If the user wants to add more than one allowed image, for example one named wanted_image with digest sha256:1111111111111111111111111111111111111111111111111111111111111111), it just needs to add it to the newline-separated list.

                      "},{"location":"core-services/container-orchestration-image-auth/#managed-containers","title":"Managed containers","text":"

                      As explained in the previous section, Managed Containers (i.e. org.eclipse.kura.container.provider.ContainerInstances) support Container Signature validation. To do so a new API has been introduced: the ContainerSignatureValidationService.

                      Currently, there are three main mechanism for container signature verification:

                      • Docker Content Trust (DCT) a.k.a. Notary V1
                      • Notation a.k.a. Notary V2
                      • Sigstore's Cosign

                      This newly introduced service allows Eclipse Kura to have multiple different Services implementing this interface and, thus, support all major signature mechanisms. A reference implementation for the ContainerSignatureValidationService is the DummyContainerSignatureValidationService available in Kura examples folder for anyone wanting to implement an alternative ContainerSignatureValidationService.

                      When a ContainerSignatureValidationService is installed on Eclipse Kura, it gets automatically registered among the available validators and will be used to perform the signature validation. Under the hood, the ContainerInstance has a list of the available ContainerSignatureValidationService providers that, upon receiving a configuration update, it interrogates to check whether the requested container image is authentic using the provided Trust Anchor.

                      • If even only one service reports the signature as valid, the image signature is considered valid.
                      • If none of the available services reports the signature as valid, the image signature is considered not valid.

                      This means that Eclipse Kura doesn't need to prompt the user for selecting the correct ContainerSignatureValidationService when performing the check.

                      Once the image signature is validated, the image digest is stored in the Eclipse Kura snapshot and added to the ContainerOrchestratorService allowlist (see section above). A container whose image digest is available in the allowlist won't be validated again, therefore after the initial check, the internet connection is no longer required for it to work.

                      "},{"location":"core-services/container-orchestration-image-auth/#example-scenarios_1","title":"Example Scenarios","text":""},{"location":"core-services/container-orchestration-image-auth/#container-instance-digest","title":"Container Instance Digest","text":"

                      Let's suppose to have a device on which Eclipse Eclipse Kura is running with the Container Orchestration Service enabled, its Enforcement feature disabled and a Container Instance named test-container up and running on the system.

                      A user wants to activate the Enforcement feature, so it enables the Allowlist Enforcement Enabled option in the Container Orchestration Service, but leaves the Container Image Allowlist option blank because it wants that no containers are started outside the framework.

                      As the feature starts, it checks all the container running on the device, including the test-container, which will be stopped and deleted: this happens because the container digest is not included in the Container Orchestration Service Allowlist and the Container Instance is not providing any information through its Container Image Enforcement Digest option.

                      In order to allow the Container Instance, the user should provide the correct digest in the instance settings. Let's suppose that the digest of the image from which the container is created is sha256:0000000000000000000000000000000000000000000000000000000000000000 and that the user fills the corresponding option with it: once the Container Instance is updated, the provided digest is included in the Enforcement feature allowlist, so the container will be allowed to run without interference. This case can be summarised as:

                      Once the instance digest is added to the enforcement feature, it can be used also to authorise container run by the Command Line Interface, or other instances on Eclipse Eclipse Eclipse Kura without providing the digest option. But what happen if the Container Instance is disabled? The aim of the Container Instance Enforcement Digest is to be used as an authorization method of the instance itself: this means that its digest is added to the enforcement feature allowlist only if the instance is enabled.

                      As it can be seen from the image, the merged Enforcement Allowlist box doesn't contain the digest associated to the Container Instance, because, being it disabled, the corresponding digest is ignored. If the enforcement is enabled and a container with digest DIGEST Z is started, it will then be stopped and deleted, because its digest won't be included in the allowlist.

                      Finally, everytime a Container Image Enforcement Digest option is modified, or the ContainerInstance is disabled or deleted, the enforcement feature will perform a check on all the already running containers. This is done because, if the digest that was previously provided has changed after an instance update, or removed due to disabling or deleting the instance, those containers that were previously authorised by this digest are no longer allowed to run. So they must be stopped and deleted.

                      Warning

                      The digests provided through Container Instances allow running also container started by the CommandLineInterface or by other Container Instances in the framework, even without providing the digest.

                      It has to be considered that, if the ContainerInstance is disabled (or its digest option changes), the enforcement feature will stop and delete the containers that are no longer matching the provided digest.

                      The user needs to be careful, then, to rely only on the digests set in the ContainerInstances options. If the user will need to run containers from the CLI, it is preferable to use the allowlist of the Container Orchestration Service. For the Container Instances, is a good practise to provide a digest.

                      "},{"location":"core-services/container-orchestration-image-auth/#container-signature-verification","title":"Container Signature Verification","text":"

                      If the Container Image Enforcement Digest option is not provided, Eclipse Eclipse Kura will proceed with the Signature Verification: this process tries to extract the digest of the image from which the container was started.

                      Let's then suppose that we are back at the beginning of the previous example, with a blank Container Orchestration Service allowlist and a not given Container Instance Digest, but this time a Trust Anchor option is given: in this case Eclipse Eclipse Kura will start the Signature Verification Process, in order to extract the digest.

                      If, for whatever reason, the Signature Verification fails, no digests will be added to the allowlist: if the enforcement feature is enabled, the started container will be then stopped and deleted by the Enforcement Monitor, because no digests are included in the final Enforcement Allowlist.

                      While, if the procedure completes correctly, the extracted digest will be added to the Enforcement Allowlist and written in the snapshot: in this way the container will pass the enforcement feature check, and the obtained digest will be stored as part of the Container Instance's configuration just started.

                      If the user wants to check again the digest through the Signature Verification Service, it just needs to erase the Container Image Enforcement Digest option from the configuration of the Container Instance: in this way the Signature feature will be triggered again and the digest recalculated.

                      Warning

                      Even in this situation, the digest could be used to authenticate container started from the CommandLineInterface: also in this case keep in mind that if the Container Instance is disabled, stopped or updated with different digest, those CLI-based container could be stopped and deleted.

                      "},{"location":"core-services/container-orchestration-provider-apis/","title":"Container Orchestration Provider APIs","text":""},{"location":"core-services/container-orchestration-provider-apis/#java-api","title":"Java API","text":""},{"location":"core-services/container-orchestration-provider-apis/#containerorchestrationservice","title":"ContainerOrchestrationService","text":"

                      The ContainerOrchestrationService is used to directly communicate with the running container engine. It exposes methods for listing, creating, and stopping containers. This class utilizes an instantiated ContainerConfiguration object as a parameter for container creation.

                      "},{"location":"core-services/container-orchestration-provider-apis/#containerconfiguration","title":"ContainerConfiguration","text":"

                      The ContainerConfiguration class, allows you to define a container to create. Using the embedded builder class, one can define many container-related parameters such as name, image, ports and volume mounts.

                      "},{"location":"core-services/container-orchestration-provider-apis/#containerinstancedescriptor","title":"ContainerInstanceDescriptor","text":"

                      The ContainerInstanceDescriptor class is used to describe a container that has already been created. This class contains runtime information such as the ID of the container.

                      "},{"location":"core-services/container-orchestration-provider-apis/#containerstate","title":"ContainerState","text":"

                      The ContainerState is a class that exposes an enum of container states tracked by the framework.

                      "},{"location":"core-services/container-orchestration-provider-apis/#passwordregistrycredentials","title":"PasswordRegistryCredentials","text":"

                      The PasswordRegistryCredentials class stores password credentials when provisioning a container to pull from an alternative password-protected registry.

                      "},{"location":"core-services/container-orchestration-provider-apis/#passwordregistrycredentials_1","title":"PasswordRegistryCredentials","text":"

                      The PasswordRegistryCredentials class stores password credentials when provisioning a container to pull from an alternative password-protected registry.

                      Note

                      The Container Orchestration Provider exports an MQTT-Namespace API. This API can be used to manage containers via MQTT requests from external applications. Please visit the Remote Gateway Inventory via MQTT documentation for more information.

                      "},{"location":"core-services/container-orchestration-provider-authenticated-registries/","title":"Container Orchestration Provider Authenticated Registries","text":"

                      The Container Orchestrator provider allows the user to pull images from private and password-protected registries. The following document will provide examples of how to connect to some popular registries.

                      Note

                      These guides make the following two assumptions.

                      1. That you have already configured the Container Orchestrator and have a container instance already created. Please see the usage doc, to learn the basics of the orchestrator.
                      2. That the image you are trying to pull supports the architecture of the gateway.
                      "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#private-docker-hub-registries","title":"Private Docker-Hub Registries","text":""},{"location":"core-services/container-orchestration-provider-authenticated-registries/#preparation","title":"Preparation:","text":"
                      • have a Docker Hub account (its credentials), and a private image ready to pull.
                      "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#procedure","title":"Procedure:","text":"
                      1. Populate the image name field. The username containing the private image must be placed before the image name separated by a forward slash. This is demonstrated below:
                      2. **Image Name: ** <Docker-Hub username>/<image name> for exampleeclipse/kura.

                      3. Populate the credential fields:

                      4. Authentication Registry URL: This field should be left blank.
                      5. Authentication Username: Your Docker Hub username.
                      6. Password: Your Docker Hub password.

                      "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#amazon-web-services-elastic-container-registries-aws-ecr","title":"Amazon Web Services - Elastic Container Registries (AWS-ECR)","text":""},{"location":"core-services/container-orchestration-provider-authenticated-registries/#preparation_1","title":"Preparation:","text":"
                      • Have access to an Amazon ECR instance.
                      • Have the AWS-CLI tool installed and appropriately configured on your computer.
                      • Have access to your AWS ECR web console.
                      "},{"location":"core-services/container-orchestration-provider-authenticated-registries/#procedure_1","title":"Procedure:","text":"
                      1. Sign in to your amazon web console, navigate to ECR and identify which container you will like to pull onto the gateway. Copy the URI of the container. This URI will reveal the information required for the following steps. Here is how to decode the URI <identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/<image name>:<image tag>.

                      2. Generating an AWS-ECR access password. Open a terminal window on the machine with aws-cli installed and enter the following command aws ecr get-login-password --region <ecr-region>. Your ECR region can be found by inspecting the container URI string copied in the previous step. This command will return a long string which will be used as the repo password in the gateway.

                      3. Populating information on the gateway.

                      4. **Image Name: ** enter the full URI without the tag.<identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/<image name>
                      5. Image Tag: enter only the image tag found at the end of the URI <image tag>
                      6. Authentication Registry URL: Paste only the part of the URI before the image name <identifier>.dkr.ecr.<ecr-region>.amazonaws.com/<directory>/
                      7. Authentication Username: will be AWS
                      8. Password: will be the string created in step two.

                      A fully configured container set to pull AWS will look like the following.

                      "},{"location":"core-services/container-orchestration-provider-usage/","title":"Container Orchestration Provider Usage","text":""},{"location":"core-services/container-orchestration-provider-usage/#before-starting","title":"Before Starting","text":"

                      For this bundle to function appropriately, the gateway must have a supported container engine installed and running. Currently, the only officially supported engine is Docker.

                      "},{"location":"core-services/container-orchestration-provider-usage/#starting-the-service","title":"Starting the Service","text":"

                      To use this service select the ContainerOrchestrationService option located in the Services area. The ContainerOrchestrationService provides the following parameters:

                      • Enabled--activates the service when set to true
                      • Container Engine Host URL--provides a string that tells the service where to find the container engine (best left to the default value).
                      • Allowlist Enforcement Enabled--activates the container enforcement of the service, which let only the allowed containers to run
                      • Container Image Allowlist Content--the comma-separated list of conainer's digests allowed to be run

                      "},{"location":"core-services/container-orchestration-provider-usage/#creating-your-first-container","title":"Creating your first container.","text":"

                      To create a container, select the + icon (Create a new component) under services. A popup dialogue box will appear. In the field Factory select org.eclipse.kura.container.provider.ContainerInstance from the drop-down. Then, use the Name field to specify a name for the container.

                      Note

                      The name specified in the 'Name' field will also be the name of the container when it is spun up by the orchestrator.

                      After pressing submit, a new component will be added under the services tab, with the name that was selected in the dialogue. Select this component to finish configuring the container.

                      "},{"location":"core-services/container-orchestration-provider-usage/#configuring-the-container","title":"Configuring the container","text":"

                      To begin configuring the container, look under Services and select the item which has the name set in the previous step. Containers may be configured using the following fields:

                      • Enabled - When true, the service will create the defined container. When false the API will not create the container or will destroy the container if already running.

                      • Image Name - Describes the image that will be used to create the container. Remember to ensure that the selected image supports the architecture of the host machine, or else the container will not be able to start.

                      • Image Tag - Describes the version of the container image that will be used to create the container.

                      • Trust Anchor - Trust anchor used to verify the container image Signature Verification

                      • Verify in transparency log - Sets the transparency log verification, to be used when a container image signature has been uploaded to the transparency log.

                      • Container Image Enforcement Digest - A string representing the digest for the image that will be allowed to run by this container instance (eg: sha256:0000000000000000000000000000000000000000000000000000000000000000). It is used in the Container Enforcement service provided by the Container Orchestration Service.

                      • Authentication Registry URL - URL for an alternative registry to pull images from. (If the field is left blank, credentials will be applied to Docker-Hub). Please see the Authenticated Registries document for more information about connecting to different popular registries.

                      • Authentication Username - Describes the username to access the container registry entered above.

                      • Password - Describes the password to access the alternative container registry.

                      • Image Download Retries - Describes the number of retries the framework will attempt to pull the image before giving up.

                      • Image Download Retry Interval - Describes the amount of time the framework will wait before attempting to pull the image again.

                      • Image Download Timeout - Describes the amount of time the framework will let the image download before timeout.

                      • Internal Ports - This field accepts a comma-separated list of ports that will be internally exposed on the spun-up container. In this field, you can also specify which protocol to run at the port by appending a port with a colon and typing in the name of the network protocol. Example: 80, 443:tcp, 8080:udp.

                      • External Ports - This field accepts a comma-separated list of ports that will be externally exposed on the host machine.

                      • Privileged Mode - This flag if enabled will give the container root capabilities to all devices on the host system. Please be aware that setting this flag can be dangerous, and must only be used in exceptional situations.

                      • Environment Variables (optional) - This field accepts a comma-separated list of environment variables, which will be set inside the container when spun up.

                      • Entrypoint Override (optional) - This field accepts a comma-separated list which is used to override the command used to start a container. Example: ./test.sh,-v,-d,--human-readable.

                      • Memory (optional) - This field allows the configuration of the maximum amount of memory the container can use in bytes. The value is a positive integer, optionally followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes. The minimum and default values depends by the native container orchestrator. If left empty, the memory assigned to the container will be set to a default value.

                      • CPUs (optional) - This value specifies how many CPUs a container can use. Decimal values are allowed, so if set to 1.5, the container will use at most one and a half cpu resource.

                      • GPUs (optional) - This field configures how many Nvidia GPUs a container can use. Allowed values are all or an integer number. If there's no Nvidia GPU installed, leave it empty. The Nvidia Container Toolkit must be installed on the system to correctly configure the service, otherwise the container will not start. If the Nvidia Container Runtime is used, leave the field empty.

                      • Volume Mount (optional) - This field accepts a comma-separated list of system-to-container file mounts. This allows for the container to access files on the host machine.

                      • Peripheral Device (optional) - This field accepts a comma-separated list of device paths. This parameter allows devices to be passed through from the host to the container.

                      • Runtime (optional): Specifies the fully qualified name of an alternate OCI-compatible runtime, which is used to run commands specified by the 'run' instruction. Example: nvidia corresponds to --runtime=nvidia. Note: when using the Nvidia Container Runtime, leave the GPUs field empty. The GPUs available on the system will be accessible from the container by default.

                      • Networking Mode (optional) - Use this field to specify what networking mode the container will use. Possible Drivers include: bridge, none, container:{container id}, host. Please note that this field is case-sensitive. This field can also be used to connect to any of the networks listed by the cli command docker network ls.

                      • Logger Type - This field provides a drop-down selection of supported container logging drivers.

                      • Logger Parameters (optional) - This field accepts a comma-separated list of logging parameters. More information can be found in the container-engine logger documentation, for instance here.

                      • Restart Container On Failure - A boolean that tells the container engine to automatically restart the container when it has failed or shut down.

                      After specifying container parameters, ensure to set Enabled to true and press Apply. The container engine will then pull the respective image, spin up and start the container. If the gateway or the framework is power cycled, and the container and Container Orchestration Service are set to enabled, the framework will automatically start the container again upon startup.

                      "},{"location":"core-services/container-orchestration-provider-usage/#stopping-the-container","title":"Stopping the container","text":"

                      Warning

                      Stopping a container will delete it in an irreversible way. Please be sure to only use stateless containers and/or save their data in external volumes.

                      To stop the container without deleting the component, set the Enabled field to false, and then press Apply. This will delete the running container, but leave this component available for running the container again in the future. If you want to completely remove the container and component, press the Delete button to the top right of the screen, and press Yes on the confirmation dialogue.

                      "},{"location":"core-services/container-orchestration-provider-usage/#container-management-dashboard","title":"Container Management Dashboard","text":"

                      The Container Orchestration service also provides the user with an intuitive container dashboard. This dashboard shows all containers running on a gateway, including containers created with the framework and those created manually through the command-line interface. To utilize this dashboard the org.eclipse.container.orchestration.provider (ContainerOrchestrationService) must be enabled, and the dashboard can be opened by navigating to Device > Containers.

                      "},{"location":"core-services/container-orchestration-provider/","title":"Container Orchestration Provider","text":"

                      The Container Orchestration Provider allows Kura to manage Docker. With this tool, you can arbitrarily pull and deploy containerized software packages and run them on your gateway. This Service allows the user to create, configure, start, and stop containers all from the browser. The bundle will also restart containers if the gateway is restarted.

                      The feature is composed of two bundles, one that exposes APIs for container management and one that implements those APIs. This API is exposed so that you can leverage it to implement containerization in your own Kura plugins.

                      "},{"location":"core-services/deployment-service/","title":"Deployment Service","text":"

                      The Deployment Service allows to download files to the gateway and to perform actions on them. In the configuration tab it is possible to specify which is the directory that has to be used to store the downloaded files and the list of actions declared as deployment hooks that will be invoked when a corresponding metric is received with the download request.

                      The configuration requires to specify two parameters:

                      • downloads.directory - The directory to be used to store the downloaded files;
                      • deployment.hook.associations - The list of DeploymentHook associations in the form <request_type>=<hook_pid>, where <hook_pid> is the Kura Service Pid of a DeploymentHook instance and <request_type> is the value of the request.type metric received with the request.
                      "},{"location":"core-services/device-configuration-changes/","title":"Device Configuration Changes","text":"

                      Kura can detect changes to the components and publish them using a selected Cloud Publisher. There are two main components that enable this:

                      • org.eclipse.kura.configuration.change.manager, and
                      • org.eclipse.kura.event.publisher

                      The org.eclipse.kura.configuration.change.manager is responsible for detecting changes to any of the configurations currently running on the system and to publish a notification to a user-defined cloud publisher. By default, the org.eclipse.kura.event.publisher is used.

                      "},{"location":"core-services/device-configuration-changes/#configuration-change-manager","title":"Configuration Change Manager","text":"

                      The configuration change manager allows to collection of the PIDs of the components that have changed in the system. The configurable properties of this component are:

                      • Enable: whether to enable or not this component.
                      • CloudPublisher Target Filter: allows to specify the cloud publisher to use for sending the produced messages.
                      • Notification send delay (sec): allows to stack the changed PIDs for a given time before sending. In other words, it allows to group together sequences of PIDs that are changed in that time frame. This is to prevent message flooding at reboot or rollback.

                      The collected PIDs are sent to the Cloud Publisher as a KuraMessage with a payload body in JSON format. The timestamp of the KuraMessage is set to the last detected configuration change event. An example of message body is:

                      [\n    {\n        \u201cpid\u201d: \u201corg.eclipse.kura.clock.ClockService\u201d\n    },\n    {\n        \u201cpid\u201d: \u201corg.eclipse.kura.log.filesystem.provider.FilesystemLogProvider\u201d\n    }\n]\n

                      In the example above, a ClockService update triggered the delay timer, which was then reset by a configuration update on the FilesystemLogProvider. Afterward, no configuration updates reset the timer so the message containing the two PIDs was sent after expiration.

                      "},{"location":"core-services/device-configuration-changes/#event-publisher","title":"Event Publisher","text":"

                      By default, the org.eclipse.kura.event.publisher used by the configuration change manager does the actual publishing on a user-defined topic of the form:

                      $EVT/#account_id/#client_id/CONF/V1/CHANGE

                      This topic is adjusted for integration with EC, but the $EVT and the CONF/V1/CHANGE parts can be customized according to user needs by tweaking the Topic prefix and Topic properties.

                      The #account_id and #client_id fields are substituted at runtime with the account name and client ID at the Data Transport Service layer of the associated Cloud Connection.

                      The Qos, Retain, and Priority properties are the usual ones defined in the standard Cloud Publisher.

                      "},{"location":"core-services/h2db-service/","title":"H2Db Service","text":"

                      Kura integrates a Java SQL database named H2. The main features of this SQL Database are:

                      • Very fast, open source, JDBC API
                      • Embedded and server modes; in-memory databases
                      • Browser-based Console application
                      • Small footprint
                      "},{"location":"core-services/h2db-service/#supported-features","title":"Supported Features","text":"

                      Kura supports the following H2 database features:

                      • Persistence modes: The H2 implementation currently supports in-memory and file-based database instances. See the Persistence Modes section for more details.

                      • Multiple database instances: It is possible to create and configure multiple database instances from the Kura Administration UI, these instances can be selectively consumed by applications. A default database instance is created automatically.

                      • TCP Server: The current implementation allows external processes to access the database instances managed by Kura using TCP. This enables the integration of external applications that can share data with Kura components using the database.

                      • Web-based console: It is possible to start the H2 Web console directly from the Kura Administration UI. The console can be used to inspect the database contents and perform arbitrary queries for debug purposes.

                      • Basic credential management: The current implementation allows to change the password of the admin DB user from the Kura Administration UI. This allows the access restriction to the existing database instances.

                      By default, the DataService in Kura uses the H2 database to persist the messages.

                      "},{"location":"core-services/h2db-service/#limitations","title":"Limitations","text":"
                      • Private in-memory instances: Only named in-memory instances are supported (e.g. jdbc:h2:mem:<dbname>, where <dbname> is not the empty string), private instances represented by the jdbc:h2:mem: URL are currently not supported.

                      • Remote connections: The current implementation only supports embedded database instances. Connecting to remote instances using the jdbc:h2:tcp:* and jdbc:h2:ssl:* connector URLs is not supported.

                      Note

                      The new DbWireRecordFilter and DbWireRecordStore Wire components have been added. These components provide the same functionalities offered by the old H2DbWireRecordFilter and H2DbWireRecordStore components, but they can be used for connectiong to a generic relational database (i.e. H2DB, MySQL or MariaDB). The legacy components will continue to be available in order to keep backward compatibility, but will be deprecated since Kura 5.2.0 and should not be used for new installations.

                      "},{"location":"core-services/h2db-service/#usage","title":"Usage","text":""},{"location":"core-services/h2db-service/#creating-a-new-h2-database-instance","title":"Creating a new H2 database instance","text":"

                      To create a new H2 database instance, use the following procedure:

                      1. Open the Administrative UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
                      2. Select org.eclipse.kura.core.db.H2DbService from the Factory drop-down list, enter an arbitrary name for the new instance and click Apply.
                      3. An entry for the newly created instance should appear in the side menu under Services, click on it to review its configuration. It is not possible to create different DB instances that manage the same DB URL. When creating a new instance please make sure that the URL specified in the field db.connector.url is not managed by another instance.
                      "},{"location":"core-services/h2db-service/#configuration-parameters","title":"Configuration Parameters","text":"

                      The H2DbService provides the following configuration parameters:

                      • Connector URL: JDBC connector URL of the database instance. Passing the USER and PASSWORD parameters in the connector URL is not supported, these paramters will be ignored if present. Please use the db.user and db.password fields to provide the credentials.

                      Warning

                      If the database is created in persisted mode, please make sure that the Linux user running Eclipse Kura has the permissions required to create the database file. If the permissions are not ok, Eclipse Kura may be able to create the database (by default it runs with the CAP_DAC_OVERRIDE capability) but it may not be able to perform the periodic defragmentation process, this may cause the database file size to grow expecially if the write rate is high.

                      Executing the following commands as root can be useful to detect potential issues, replace database_parent_directory with the parent directory of the database file and kura_linux_user with the Linux user that is executing Eclipse Kura (e.g. kurad):

                      export TARGET=\"$(readlink -f database_parent_directory)\"\nexport KURA_USER=\"kura_linux_user\"\nsudo -u \"${KURA_USER}\" sh -c \"touch '${TARGET}/.testfile' && rm '${TARGET}/.testfile'\"\n

                      If command fails it may be necessary to change the database directory or adjust the permissions.

                      • User: Specifies the user for the database connection. Furthermore

                      • Password: Specifies the password. The default password is the empty string.

                      • Checkpoint interval (seconds): H2DbService instances support running periodic checkpoints to ensure data consistency. This parameter specifies the interval in seconds between two successive checkpoints. This setting has no effect for in-memory database instances.

                      • Defrag interval (minutes): H2DbService instances support running periodic defragmentation (compaction). This parameter specifies the interval in minutes between two successive checkpoints, set to zero to disable. This setting has no effect for in-memory database instances. Existing database connections will be closed during the defragmentation process and need to be reopened by the applications.

                      • Connection pool max size: The H2DbService manages connections using a connection pool. This parameter defines the maximum number of connections for the pool

                      "},{"location":"core-services/h2db-service/#selecting-a-database-instance-for-existing-components","title":"Selecting a database instance for existing components","text":"

                      A database instance is identified by its Kura service PID. The PID for the default instance is org.eclipse.kura.db.H2DbService while the PID for instances created using the Web UI is the string entered in the Name field at step 2 of the previous section.

                      The built-in components that use database functionalities allow to specify which instance to use in their configuration. These components are the DataService component of the cloud stack, the DbWireRecordFilter and DbWireRecordStore wire components. The configuration of each component contains a property that allows to specify the service PID of the desired instance.

                      "},{"location":"core-services/h2db-service/#usage-through-wires","title":"Usage through Wires","text":"

                      It is possible to store and extract Wire Records into/from a H2 database instance using the Wire Record Store and Wire Record Query wire components.

                      When a Wire Record is received by a Wire Record Store attached to a H2 based database instance, the data will be stored in a table whose name is the current value of the Record Collection Name configuration parameter of the Wire Component.

                      Each property contained in a Wire Record will be appended to a column with the same name as the property key. A new column will be created if it doesn't already exists.

                      Note

                      Storing wire record properties with the FLOAT data type using the Wire Record Store is not recommended since the type information will be lost. Values inserted as FLOAT using the Wire Record Store will be retrieved as DOUBLE using the Wire Record Query component.

                      Warning

                      It is not recommended to store Wire Records having properties with the same key and different value type. If the value type changes, the target column will be dropped and recreated with the type derived from the last received record. All existing data in the target column will be lost. The purpose of this is to allow changing the type of a column with a Wire Graph configuration update.

                      "},{"location":"core-services/h2db-service/#enabling-the-tcp-server","title":"Enabling the TCP Server","text":"

                      Danger

                      This feature is intended to be used only for debugging/development purposes. The server created by H2 is not running on a secure protocol. Only enable the server for a limited time and make sure to properly secure the firewall ports on which it is running.

                      The TCP server can be used by creating a H2DbServer instance:

                      1. Open the Web UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
                      2. Select org.eclipse.kura.core.db.H2DbServer from the Factory drop-down\u200b list, enter an arbitrary name for the new instance and click Apply.
                      3. Clicking on the name of the new server instance on the left side of the Web UI\u200b. The configuration of the server component will appear.
                      4. Set the db.server.type field to TCP.
                      5. Review the server options under db.server.commandline, check the official documentation for more information about the available options.
                      6. Set the db.server.enabled to true.

                      The server, with the default configuration, will be listening on port 9123.

                      Tip

                      Make sure to review the firewall configuration in order to ensure that the server is reachable from an external process.

                      "},{"location":"core-services/h2db-service/#enabling-the-web-console","title":"Enabling the Web Console","text":"

                      Danger

                      This feature is intended to be used only for debugging/development purposes. The server created by H2 is not running on a secure protocol. Only enable the server for a limited time and make sure to properly secure the firewall ports on which it is running.

                      In order to enable the H2 Web console, proceed as follows:

                      1. Create a new H2DbServer instance.
                      2. Set the db.server.type field to WEB.
                      3. Enter appropriate parameters for the Web server in the db.server.commandline field. An example of valid settings can be: -webPort 9123 -webAllowOthers -ifExists -webExternalNames <device-ip>.
                      4. Set the db.server.enabled to true.

                      The server is now listening on the specified port.

                      Tip

                      Make sure to review the firewall configuration in order to ensure that the server is reachable from an external process.

                      Use a browser to access the console. Open the http://: URL, where is the IP address of the gateway and is the port specified at step 3.

                      Enter the DB URL as specified in the Kura configuration in the JDBC URL field and the credentials. Click on Connect, you should be able to access the console.

                      "},{"location":"core-services/h2db-service/#change-the-database-password","title":"Change the Database Password","text":"

                      To change the database password the System Administrator needs to:

                      1. Open the configuration of the desired database instance in the Web UI.
                      2. Enter the new password in the db.password field.
                      3. Click Apply.

                      Warn

                      If the H2DbServer instance fails to open a database, it will delete and recreate all database files. This behavior\u200b is aimed at preventing potential issues caused by incorrect credentials in the configuration snapshots. It is highly recommended to perform a backup of an existing database before trying to open it using a H2DbService instance and before changing the password.

                      "},{"location":"core-services/h2db-service/#persistence-modes","title":"Persistence Modes","text":"

                      The H2 database supports several persistence modes.

                      "},{"location":"core-services/h2db-service/#in-memory","title":"In Memory","text":"

                      An in-memory database instance can be created using the following URL structure: jdbc:h2:mem:, where is a non-empty string that represents the database name. This configuration is suggested for database instances that are frequently updated. Examples:

                      • jdbc:h2:mem:kuradb
                      • jdbc:h2:mem:mydb

                      The default database instance is in-memory by default and uses the jdbc:h2:mem:kuradb URL.

                      "},{"location":"core-services/h2db-service/#persistent","title":"Persistent","text":"

                      A persistent database instance can be created using the jdbc:h2:file:, where is a non-empty string that represents the database path.

                      If no URL parameters are supplied the database will enable the transaction log by default. The transaction log is used to restore the database to a consistent state after a crash or power failure. This provides good protection against data losses but causes a lot of writes to the storage device, reducing both performance and the lifetime of flash-based storage devices.

                      Examples: - jdbc:h2:file:/opt/db/mydb

                      Make sure to use absolute paths in the DB URL since H2 does not support DB paths relative to the working directory.

                      "},{"location":"core-services/introduction/","title":"Introduction","text":"

                      This section describes the administrative tools available using the Gateway Administration Console. This web interface provides the ability to configure all services and applications that are installed and running on the gateway.

                      "},{"location":"core-services/network-status-rest-v1/","title":"Network Status Service V1 REST APIs and MQTT Request Handler","text":"

                      The NET-STATUS-V1 cloud request handler and the corresponding REST APIs allow to retrieve the current status of the network interfaces available on the system.

                      This cloud request handler and rest API is available only on the systems that provide a NetworkStatusService implementation, at the moment this corresponds to the devices that include the NetworkManager integration.

                      Accessing the REST APIs requires to use an identity with the rest.network.status permission assigned.

                      • Request definitions
                        • GET/interfaceIds
                        • GET/status
                        • POST/status/byInterfaceId
                      • JSON definitions
                        • InterfaceIds
                        • InterfaceStatusList
                        • LoopbackInterfaceStatus
                        • EthernetInterfaceStatus
                        • WifiInterfaceStatus
                        • ModemInterfaceStatus
                        • NetworkInterfaceStatus
                        • IPAddressString
                        • HardwareAddress
                        • NetworkInterfaceIpAddress
                        • NetworkInterfaceIpAddressStatus
                        • WifiChannel
                        • WifiAccessPoint
                        • ModemModePair
                        • Sim
                        • Bearer
                        • ModemPorts
                        • NetworkInterfaceType
                        • NetworkInterfaceState
                        • WifiCapability
                        • WifiMode
                        • WifiSecurity
                        • ModemPortType
                        • ModemCapability
                        • ModemMode
                        • ModemBand
                        • SimType
                        • ESimStatus
                        • BearerIpType
                        • ModemConnectionType
                        • ModemConnectionStatus
                        • AccessTechnology
                        • RegistrationStatus
                        • FailureReport
                        • GenericFailureReport
                      "},{"location":"core-services/network-status-rest-v1/#request-definitions","title":"Request definitions","text":""},{"location":"core-services/network-status-rest-v1/#getinterfaceids","title":"GET/interfaceIds","text":"
                      • REST API path : /services/networkStatus/v1/interfaceIds
                      • description : Returns the identifiers of the network interfaces detected in the system. For Ethernet and WiFi interfaces, the identifier is typically the interface name. For the modems, instead, it is the usb or pci path.
                      • responses :
                        • 200
                          • description : The list of the identifiers of the network interfaces detected in the system.
                          • response body :
                            • InterfaceIds
                        • 500
                          • description : If an unexpected failure occurs while retrieving the interface list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"core-services/network-status-rest-v1/#getstatus","title":"GET/status","text":"
                      • REST API path : /services/networkStatus/v1/status
                      • description : Returns the status for all interfaces currently available on the system. Failures in retrieving the status of specific interfaces will be reported using the failures field of the response.
                      • responses :
                        • 200
                          • description : The status of the network interfaces in the system.
                          • response body :
                            • InterfaceStatusList
                        • 500
                          • description : If an unexpected failure occurs while retrieving the interface list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"core-services/network-status-rest-v1/#poststatusbyinterfaceid","title":"POST/status/byInterfaceId","text":"
                      • REST API path : /services/networkStatus/v1/status/byInterfaceId
                      • description : Returns the status for the network interfaces whose id is specified in the request. Failures in retrieving the status of specific interfaces, or the fact that an interface with the requested id cannot be found will be reported using the failures field of the responses.
                      • request body :
                        • InterfaceIds
                      • responses :
                        • 200
                          • description : The status of the network interfaces in the system.
                          • response body :
                            • InterfaceStatusList
                        • 400
                          • description : If the request object does not contain the interfaceIds field, it is null or if it contains empty or null interface ids.
                          • response body :
                            • GenericFailureReport
                        • 500
                          • description : If an unexpected failure occurs while retrieving the interface list.
                          • response body :
                            • GenericFailureReport
                      "},{"location":"core-services/network-status-rest-v1/#json-definitions","title":"JSON definitions","text":""},{"location":"core-services/network-status-rest-v1/#interfaceids","title":"InterfaceIds","text":"

                      An object containing a list of network interface identifiers

                      Properties:

                      • interfaceIds: array The list of network interface identifiers

                        • array element type: string An interface identifier
                      {\n  \"interfaceIds\": [\n    \"lo\"\n  ]\n}\n
                      "},{"location":"core-services/network-status-rest-v1/#interfacestatuslist","title":"InterfaceStatusList","text":"

                      An object reporting a list of network interface status. If a failure occurs retrieving the status for a specific interface, the reason will be reported in the failures list.

                      Properties:

                      • interfaces: array A list of network interface status

                        • array element type: variant
                          • Variants:
                            • object
                              • LoopbackInterfaceStatus
                            • object
                              • EthernetInterfaceStatus
                            • object
                              • WifiInterfaceStatus
                            • object
                              • ModemInterfaceStatus
                      • failures: array

                        • array element type: object
                          • FailureReport

                      {\n  \"failures\": [],\n  \"interfaces\": [\n    {\n      \"autoConnect\": true,\n      \"driver\": \"unknown\",\n      \"driverVersion\": \"\",\n      \"firmwareVersion\": \"\",\n      \"hardwareAddress\": \"00:00:00:00:00:00\",\n      \"id\": \"lo\",\n      \"interfaceIp4Addresses\": {\n        \"addresses\": [\n          {\n            \"address\": \"127.0.0.1\",\n            \"prefix\": 8\n          }\n        ],\n        \"dnsServerAddresses\": []\n      },\n      \"interfaceName\": \"lo\",\n      \"mtu\": 65536,\n      \"state\": \"UNMANAGED\",\n      \"type\": \"LOOPBACK\",\n      \"virtual\": true\n    }\n  ]\n}\n
                      {\n  \"failures\": [\n    {\n      \"interfaceId\": \"foo\",\n      \"reason\": \"Not found.\"\n    }\n  ],\n  \"interfaces\": []\n}\n

                      "},{"location":"core-services/network-status-rest-v1/#loopbackinterfacestatus","title":"LoopbackInterfaceStatus","text":"

                      Object that contains specific properties to describe the status of an Loopback interface. It contains also all of the properties specified by NetworkInterfaceStatus.

                      Properties:

                      {\n  \"autoConnect\": true,\n  \"driver\": \"unknown\",\n  \"driverVersion\": \"\",\n  \"firmwareVersion\": \"\",\n  \"hardwareAddress\": \"00:00:00:00:00:00\",\n  \"id\": \"lo\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"127.0.0.1\",\n        \"prefix\": 8\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"lo\",\n  \"mtu\": 65536,\n  \"state\": \"UNMANAGED\",\n  \"type\": \"LOOPBACK\",\n  \"virtual\": true\n}\n
                      "},{"location":"core-services/network-status-rest-v1/#ethernetinterfacestatus","title":"EthernetInterfaceStatus","text":"

                      Object that contains specific properties to describe the status of an Ethernet interface. It contains also all of the properties specified by NetworkInterfaceStatus.

                      Properties:

                      • linkUp: bool
                      {\n  \"autoConnect\": true,\n  \"driver\": \"igb\",\n  \"driverVersion\": \"5.6.0-k\",\n  \"firmwareVersion\": \"3.25, 0x800005d0\",\n  \"hardwareAddress\": \"00:E0:C7:0A:5F:89\",\n  \"id\": \"eno1\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"172.16.0.1\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": [],\n    \"gateway\": \"0.0.0.0\"\n  },\n  \"interfaceName\": \"eno1\",\n  \"linkUp\": true,\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"ETHERNET\",\n  \"virtual\": false\n}\n
                      "},{"location":"core-services/network-status-rest-v1/#wifiinterfacestatus","title":"WifiInterfaceStatus","text":"

                      Object that contains specific properties to describe the status of a WiFi interface. It contains also all of the properties specified by NetworkInterfaceStatus.

                      Properties:

                      • capabilities: array

                        • array element type: string (enumerated)
                          • WifiCapability
                      • channels: array

                        • array element type: object
                          • WifiChannel
                      • countryCode: string

                      • mode: string (enumerated)

                        • WifiMode
                      • activeWifiAccessPoint: object (optional)

                        • WifiAccessPoint
                      • availableWifiAccessPoints: array

                        • array element type: object
                          • WifiAccessPoint

                      {\n  \"activeWifiAccessPoint\": {\n    \"channel\": {\n      \"channel\": 11,\n      \"frequency\": 2462\n    },\n    \"hardwareAddress\": \"11:22:33:44:55:66\",\n    \"maxBitrate\": 130000,\n    \"mode\": \"INFRA\",\n    \"rsnSecurity\": [\n      \"GROUP_CCMP\",\n      \"KEY_MGMT_PSK\",\n      \"PAIR_CCMP\"\n    ],\n    \"signalQuality\": 100,\n    \"signalStrength\": -20,\n    \"ssid\": \"MyAccessPoint\",\n    \"wpaSecurity\": [\n      \"NONE\"\n    ],\n    \"flags\": [\n      \"PRIVACY\"\n    ]\n  },\n  \"autoConnect\": true,\n  \"availableWifiAccessPoints\": [\n    {\n      \"channel\": {\n        \"channel\": 11,\n        \"frequency\": 2462\n      },\n      \"hardwareAddress\": \"11:22:33:44:55:66\",\n      \"maxBitrate\": 130000,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 100,\n      \"signalStrength\": -20,\n      \"ssid\": \"MyAccessPoint\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\"\n      ]\n    },\n    {\n      \"channel\": {\n        \"channel\": 5,\n        \"frequency\": 2432\n      },\n      \"hardwareAddress\": \"22:33:44:55:66:77\",\n      \"maxBitrate\": 270000,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 42,\n      \"signalStrength\": -69,\n      \"ssid\": \"OtherSSID\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\"\n      ]\n    }\n  ],\n  \"capabilities\": [\n    \"CIPHER_WEP40\",\n    \"WPA\",\n    \"AP\",\n    \"FREQ_VALID\",\n    \"ADHOC\",\n    \"RSN\",\n    \"CIPHER_TKIP\",\n    \"CIPHER_WEP104\",\n    \"CIPHER_CCMP\",\n    \"FREQ_2GHZ\"\n  ],\n  \"channels\": [\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 1,\n      \"disabled\": false,\n      \"frequency\": 2412,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    },\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 2,\n      \"disabled\": false,\n      \"frequency\": 2417,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    }\n  ],\n  \"countryCode\": \"IT\",\n  \"driver\": \"brcmfmac\",\n  \"driverVersion\": \"7.45.98.94\",\n  \"firmwareVersion\": \"01-3b33decd\",\n  \"hardwareAddress\": \"44:55:66:77:88:99\",\n  \"id\": \"wlan0\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"192.168.0.113\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"wlan0\",\n  \"mode\": \"INFRA\",\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"WIFI\",\n  \"virtual\": false\n}\n
                      {\n  \"activeWifiAccessPoint\": {\n    \"channel\": {\n      \"channel\": 1,\n      \"frequency\": 2412\n    },\n    \"hardwareAddress\": \"44:55:66:77:88:99\",\n    \"maxBitrate\": 0,\n    \"mode\": \"INFRA\",\n    \"rsnSecurity\": [\n      \"GROUP_CCMP\",\n      \"KEY_MGMT_PSK\",\n      \"PAIR_CCMP\"\n    ],\n    \"signalQuality\": 0,\n    \"signalStrength\": -104,\n    \"ssid\": \"kura_gateway_raspberry_pi\",\n    \"wpaSecurity\": [\n      \"NONE\"\n    ],\n    \"flags\": [\n      \"PRIVACY\"\n    ]\n  },\n  \"autoConnect\": true,\n  \"availableWifiAccessPoints\": [\n    {\n      \"channel\": {\n        \"channel\": 1,\n        \"frequency\": 2412\n      },\n      \"hardwareAddress\": \"44:55:66:77:88:99\",\n      \"maxBitrate\": 0,\n      \"mode\": \"INFRA\",\n      \"rsnSecurity\": [\n        \"GROUP_CCMP\",\n        \"KEY_MGMT_PSK\",\n        \"PAIR_CCMP\"\n      ],\n      \"signalQuality\": 0,\n      \"signalStrength\": -104,\n      \"ssid\": \"kura_gateway_raspberry_pi\",\n      \"wpaSecurity\": [\n        \"NONE\"\n      ],\n      \"flags\": [\n        \"PRIVACY\",\n        \"WPS\"\n      ]\n    }\n  ],\n  \"capabilities\": [\n    \"CIPHER_WEP40\",\n    \"WPA\",\n    \"AP\",\n    \"FREQ_VALID\",\n    \"ADHOC\",\n    \"RSN\",\n    \"CIPHER_TKIP\",\n    \"CIPHER_WEP104\",\n    \"CIPHER_CCMP\",\n    \"FREQ_2GHZ\"\n  ],\n  \"channels\": [\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 1,\n      \"disabled\": false,\n      \"frequency\": 2412,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    },\n    {\n      \"attenuation\": 20.0,\n      \"channel\": 2,\n      \"disabled\": false,\n      \"frequency\": 2417,\n      \"noInitiatingRadiation\": false,\n      \"radarDetection\": false\n    }\n  ],\n  \"countryCode\": \"00\",\n  \"driver\": \"brcmfmac\",\n  \"driverVersion\": \"7.45.98.94\",\n  \"firmwareVersion\": \"01-3b33decd\",\n  \"hardwareAddress\": \"44:55:66:77:88:99\",\n  \"id\": \"wlan0\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"172.16.1.1\",\n        \"prefix\": 24\n      }\n    ],\n    \"dnsServerAddresses\": []\n  },\n  \"interfaceName\": \"wlan0\",\n  \"mode\": \"MASTER\",\n  \"mtu\": 1500,\n  \"state\": \"ACTIVATED\",\n  \"type\": \"WIFI\",\n  \"virtual\": false\n}\n

                      "},{"location":"core-services/network-status-rest-v1/#modeminterfacestatus","title":"ModemInterfaceStatus","text":"

                      Object that contains specific properties to describe the status of a Modem interface. It contains also all of the properties specified by NetworkInterfaceStatus.

                      Properties:

                      • model: string

                      • manufacturer: string

                      • serialNumber: string

                      • softwareRevision: string

                      • hardwareRevision: string

                      • primaryPort: string

                      • ports: object

                        • ModemPorts
                      • supportedModemCapabilities: array

                        • array element type: string (enumerated)
                          • ModemCapability
                      • currentModemCapabilities: array

                        • array element type: string (enumerated)
                          • ModemCapability
                      • powerState: string (enumerated)

                        • ModemPowerState
                      • supportedModes: array

                        • array element type: object
                          • ModemModePair
                      • currentModes: object

                        • ModemModePair
                      • supportedBands: array

                        • array element type: string (enumerated)
                          • ModemBand
                      • currentBands: array

                        • array element type: string (enumerated)
                          • ModemBand
                      • gpsSupported: bool

                      • availableSims: array

                        • array element type: object
                          • Sim
                      • simLocked: bool

                      • bearers: array

                        • array element type: object
                          • Bearer
                      • connectionType: string (enumerated)

                        • ModemConnectionType
                      • connectionStatus: string (enumerated)

                        • ModemConnectionStatus
                      • accessTechnologies: array

                        • array element type: string (enumerated)
                          • AccessTechnology
                      • signalQuality: number

                      • signalStrength: number

                      • registrationStatus: string (enumerated)

                        • RegistrationStatus
                      • operatorName: string

                      {\n  \"accessTechnologies\": [\n    \"LTE\"\n  ],\n  \"autoConnect\": true,\n  \"availableSims\": [\n    {\n      \"active\": true,\n      \"primary\": true,\n      \"eSimStatus\": \"UNKNOWN\",\n      \"eid\": \"\",\n      \"iccid\": \"1111111111111111111\",\n      \"imsi\": \"111111111111111\",\n      \"operatorName\": \"MyOperator\",\n      \"operatorIdentifier\": \"12345\",\n      \"simType\": \"PHYSICAL\"\n    }\n  ],\n  \"bearers\": [\n    {\n      \"apn\": \"apn.myoperator.com\",\n      \"bytesReceived\": 0,\n      \"bytesTransmitted\": 0,\n      \"connected\": true,\n      \"ipTypes\": [\n        \"IPV4\"\n      ],\n      \"name\": \"wwp11s0f2u3i4\"\n    }\n  ],\n  \"connectionStatus\": \"CONNECTED\",\n  \"connectionType\": \"DirectIP\",\n  \"currentBands\": [\n    \"DCS\",\n    \"EUTRAN_3\",\n    \"EUTRAN_19\",\n    \"EUTRAN_40\",\n    \"EUTRAN_26\",\n    \"EUTRAN_28\",\n    \"EUTRAN_41\",\n    \"UTRAN_6\",\n    \"EUTRAN_13\",\n    \"EUTRAN_25\",\n    \"EUTRAN_5\",\n    \"EUTRAN_7\",\n    \"EUTRAN_8\",\n    \"UTRAN_2\",\n    \"EUTRAN_38\",\n    \"UTRAN_1\",\n    \"EUTRAN_12\",\n    \"UTRAN_8\",\n    \"EUTRAN_18\",\n    \"UTRAN_19\",\n    \"G850\",\n    \"EUTRAN_20\",\n    \"UTRAN_5\",\n    \"EUTRAN_1\",\n    \"EUTRAN_39\",\n    \"EUTRAN_2\",\n    \"EUTRAN_4\",\n    \"EGSM\",\n    \"PCS\",\n    \"UTRAN_4\"\n  ],\n  \"currentModemCapabilities\": [\n    \"GSM_UMTS\",\n    \"LTE\"\n  ],\n  \"currentModes\": {\n    \"modes\": [\n      \"MODE_2G\",\n      \"MODE_3G\",\n      \"MODE_4G\"\n    ],\n    \"preferredMode\": \"MODE_4G\"\n  },\n  \"driver\": \"qmi_wwan, option1\",\n  \"driverVersion\": \"\",\n  \"firmwareVersion\": \"\",\n  \"gpsSupported\": true,\n  \"hardwareAddress\": \"00:00:00:00:00:00\",\n  \"hardwareRevision\": \"10000\",\n  \"id\": \"1-3\",\n  \"interfaceIp4Addresses\": {\n    \"addresses\": [\n      {\n        \"address\": \"1.2.3.4\",\n        \"prefix\": 30\n      }\n    ],\n    \"dnsServerAddresses\": [\n      \"1.2.3.6\",\n      \"1.2.3.7\"\n    ],\n    \"gateway\": \"1.2.3.5\"\n  },\n  \"interfaceName\": \"wwp11s0f2u3i4\",\n  \"manufacturer\": \"QUALCOMM INCORPORATED\",\n  \"model\": \"QUECTEL Mobile Broadband Module\",\n  \"mtu\": 1500,\n  \"operatorName\": \"MyOperator\",\n  \"ports\": {\n    \"cdc-wdm0\": \"QMI\",\n    \"ttyUSB0\": \"QCDM\",\n    \"ttyUSB1\": \"GPS\",\n    \"ttyUSB2\": \"AT\",\n    \"ttyUSB3\": \"AT\",\n    \"wwp11s0f2u3i4\": \"NET\"\n  },\n  \"powerState\": \"ON\",\n  \"primaryPort\": \"cdc-wdm0\",\n  \"registrationStatus\": \"HOME\",\n  \"serialNumber\": \"111111111111111\",\n  \"signalQuality\": 55,\n  \"signalStrength\": -80,\n  \"simLocked\": true,\n  \"softwareRevision\": \"EG25GGBR07A08M2G\",\n  \"state\": \"ACTIVATED\",\n  \"supportedBands\": [\n    \"DCS\",\n    \"EUTRAN_3\",\n    \"EUTRAN_19\",\n    \"EUTRAN_40\",\n    \"EUTRAN_26\",\n    \"EUTRAN_28\",\n    \"EUTRAN_41\",\n    \"UTRAN_6\",\n    \"EUTRAN_13\",\n    \"EUTRAN_25\",\n    \"EUTRAN_5\",\n    \"EUTRAN_7\",\n    \"EUTRAN_8\",\n    \"UTRAN_2\",\n    \"EUTRAN_38\",\n    \"UTRAN_1\",\n    \"EUTRAN_12\",\n    \"UTRAN_8\",\n    \"EUTRAN_18\",\n    \"UTRAN_19\",\n    \"G850\",\n    \"EUTRAN_20\",\n    \"UTRAN_5\",\n    \"EUTRAN_1\",\n    \"EUTRAN_39\",\n    \"EUTRAN_2\",\n    \"EUTRAN_4\",\n    \"EGSM\",\n    \"PCS\",\n    \"UTRAN_4\"\n  ],\n  \"supportedModemCapabilities\": [\n    \"NONE\"\n  ],\n  \"supportedModes\": [\n    {\n      \"modes\": [\n        \"MODE_4G\"\n      ],\n      \"preferredMode\": \"NONE\"\n    },\n    {\n      \"modes\": [\n        \"MODE_2G\",\n        \"MODE_3G\",\n        \"MODE_4G\"\n      ],\n      \"preferredMode\": \"MODE_4G\"\n    }\n  ],\n  \"type\": \"MODEM\",\n  \"virtual\": false\n}\n
                      "},{"location":"core-services/network-status-rest-v1/#networkinterfacestatus","title":"NetworkInterfaceStatus","text":"

                      This object contains the common properties contained by the status object reported for a network interface. A network interface is identified by Kura using the id field. It is used to internally manage the interface. The interfaceName, instead, is the IP interface as it may appear on the system. For Ethernet and WiFi interfaces the two values coincide (i.e. eth0, wlp1s0, ...). For modems, instead, the id is typically the usb or pci path, while the interfaceName is the IP interface created when they are connected. When the modem is disconnected the interfaceName can have a different value.

                      Properties:

                      • id: string

                      • interfaceName: string

                      • hardwareAddress: string

                        • HardwareAddress
                      • driver: string

                      • driverVersion: string

                      • firmwareVersion: string

                      • virtual: bool

                      • state: string (enumerated)

                        • NetworkInterfaceState
                      • autoConnect: bool

                      • mtu: number

                      • interfaceIp4Addresses: array (optional)

                        • array element type: object
                          • NetworkInterfaceIpAddressStatus
                      • interfaceIp6Addresses: array (optional)

                        • array element type: object
                          • NetworkInterfaceIpAddressStatus
                      "},{"location":"core-services/network-status-rest-v1/#ipaddressstring","title":"IPAddressString","text":"

                      Represents an IPv4 or IPv6 addresses as a string.

                      "},{"location":"core-services/network-status-rest-v1/#hardwareaddress","title":"HardwareAddress","text":"

                      Represents an hardware address as its bytes reported as two characters hexadecimal strings separated by the ':' character. e.g. 00:11:22:33:A4:FC:BB.

                      "},{"location":"core-services/network-status-rest-v1/#networkinterfaceipaddress","title":"NetworkInterfaceIpAddress","text":"

                      This object describes an IP address with its prefix. It can be used for IPv4 or IPv6 addresses.

                      Properties:

                      • address: string

                        • IPAddressString
                      • prefix: number

                      "},{"location":"core-services/network-status-rest-v1/#networkinterfaceipaddressstatus","title":"NetworkInterfaceIpAddressStatus","text":"

                      This class describes the IP address status of a network interface: a list of IP addresses, an optional gateway and a list of DNS servers address. It can be used for IPv4 or IPv6 addresses.

                      Properties:

                      • addresses: array

                        • array element type: object
                          • NetworkInterfaceIpAddress
                      • gateway: string (optional This field can be missing if the interface has no gateway.)

                        • IPAddressString
                      • dnsServerAddresses: array

                        • array element type: string
                          • IPAddressString
                      "},{"location":"core-services/network-status-rest-v1/#wifichannel","title":"WifiChannel","text":"

                      This class represent a WiFi channel, providing the channel number, frequency, status and other useful information.

                      Properties:

                      • channel: number

                      • frequency: number

                      • disabled: bool (optional)

                      • attenuation: number (optional)

                      • noInitiatingRadiation: bool (optional)

                      • radarDetection: bool (optional)

                      "},{"location":"core-services/network-status-rest-v1/#wifiaccesspoint","title":"WifiAccessPoint","text":"

                      This object describes a Wifi Access Point. It can be used both for describing a detected AP after a WiFi scan when in Station mode and the provided AP when in Master (or Access Point) mode.

                      Properties:

                      • ssid: string The Service Set IDentifier of the WiFi network

                      • hardwareAddress: string

                        • HardwareAddress
                      • channel: object

                        • WifiChannel
                      • mode: string (enumerated)

                        • WifiMode
                      • maxBitrate: number The maximum bitrate this access point is capable of.

                      • signalQuality: number The current signal quality of the access point in percentage.

                      • signalStrength: number The current signal strength of the access point in dBm.

                      • wpaSecurity: array The WPA capabilities of the access point

                        • array element type: string (enumerated)
                          • WifiSecurity
                      • rsnSecurity: array The RSN capabilities of the access point

                        • array element type: string (enumerated)
                          • WifiSecurity
                      • flags: array The capabilities of the access point

                        • array element type: string (enumerated)
                          • WifiFlags
                      "},{"location":"core-services/network-status-rest-v1/#modemmodepair","title":"ModemModePair","text":"

                      This object represents a pair of Modem Mode list and a preferred one.

                      Properties:

                      • modes: array

                        • array element type: string (enumerated)
                          • ModemMode
                      • preferredMode: string (enumerated)

                        • ModemMode
                      "},{"location":"core-services/network-status-rest-v1/#sim","title":"Sim","text":"

                      This class contains all relevant properties to describe a SIM (Subscriber Identity Module).

                      Properties:

                      • active: bool

                      • primary: bool

                      • iccid: string

                      • imsi: string

                      • eid: string

                      • operatorName: string

                      • operatorIdentifier: string

                      • simType: string (enumerated)

                        • SimType
                      • eSimStatus: string (enumerated)

                        • ESimStatus
                      "},{"location":"core-services/network-status-rest-v1/#bearer","title":"Bearer","text":"

                      This object describes the Bearer or Context associated to a modem connection.

                      Properties:

                      • name: string

                      • connected: bool

                      • apn: string

                      • ipTypes: array

                        • array element type: string (enumerated)
                          • BearerIpType
                      • bytesTransmitted: number

                      • bytesReceived: number

                      "},{"location":"core-services/network-status-rest-v1/#modemports","title":"ModemPorts","text":"

                      An object representing a set of modem ports. The members of this object represent modem ports, the member names represent the modem port name. This object can have a variable number of members.

                      Properties:

                      • propertyName: string (enumerated)
                        • ModemPortType
                      "},{"location":"core-services/network-status-rest-v1/#networkinterfacetype","title":"NetworkInterfaceType","text":"

                      The type of a network interface.

                      • Possible values
                        • UNKNOWN: The device type is unknown
                        • ETHERNET: The device is a wired Ethernet device.
                        • WIFI: The device is an 802.11 WiFi device.
                        • UNUSED1
                        • UNUSED2
                        • BT: The device is a Bluetooth device.
                        • OLPC_MESH: The device is an OLPC mesh networking device.
                        • WIMAX: The device is an 802.16e Mobile WiMAX device.
                        • MODEM: The device is a modem supporting one or more of analog telephone, CDMA/EVDO, GSM/UMTS/HSPA, or LTE standards to access a cellular or wireline data network.
                        • INFINIBAND: The device is an IP-capable InfiniBand interface.
                        • BOND: The device is a bond master interface.
                        • VLAN: The device is a VLAN interface.
                        • ADSL: The device is an ADSL device.
                        • BRIDGE: The device is a bridge master interface.
                        • GENERIC: This is a generic support for unrecognized device types.
                        • TEAM: The device is a team master interface.
                        • TUN: The device is a TUN or TAP interface.
                        • TUNNEL: The device is an IP tunnel interface.
                        • MACVLAN: The device is a MACVLAN interface.
                        • VXLAN: The device is a VXLAN interface.
                        • VETH: The device is a VETH interface.
                        • MACSEC: The device is a MACsec interface.
                        • DUMMY: The device is a dummy interface.
                        • PPP: The device is a PPP interface.
                        • OVS_INTERFACE: The device is a Open vSwitch interface.
                        • OVS_PORT: The device is a Open vSwitch port
                        • OVS_BRIDGE: The device is a Open vSwitch bridge.
                        • WPAN: The device is a IEEE 802.15.4 (WPAN) MAC Layer Device.
                        • SIXLOWPAN: The device is a 6LoWPAN interface.
                        • WIREGUARD: The device is a WireGuard interface.
                        • WIFI_P2P: The device is an 802.11 Wi-Fi P2P device.
                        • VRF: The device is a VRF (Virtual Routing and Forwarding) interface.
                        • LOOPBACK: The device is a loopback device.
                      "},{"location":"core-services/network-status-rest-v1/#networkinterfacestate","title":"NetworkInterfaceState","text":"

                      The state of a network interface.

                      • Possible values
                        • UNKNOWN: The device is in an unknown state.
                        • UNMANAGED: The device cannot be used (carrier off, rfkill, etc).
                        • UNAVAILABLE: The device is not connected.
                        • DISCONNECTED: The device is preparing to connect.
                        • PREPARE: The device is being configured.
                        • CONFIG: The device is awaiting secrets necessary to continue connection.
                        • NEED_AUTH: The IP settings of the device are being requested and configured.
                        • IP_CONFIG: The device's IP connectivity ability is being determined.
                        • IP_CHECK: The device is waiting for secondary connections to be activated.
                        • SECONDARIES: The device is waiting for secondary connections to be activated.
                        • ACTIVATED: The device is active.
                        • DEACTIVATING: The device's network connection is being turn down.
                        • FAILED: The device is in a failure state following an attempt to activate it.
                      "},{"location":"core-services/network-status-rest-v1/#wificapability","title":"WifiCapability","text":"

                      The capability of a WiFi interface.

                      • Possible values
                        • NONE: The device has no encryption/authentication capabilities
                        • CIPHER_WEP40: The device supports 40/64-bit WEP encryption.
                        • CIPHER_WEP104: The device supports 104/128-bit WEP encryption.
                        • CIPHER_TKIP: The device supports the TKIP encryption.
                        • CIPHER_CCMP: The device supports the AES/CCMP encryption.
                        • WPA: The device supports the WPA1 encryption/authentication protocol.
                        • RSN: The device supports the WPA2/RSN encryption/authentication protocol.
                        • AP: The device supports Access Point mode.
                        • ADHOC: The device supports Ad-Hoc mode.
                        • FREQ_VALID: The device reports frequency capabilities.
                        • FREQ_2GHZ: The device supports 2.4GHz frequencies.
                        • FREQ_5GHZ: The device supports 5GHz frequencies.
                        • MESH: The device supports mesh points.
                        • IBSS_RSN: The device supports WPA2 in IBSS networks
                      "},{"location":"core-services/network-status-rest-v1/#wifimode","title":"WifiMode","text":"

                      Modes of operation for wifi interfaces

                      • Possible values
                        • UNKNOWN: Mode is unknown.
                        • ADHOC: Uncoordinated network without central infrastructure.
                        • INFRA: Client mode - Coordinated network with one or more central controllers.
                        • MASTER: Access Point Mode - Coordinated network with one or more central controllers.
                      "},{"location":"core-services/network-status-rest-v1/#wifisecurity","title":"WifiSecurity","text":"

                      Flags describing the security capabilities of an access point.

                      • Possible values
                        • NONE: None
                        • PAIR_WEP40: Supports pairwise 40-bit WEP encryption.
                        • PAIR_WEP104: Supports pairwise 104-bit WEP encryption.
                        • PAIR_TKIP: Supports pairwise TKIP encryption.
                        • PAIR_CCMP: Supports pairwise CCMP encryption.
                        • GROUP_WEP40: Supports a group 40-bit WEP cipher.
                        • GROUP_WEP104: Supports a group 104-bit WEP cipher.
                        • GROUP_TKIP: Supports a group TKIP cipher.
                        • GROUP_CCMP: Supports a group CCMP cipher.
                        • KEY_MGMT_PSK: Supports PSK key management.
                        • KEY_MGMT_802_1X: Supports 802.1x key management.
                        • SECURITY_NONE: Supports no encryption.
                        • SECURITY_WEP: Supports WEP encryption.
                        • SECURITY_WPA: Supports WPA encryption.
                        • SECURITY_WPA2: Supports WPA2 encryption.
                        • SECURITY_WPA_WPA2: Supports WPA and WPA2 encryption.
                      "},{"location":"core-services/network-status-rest-v1/#wififlags","title":"WifiFlags","text":"

                      Flags describing the capabilities of an access point.

                      • Possible values
                        • NONE: None
                        • PRIVACY: supports authentication and encryption
                        • WPS: supports WPS (Wi-Fi Protected Setup)
                        • WPS_PBC: supports push-button based WPS
                        • WPS_PIN: supports PIN based WPS
                      "},{"location":"core-services/network-status-rest-v1/#modemporttype","title":"ModemPortType","text":"

                      The type of a modem port.

                      • Possible values
                        • UNKNOWN
                        • NET
                        • AT
                        • QCDM
                        • GPS
                        • QMI
                        • MBIM
                        • AUDIO
                        • IGNORED
                      "},{"location":"core-services/network-status-rest-v1/#modemcapability","title":"ModemCapability","text":"

                      The generic access technologies families supported by a modem.

                      • Possible values
                        • NONE: The modem has no capabilities.
                        • POTS: The modem supports the Plain Old Telephone Service (analog wired telephone network).
                        • EVDO: The modem supports EVDO revision 0, A or B.
                        • GSM_UMTS: The modem supports at least one of GSM, GPRS, EDGE, UMTS, HSDPA, HSUPA or HSPA+ technologies.
                        • LTE: The modem has LTE capabilities.
                        • IRIDIUM: The modem supports Iridium technology.
                        • FIVE_GNR: The modem supports 5GNR.
                        • TDS: The modem supports TDS.
                        • ANY: The modem supports all capabilities.
                      "},{"location":"core-services/network-status-rest-v1/#modemmode","title":"ModemMode","text":"

                      The generic access mode a modem supports.

                      • Possible values
                        • NONE
                        • CS
                        • MODE_2G
                        • MODE_3G
                        • MODE_4G
                        • MODE_5G
                        • ANY
                      "},{"location":"core-services/network-status-rest-v1/#modemband","title":"ModemBand","text":"

                      The radio bands supported by a modem when connected to a mobile network.

                      • Possible values
                        • UNKNOWN
                        • EGSM
                        • DCS
                        • PCS
                        • G850
                        • UTRAN_1
                        • UTRAN_3
                        • UTRAN_4
                        • UTRAN_6
                        • UTRAN_5
                        • UTRAN_8
                        • UTRAN_9
                        • UTRAN_2
                        • UTRAN_7
                        • G450
                        • G480
                        • G750
                        • G380
                        • G410
                        • G710
                        • G810
                        • EUTRAN_1
                        • EUTRAN_2
                        • EUTRAN_3
                        • EUTRAN_4
                        • EUTRAN_5
                        • EUTRAN_6
                        • EUTRAN_7
                        • EUTRAN_8
                        • EUTRAN_9
                        • EUTRAN_10
                        • EUTRAN_11
                        • EUTRAN_12
                        • EUTRAN_13
                        • EUTRAN_14
                        • EUTRAN_17
                        • EUTRAN_18
                        • EUTRAN_19
                        • EUTRAN_20
                        • EUTRAN_21
                        • EUTRAN_22
                        • EUTRAN_23
                        • EUTRAN_24
                        • EUTRAN_25
                        • EUTRAN_26
                        • EUTRAN_27
                        • EUTRAN_28
                        • EUTRAN_29
                        • EUTRAN_30
                        • EUTRAN_31
                        • EUTRAN_32
                        • EUTRAN_33
                        • EUTRAN_34
                        • EUTRAN_35
                        • EUTRAN_36
                        • EUTRAN_37
                        • EUTRAN_38
                        • EUTRAN_39
                        • EUTRAN_40
                        • EUTRAN_41
                        • EUTRAN_42
                        • EUTRAN_43
                        • EUTRAN_44
                        • EUTRAN_45
                        • EUTRAN_46
                        • EUTRAN_47
                        • EUTRAN_48
                        • EUTRAN_49
                        • EUTRAN_50
                        • EUTRAN_51
                        • EUTRAN_52
                        • EUTRAN_53
                        • EUTRAN_54
                        • EUTRAN_55
                        • EUTRAN_56
                        • EUTRAN_57
                        • EUTRAN_58
                        • EUTRAN_59
                        • EUTRAN_60
                        • EUTRAN_61
                        • EUTRAN_62
                        • EUTRAN_63
                        • EUTRAN_64
                        • EUTRAN_65
                        • EUTRAN_66
                        • EUTRAN_67
                        • EUTRAN_68
                        • EUTRAN_69
                        • EUTRAN_70
                        • EUTRAN_71
                        • EUTRAN_85
                        • CDMA_BC0
                        • CDMA_BC1
                        • CDMA_BC2
                        • CDMA_BC3
                        • CDMA_BC4
                        • CDMA_BC5
                        • CDMA_BC6
                        • CDMA_BC7
                        • CDMA_BC8
                        • CDMA_BC9
                        • CDMA_BC10
                        • CDMA_BC11
                        • CDMA_BC12
                        • CDMA_BC13
                        • CDMA_BC14
                        • CDMA_BC15
                        • CDMA_BC16
                        • CDMA_BC17
                        • CDMA_BC18
                        • CDMA_BC19
                        • UTRAN_10
                        • UTRAN_11
                        • UTRAN_12
                        • UTRAN_13
                        • UTRAN_14
                        • UTRAN_19
                        • UTRAN_20
                        • UTRAN_21
                        • UTRAN_22
                        • UTRAN_25
                        • UTRAN_26
                        • UTRAN_32
                        • ANY
                        • NGRAN_1
                        • NGRAN_2
                        • NGRAN_3
                        • NGRAN_5
                        • NGRAN_7
                        • NGRAN_8
                        • NGRAN_12
                        • NGRAN_13
                        • NGRAN_14
                        • NGRAN_18
                        • NGRAN_20
                        • NGRAN_25
                        • NGRAN_26
                        • NGRAN_28
                        • NGRAN_29
                        • NGRAN_30
                        • NGRAN_34
                        • NGRAN_38
                        • NGRAN_39
                        • NGRAN_40
                        • NGRAN_41
                        • NGRAN_48
                        • NGRAN_50
                        • NGRAN_51
                        • NGRAN_53
                        • NGRAN_65
                        • NGRAN_66
                        • NGRAN_70
                        • NGRAN_71
                        • NGRAN_74
                        • NGRAN_75
                        • NGRAN_76
                        • NGRAN_77
                        • NGRAN_78
                        • NGRAN_79
                        • NGRAN_80
                        • NGRAN_81
                        • NGRAN_82
                        • NGRAN_83
                        • NGRAN_84
                        • NGRAN_86
                        • NGRAN_89
                        • NGRAN_90
                        • NGRAN_91
                        • NGRAN_92
                        • NGRAN_93
                        • NGRAN_94
                        • NGRAN_95
                        • NGRAN_257
                        • NGRAN_258
                        • NGRAN_260
                        • NGRAN_261
                      "},{"location":"core-services/network-status-rest-v1/#simtype","title":"SimType","text":"

                      The SIM (Subscriber Identity Module) type.

                      • Possible values
                        • UNKNOWN
                        • PHYSICAL
                        • ESIM
                      "},{"location":"core-services/network-status-rest-v1/#esimstatus","title":"ESimStatus","text":"

                      The status of an ESIM.

                      • Possible values
                        • UNKNOWN
                        • NO_PROFILES
                        • WITH_PROFILES
                      "},{"location":"core-services/network-status-rest-v1/#beareriptype","title":"BearerIpType","text":"

                      The type of Bearer or Context associated to a modem connection.

                      • Possible values
                        • NONE
                        • IPV4
                        • IPV6
                        • IPV4V6
                        • NON_IP
                        • ANY
                      "},{"location":"core-services/network-status-rest-v1/#modemconnectiontype","title":"ModemConnectionType","text":"
                      • Possible values
                        • PPP: Point to Point Protocol
                        • DirectIP: Direct IP
                      "},{"location":"core-services/network-status-rest-v1/#modemconnectionstatus","title":"ModemConnectionStatus","text":"

                      The status of a modem.

                      • Possible values
                        • FAILED: The modem is unavailable
                        • UNKNOWN: The modem is in an unknown state.
                        • INITIALIZING: The modem is being initialised.
                        • LOCKED: The modem is locked.
                        • DISABLED: The modem is disabled and powered off.
                        • DISABLING: The modem is disabling.
                        • ENABLING: The modem is enabling..
                        • ENABLED: The modem is enabled but not registered to a network provider.
                        • SEARCHING: The modem is searching for a network provider.
                        • REGISTERED: The modem is registered to a network provider.
                        • DISCONNECTING: The modem is disconnecting.
                        • CONNECTING: The modem is connecting.
                        • CONNECTED: The modem is connected.
                      "},{"location":"core-services/network-status-rest-v1/#accesstechnology","title":"AccessTechnology","text":"

                      The specific technology types used when a modem is connected or registered to a network.

                      • Possible values
                        • UNKNOWN
                        • POTS
                        • GSM
                        • GSM_COMPACT
                        • GPRS
                        • EDGE
                        • UMTS
                        • HSDPA
                        • HSUPA
                        • HSPA
                        • HSPA_PLUS
                        • ONEXRTT
                        • EVDO0
                        • EVDOA
                        • EVDOB
                        • LTE
                        • FIVEGNR
                        • LTE_CAT_M
                        • LTE_NB_IOT
                        • ANY
                      "},{"location":"core-services/network-status-rest-v1/#registrationstatus","title":"RegistrationStatus","text":"

                      The registration status of a modem when connected to a mobile network.

                      • Possible values
                        • IDLE
                        • HOME
                        • SEARCHING
                        • DENIED
                        • UNKNOWN
                        • ROAMING
                        • HOME_SMS_ONLY
                        • ROAMING_SMS_ONLY
                        • EMERGENCY_ONLY
                        • HOME_CSFB_NOT_PREFERRED
                        • ROAMING_CSFB_NOT_PREFERRED
                        • ATTACHED_RLOS
                      "},{"location":"core-services/network-status-rest-v1/#failurereport","title":"FailureReport","text":"

                      An object reporting a failure while retrieving the status of a specific network interface

                      Properties:

                      • interfaceId: string The identifier of the interface whose status cannot be retrieved.

                      • reason: string A message describing the reason of the failure.

                      "},{"location":"core-services/network-status-rest-v1/#genericfailurereport","title":"GenericFailureReport","text":"

                      An object reporting a failure message.

                      Properties:

                      • message: string A message describing the failure.
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/","title":"Nvidia\u2122 Triton Server Inference Engine","text":"

                      The Nvidia\u2122 Triton Server is an open-source inference service software that enables the user to deploy trained AI models from any framework on GPU or CPU infrastructure. It supports all major frameworks like TensorFlow, TensorRT, PyTorch, ONNX Runtime, and even custom framework backend. With specific backends, it is also possible to run Python scripts, mainly for pre-and post-processing purposes, and exploit the DALI building block for optimized operations. For more detail about the Triton Server, please refer to the official website.

                      Kura provides three components for exposing the Triton Server service functionality which implement the inference engine APIs and provides methods for interacting with a local or remote Nvidia\u2122 Triton Server:

                      • TritonServerRemoteService: provides methods for interacting with a remote Nvidia\u2122 Triton Server without managing the server lifecycle. Can be used both for connecting to a remote instance or a local non-managed instance. It exposes a simpler but more limited configuration.
                      • TritonServerNativeService: provides methods for interacting with a local native Nvidia\u2122 Triton Server. Requires the Triton Server executable to be already available on the device and offers more options and features (like AI Model Encryption).
                      • TritonServerContainerService: provides methods for interacting with a local container running Nvidia\u2122 Triton Server. Requires the Triton Server container image to be already available on the device and offers more options and features (like AI Model Encryption).
                      • TritonServerService: provides methods for interacting with a local or remote Nvidia\u2122 Triton Server within the same component. Note: deprecated since 5.2.0
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#nvidiatm-triton-server-installation","title":"Nvidia\u2122 Triton Server installation","text":"

                      Before running Kura's Triton Server Service, you must install the Triton Inference Server. Here you can find the necessary steps for the available suggested installation methods.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#native-triton-installation-on-jetson-devices","title":"Native Triton installation on Jetson devices","text":"

                      A release of Triton for JetPack is provided in the tar file in the Triton Inference Server release notes. Full documentation is available here.

                      Installation steps:

                      • Before running the executable you need to install the Runtime Dependencies for Triton.
                      • After doing so you can extract the tar file and run the executable in the bin folder.
                      • It is highly recommended to add the tritonserver executable to your path or symlinking the executable to /usr/local/bin.
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-docker-image-installation","title":"Triton Docker image installation","text":"

                      Before you can use the Triton Docker image you must install Docker. If you plan on using a GPU for inference you must also install the NVIDIA Container Toolkit.

                      Pull the image using the following command.

                      $ docker pull nvcr.io/nvidia/tritonserver:<xx.yy>-py3\n

                      Where <xx.yy> is the version of Triton that you want to pull.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#native-triton-installation-on-supported-devices","title":"Native Triton installation on supported devices","text":"

                      The official docs mention the possibility to perform a native installation on supported platform by extracting the binaries from the Docker images. To do so you must install the necessary dependencies (some can be found in the Jetson runtime dependencies docs) on the system. For Triton to support NVIDIA GPUs you must install CUDA, cuBLAS and cuDNN referencing the support matrix.

                      Note

                      For Python models the libraries available to the Python model are the ones available for the user running the Triton server. Therefore you'll need to install the libraries through pip for the kurad user.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-setup","title":"Triton Server setup","text":"

                      The Triton Inference Server serves models from one or more model repositories that are specified when the server is started. The model repository is the directory where you place the models that you want Triton to serve. Be sure to follow the instructions to setup the model repository directory.

                      Further information about an example Triton Server setup can be found in the official documentation.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-remote-service-component","title":"Triton Server Remote Service component","text":"

                      The Kura Triton Server Remote Service component is the implementation of the inference engine APIs and provides methods for interacting with a remote (i.e. unmnanaged) Nvidia\u2122 Triton Server. As presented below, the component enables the user to communicate to an external server to load specific models. With this component the server lifecycle (startup, shutdown) won't be handled by Kura and it's the user responsibility to make it available to Kura for connecting.

                      The parameters used to configure the Triton Service are the following:

                      • Nvidia Triton Server address: the address of the Nvidia Triton Server.
                      • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
                      • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
                      • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
                      • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance. By default, size of 4194304 bytes (= 4.19 MB) is used. Increase this value to be able to send large amounts of data as input to the Triton server (like Full HD images). The Kura logs will show the following error when exceeding such limit:
                        io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 4194304\n

                      Note

                      Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-native-service-component","title":"Triton Server Native Service component","text":"

                      The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local native Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway and handles its lifecycle. This operating mode supports more features for interacting with the server like the AI Model Encryption.

                      Note

                      Requirement: tritonserver executable needs to be available in the path to the kurad user. Be sure to have a working Triton Server installation before configuring the local native Triton Server instance through Kura UI.

                      The parameters used to configure the Triton Service are the following:

                      • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
                      • Local model repository path: Specify the path on the filesystem where the models are stored.
                      • Local model decryption password: Specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
                      • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
                      • Local backends path: Specify the path on the filesystem where the backends are stored.
                      • Optional configuration for the local backends: A semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
                      • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
                      • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

                      Note

                      Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-container-service-component","title":"Triton Server Container Service component","text":"

                      The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local container running the Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway and handles its lifecycle. This operating mode supports more features for interacting with the server like the AI Model Encryption.

                      Note

                      Requirement: 1. Triton Server container image already installed on the device. For instructions refer to the installation section in this page. 2. Kura's Container Orchestration Service enabled.

                      The parameters used to configure the Triton Service are the following:

                      • Container Image: The image the container will be created with.
                      • Container Image Tag: Describes which image version that should be used for creating the container.
                      • Nvidia Triton Server ports: The ports used to connect to the server for HTTP, GRPC, and Metrics services.
                      • Local model repository path: Specify the path on the filesystem where the models are stored.
                      • Local model decryption password: Specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
                      • Inference Models: A comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
                      • Local Backends Path: Specifies the host filesystem path where the backends are stored. This folder will be mounted as a volume inside the Triton container and will override the existing backends. If left blank, the backends provided by the Triton container will be used.
                      • Optional configuration for the local backends: A semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
                      • Memory: The maximum amount of memory the container can use in bytes. Set it as a positive integer, optionally followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes. The minimum allowed value is platform dependent (i.e. 6m). If left empty, the memory assigned to the container will be set to a default value by the native container orchestrator.
                      • CPUs: Specify how many CPUs the Triton container can use. Decimal values are allowed, so if set to 1.5, the container will use at most one and a half cpu resource.
                      • GPUs: Specify how many Nvidia GPUs the Triton container can use. Allowed values are 'all' or an integer number. If there's no Nvidia GPU installed, leave the field empty. If the Nvidia Container Runtime is used, leave the field empty.
                      • Runtime: Specifies the fully qualified name of an alternate OCI-compatible runtime, which is used to run commands specified by the 'run' instruction for the Triton container. Example: nvidia corresponds to --runtime=nvidia. Note: when using the Nvidia Container Runtime, leave the GPUs field empty. The GPUs available on the system will be accessible from the container by default.
                      • Devices: A comma-separated list of device paths passed to the Triton server container (e.g. /dev/video0).
                      • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
                      • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

                      Note

                      Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are typically used by Kura for debug purposes.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#triton-server-service-component-deprecated-since-520","title":"Triton Server Service component [deprecated since 5.2.0]","text":"

                      The Kura Triton Server component is the implementation of the inference engine APIs and provides methods for interacting with a local or remote Nvidia\u2122 Triton Server. As presented below, the component enables the user to configure a local server running on the gateway or to communicate to an external server to load specific models.

                      The parameters used to configure the Triton Service are the following:

                      • Local Nvidia Triton Server: If enabled, a local native Nvidia Triton Server is started on the gateway. In this case, the model repository and backends path are mandatory. Moreover, the server address property is overridden and set to localhost. Be aware that the Triton Server has to be already installed on the system.
                      • Nvidia Triton Server address: the address of the Nvidia Triton Server.
                      • Nvidia Triton Server ports: the ports used to connect to the server for HTTP, GRPC, and Metrics services.
                      • Local model repository path: Only for a local instance, specify the path on the filesystem where the models are stored.
                      • Local model decryption password: Only for local instance, specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.
                      • Inference Models: a comma-separated list of inference model names that the server will load. The models have to be already present in the filesystem where the server is running. This option simply tells the server to load the given models from a local or remote repository.
                      • Local backends path: Only for a local instance, specify the path on the filesystem where the backends are stored.
                      • Optional configuration for the local backends: Only for local instance, a semi-colon separated list of configuration for the backends. i.e. tensorflow,version=2;tensorflow,allow-soft-placement=false
                      • Timeout (in seconds) for time consuming tasks: Timeout (in seconds) for time consuming tasks like server startup, shutdown or model load. If the task exceeds the timeout, the operation will be terminated with an error.
                      • Max. GRPC message size (bytes): this field controls the maximum allowed size for the GRPC calls to the server instance.

                      Note

                      Pay attention on the ports used for communicating with the Triton Server. The default ports are the 8000-8002, but these are tipically used by Kura for debug purposes.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-local-native-triton-server-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a local native Triton Server with Triton Server Service component [deprecated since 5.2.0]","text":"

                      Note

                      Requirement: tritonserver executable needs to be available in the path to the kurad user. Be sure to have a working Triton Server installation before configuring the local native Triton Server instance through Kura UI.

                      When the Local Nvidia Triton Server option is set to true, a local instance of the Nvidia\u2122 Triton Server is started on the gateway. The following configuration is required:

                      • Local Nvidia Triton Server: true
                      • Nvidia Triton Server address: localhost
                      • Nvidia Triton Server ports: mandatory
                      • Local model repository path: mandatory
                      • Inference Models: mandatory. Note that the models have to be already present on the filesystem.
                      • Local backends path: mandatory

                      The typical command used to start the Triton Server is like this:

                      tritonserver --model-repository=<model_repository_path> \\\n--backend-directory=<backend_repository_path> \\\n--backend-config=<backend_config> \\\n--http-port=<http_port> \\\n--grpc-port=<grpc_port> \\\n--metrics-port=<metrics_port> \\\n--model-control-mode=explicit \\\n--load-model=<model_name_1> \\\n--load-model=<model_name_2> \\\n...\n
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-local-triton-server-running-in-a-docker-container-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a local Triton Server running in a Docker container with Triton Server Service component [deprecated since 5.2.0]","text":"

                      If the Nvidia\u2122 Triton Server is running as a Docker container in the gateway, the following configuration is required:

                      • Local Nvidia Triton Server: false
                      • Nvidia Triton Server address: localhost
                      • Nvidia Triton Server ports: \\<mandatory>
                      • Inference Models: \\<mandatory>. The models have to be already present on the filesystem.

                      In order to correctly load the models at runtime, configure the server with the --model-control-mode=explicit option. The typical command used for running the docker container is as follows. Note the forward of the ports to not interfere with Kura.

                      docker run --rm \\\n-p4000:8000 \\\n-p4001:8001 \\\n-p4002:8002 \\\n--shm-size=150m \\\n-v path/to/models:/models \\\nnvcr.io/nvidia/tritonserver:[version] \\\ntritonserver --model-repository=/models --model-control-mode=explicit\n
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#configuration-for-a-remote-triton-server-with-triton-server-service-component-deprecated-since-520","title":"Configuration for a remote Triton Server with Triton Server Service component [deprecated since 5.2.0]","text":"

                      When the Nvidia\u2122 Triton Server is running on a remote server, the following configuration is needed:

                      • Local Nvidia Triton Server: false
                      • Nvidia Triton Server address: mandatory
                      • Nvidia Triton Server ports: mandatory
                      • ** Inference Models**: mandatory. The models have to be already present on the filesystem.
                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#ai-model-encryption-support","title":"AI Model Encryption Support","text":"

                      For ensuring inference integrity and providing copyright protection of deep-learning models on edge devices, Kura provides decryption capabilities for trained models to be served through the Triton Server.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#how-it-works","title":"How it works","text":"

                      Prerequisites: a deep-learning trained model (or more) exists with the corresponding necessary configuration for running on the Triton Server without encryption. A folder containing the required files (model, configuration etc) has been tested on a Triton Server.

                      Restrictions: if model encryption is used, the following restrictions apply:

                      • model encryption support is only available for a local Triton Server instance
                      • all models in the folder containing the encrypted models must be encrypted
                      • all models must be encrypted with OpenPGP-compliant AES 256 cipher algorithm
                      • all models must be encrypted with the same password

                      Once the development of the deep-learning model is complete, the developer who wants to deploy the model on the edge device in a secure manner can proceed with encrypting the Triton model using the procedure detailed below. After encrypting the model he/she can transfer the file on the edge device using his/her preferred method.

                      Kura will keep the stored model protected at all times and have the model decrypted in runtime only for use by the Inference Server Runtime. As soon as the model is correctly loaded into memory the decrypted model will be removed from the filesystem.

                      As an additional security measure, the Model Repository containing the decrypted models will be stored in a temporary subfolder and will feature restrictive permission such that only Kura, the Inference Server and the root user will be able to access it.

                      "},{"location":"core-services/nvidia-triton-server-inference-engine/#encryption-procedure","title":"Encryption procedure","text":"

                      Given a trained model inside the folder tf_autoencoder_fp32 (for example) with the following layout (see the official documentation for details):

                      tf_autoencoder_fp32\n\u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 model.savedmodel\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 assets\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 keras_metadata.pb\n\u2502\u00a0\u00a0     \u251c\u2500\u2500 saved_model.pb\n\u2502\u00a0\u00a0     \u2514\u2500\u2500 variables\n\u2502\u00a0\u00a0         \u251c\u2500\u2500 variables.data-00000-of-00001\n\u2502\u00a0\u00a0         \u2514\u2500\u2500 variables.index\n\u2514\u2500\u2500 config.pbtxt\n

                      Compress the model into a zip archive with the following command:

                      zip -vr tf_autoencoder_fp32.zip tf_autoencoder_fp32/\n

                      then encrypt it with the AES 256 algorithm using the following gpg command:

                      gpg --armor --symmetric --cipher-algo AES256 tf_autoencoder_fp32.zip\n

                      The resulting archive tf_autoencoder_fp32.zip.asc can be transferred to the Local Model Repository Path on the target machine and will be decrypted by Kura.

                      "},{"location":"core-services/position-service/","title":"Position Service","text":"

                      The PositionService provides the geographic position of the gateway if a GPS component is available and enabled.

                      When this service is enabled and provides a valid geographic position, this position is published in the gateway birth certificate with its location.

                      The GPS connection parameters must be defined in order to allow the service to receive the GPS frames. The PositionService supports direct access to gps device or the connection to that through gpsd.

                      For a device that is not connected to a GPS, it is possible to define a static position by entering latitude, longitude,, altitude and GNSS Type. In this case, the position, date and time information is returned by the PositionService as if it were an actual GPS position. This may be useful when a gateway is installed in a known place and does not move.

                      To use this service, select the PositionService option located in the Services area as shown in the screen capture below.

                      This service provides the following configuration parameters:

                      • enabled - defines whether or not this service is enabled or disabled. (Required field.)

                      • static - specifies true or false whether to use a static position instead of a GPS. (Required field.)

                      • provider - species which position provider use, can be gpsd or serial.

                        • gpsd - gpsd service daemon if is available on the system.
                        • serial - direct access to gps device through serial or usb port.
                        • modemManager - location information retrieved through a Modem Manager controlled modem

                      Warning

                      The Modem Manager based provider exploits the Location service offered by an enabled modem controlled by the ModemManager daemon: to work properly, MM requires that the modem has a SIM card inserted. When selecting this mode make sure you meet this necessary condition. It is possible to check the status of the modem through the OS running the command mmcli -m <numberOfModem>, the status of the location service through: mmcli -m <numberOfModem> --location-status; while the location data can be retrieved by running mmcli -m <numberOfModem> --location-get.

                      • modem.manager.refresh.rate.seconds - refresh time of the position when using the ModemManager base provider.

                      • gpsd.host - host where gpsd service deamon is running. (required only if gpsd provider is selected.)

                      • gpsd.port - port where gpsd service is listening. (required only if gpsd provider is selected.)

                      • gpsd.max.fix.validity.interval.seconds - maximum time waited for a new position info before considering the fix lost.

                      • latitude - provides the static latitude value in degrees.

                      • longitude - provides the static longitude value in degrees.

                      • altitude - provides the static altitude value in meters.

                      • GNSS Type - provides the gnss system used to retrieve static information.

                      • port - supplies the USB or serial port of the GPS device.

                      • baudRate - supplies the baud rate of the GPS device.

                      • bitsPerWord - sets the number of bits per word (databits) for the serial communication to the GPS device.

                      • stopbits - sets the number of stop bits for the serial communication to the GPS device.

                      • parity - sets the parity for the serial communication to the GPS device.

                      "},{"location":"core-services/rest-service/","title":"REST Service","text":"

                      Kura provides a built-in REST Service based on the osgi-jax-rs-connector project.

                      By default, REST service providers register their services using the context path /services. The REST service provides the BASIC Authentication support and HTTPS client certificate authentication support.

                      Starting from Kura 5.4.0, REST service provides built in session management support, the Session REST APIs can be used to establish a session. Using sessions is the recommended way for interacting with Kura REST APIs from a browser based application, for this use case it is also possible to disable the default session-less BASIC and certificate based authentication (see the Basic Authentication Enabled and Enable Certificate Authentication Whitout Session Management configuration parameters below).

                      REST API access is available on all HTTP ports defined in the HTTP/HTTPS Configuration section, unless access is restricted to dedicated ports using the corresponding configuration parameter (see below).

                      Certificate authentication support is only available on the HTTPS With Certificate Authentication Ports configured in HTTP/HTTPS Configuration section.

                      Kura Identity names and passwords can be used for BASIC Authentication. Certificate authentication follows the same rules as Gateway Administration Console Authentication.

                      Warning

                      If the forced password change feature for a given identity is enabled, REST API password authentication will be blocked for that identity until the password is updated by the user or the feature is manually disabled. Certificate authentication will continue to be allowed even if the forced password change feature is enabled.

                      JAX-RS roles are mapped to Kura permissions, the name of a permission associated with a JAX-RS role is the rest. prefix followed by the role name. For example the assets role is mapped to the rest.assets permission. REST related permissions can be assigned to an identity using the Gateway Administration Console in the Identities section.

                      "},{"location":"core-services/rest-service/#rest-service-configuration","title":"Rest Service configuration","text":"

                      The available configuration parameters are the following:

                      • Allowed Ports: If set to a non empty list, REST API access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. Please make sure that the allowed ports are open in HttpService and Firewall configuration. (Default: empty)

                      • Password Authentication Enabled: Enables or disables the built-in password authentication support. (Default: true)

                      • Certificate Authentication Enabled: Enables or disables the built-in certificate authentication support. (Default: true)

                      • Session Based Authentication Enabled: If set to true, enables authentication using the dedicated /services/session/v1 endpoints and cookie based session management. (Default: true)

                      • Session Inactivity Interval (Seconds): The session inactivity interval, sessions will expire if no request is performed for the amount of time specified by this parameter in seconds. This parameter is ignored if Session Based Authentication Enabled is set to false. (Default: 900)

                      • Basic Authentication Enabled: Allows to perform authentication by providing identity name and password as BASIC credentials in the request to any resource endpoint. Requires that the Password Authentication Enabled parameter is set to true. (Default: true)

                      • Enable Certificate Authentication Without Session Management: If set to true, calling /services/session/v1/certificate to create a session will not be necessary in order to perform certificate based authentication. Presenting a valid HTTPS client certificate and accessing resource endpoint directly is enough for authentication to succeed. Requires that the Certificate Authentication Enabled parameter is set to true. (Default: true)

                      "},{"location":"core-services/rest-service/#custom-authentication-methods","title":"Custom authentication methods","text":"

                      Starting from Kura 5.2.0 it is also possible to develop custom REST authentication method providers by registering an implementation of the org.eclipse.kura.rest.auth.AuthenticationProvider interface as an OSGi service. The org.eclipse.kura.example.rest.authentication.provider bundle in Kura repository provides an example on how to implement a custom authentication method.

                      "},{"location":"core-services/rest-service/#assets-rest-apis","title":"Assets REST APIs","text":"

                      Kura exposes REST APIs for the Asset instances instantiated in the framework. Assets REST APIs are available in the context path /services/assets. Following, the supported REST endpoints.

                      Method Path Allowed roles Encoding Request parameters Description GET / assets JSON None Returns the list of available assets GET /{pid} assets JSON None Returns the list of available channels for the selected asset (specified by the corresponding PID) GET /{pid}/_read assets JSON None Returns the read for all the READ channels in the selected Asset POST /{pid}/_read assets JSON The list of channels where the READ operation should be performed. Returns the result of the read operation for the specified channels POST /{pid}/_write assets JSON The list of channels and associated values that will be used for the WRITE operation. Performs the write operation for the specified channels returning the result of the operation."},{"location":"core-services/simple-artemis-mqtt-broker/","title":"Simple Artemis MQTT Broker","text":"

                      By default, this instance is disabled but, selecting the Simple Artemis MQTT Broker option in Services it is possible to enable a basic instance of an \u200bActiveMQ-7 broker with MQTT capabilities.

                      The service has the following configuration fields:

                      • Enabled - (Required) - Enables the broker instance
                      • MQTT address - MQTT broker listener address. In order to allow access to the broker from processes running on external nodes, make sure to bind the server to an externally accessible address. Setting this parameter to 0.0.0.0 binds to all addresses.
                      • MQTT port - (Required) - MQTT broker port, make sure to open the specified port in the firewall configuration section if external access to the broker is required.
                      • User name - The username\u200b required to access to the broker
                      • Password of the user - The password required to connect. If the password is empty, no password will be required to connect.
                      "},{"location":"core-services/sqlite-db-service/","title":"SQLite Db Service","text":"

                      Starting from version 5.3, Kura provides provides an integration of the SQLite database, based on the org.xerial:sqlite-jdbc wrapper .

                      The database integration is not included in the official distribution, but it can be downloaded from Eclipse Marketplace as a deployment package.

                      Warning

                      Note about Raspberry PI: Recent versions of Raspberry Pi OS 32 bit on Raspberry PI 4 will use by default a 64 bit kernel with a 32 bit userspace. This can cause issues to applications that use the result of uname -m to decide which native libraries to load (see https://github.com/raspberrypi/firmware/issues/1795). This currently affects the Kura SQLite database connector. It should be possible to solve by switching to the 32 bit kernel setting arm_64bit=0 in /boot/config.txt and restarting the device.

                      "},{"location":"core-services/sqlite-db-service/#supported-features","title":"Supported Features","text":"

                      Kura supports the following SQLite database features:

                      • Persistence modes: The implementation currently supports in-memory and file-based database instances.

                      • Multiple database instances: It is possible to create and configure multiple database instances from the Kura Administration UI, these instances can be selectively consumed by applications.

                      • Journaling modes: The implementation currently supports the WAL and rollback journal journaling modes.

                      • Periodic database defrag and checkpoint: The implementations currently supports periodic defrag (VACUUM) and periodic WAL checkpoint.

                      "},{"location":"core-services/sqlite-db-service/#usage","title":"Usage","text":""},{"location":"core-services/sqlite-db-service/#creating-a-new-sqlite-database-instance","title":"Creating a new SQLite database instance","text":"

                      To create a new SQLite database instance, use the following procedure:

                      1. Open the Administrative UI and press the + button in the side menu, under the Services section. A pop-up dialog should appear.
                      2. Select org.eclipse.kura.db.SQLiteDbService from the Factory drop-down list, enter an arbitrary name for the new instance and click Apply.
                      3. An entry for the newly created instance should appear in the side menu under Services, click on it to review its configuration. It is not possible to create different DB instances that manage the same database file.
                      "},{"location":"core-services/sqlite-db-service/#configuration-parameters","title":"Configuration Parameters","text":"

                      The SQLite DB provides the following configuration parameters:

                      • Database Mode: Defines the database mode. If In Memory is selected, the database will not be persisted on the filesystem, all data will be lost if Kura is restarted and/or the database instance is deleted. If Persisted is selected, the database will be stored on disk in the location specified by the Persisted Database Path parameter.

                      • Persisted Database Path: Defines the path to the database file (it must include the database file name). This parameter is only relevant for persisted databases.

                      • Encryption Key: Allows to specify a key/passphrase for encrypting the database file. This feature requires a SQLite binary with an encryption extension, and is only relevant for persisted databases. The key format can be specified using the Encryption Key Format parameter. If the value of this parameter is changed, the encryption key of the database will be updated accordingly. This parameter can be left empty to create an unencrypted database or to decrypt an encrypted one.

                      Note

                      The sqlite-jdbc version distributed with Kura does not contain any encryption extension, encryption features will not be available out of the box. See sqlite-jdbc documentaton for instructions about how to use a security extension.

                      • Encryption Key Format: Allows to specify the format of the Encryption Key parameter value. The possible values are ASCII (an ASCII string), Hex SSE (the key is an hexadecimal string to be used with the SSE extension) or Hex SQLCipher (the key is an hexadecimal string to be used with the SQLCipher extension).

                      • Journal Mode: The database journal mode. The following options are available:

                        • Rollback Journal: The database instance will use the rollback journal for more details. More specifically, the DELETE argument will be passed to the pragma journal_mode command.

                        • WAL: The database instance will use WAL mode. This is the default mode.

                        The WAL mode description page contains a comparison of the two modes.

                      • Defrag enabled: Enables or disables the database defragmentation. Use the Defrag Interval parameter to specify the interval.

                      • Defrag interval (seconds): The implementation supports running periodic defragmentation using the VACUUM command. This parameter specifies the interval in minutes between two successive checkpoints, set to zero to disable. This parameter is only relevant for persisted databases.

                      Warning

                      The total disk space used by SQLite database files might temporarily increase during defragmentation and/or if transactions that modify a lot of data are performed (including deletion). In particular:

                      • Defragmentation is implemented by copying all non-free database pages to a new file and then deleting the original file.
                      • If transactions that involve a lot of data are performed, in case of rollback journal the old content of the modified pages will be stored in the journal until the transaction is completed, and in WAL mode the new content of modified pages will be stored to the WAL file until the next checkpoint is performed.

                      It is recommended to perform some tests to determine the maximum database files size that will be used by the application and ensure that the size of the partition containing the database is at least twice as the expected db size.

                      • Checkpoint enabled: Enables or disables checkpoints in WAL journal mode. Use the WAL Checkpoint Interval parameter to specify the interval.
                      • WAL Checkpoint Interval (Seconds): The implementation supports running periodic periodic WAL checkpoints. Checkpoints will be performed in TRUNCATE mode. This parameter specifies the interval in seconds between two consecutive checkpoints, set to zero to disable. This parameter is only relevant for persisted databases in WAL Journal Mode. In WAL mode a checkpoint will be performed after a periodic defragmentation regardless of the value of this parameter.

                      • Connection pool max size: The implementation manages connections using a connection pool. This parameter defines the maximum number of connections for the pool. If a new connection is requested

                      • Delete Database Files On Failure: If set to true, the database files will be deleted in case of failure in opening a persisted database. This is intended as a last resort measure for keeping the database service operational, especially in the case when it is used as a cloud connection message store.

                      • Debug Shell Access Enabled: Enables or disables the interaction with this database instance using the sqlitedbg:excuteQuery OSGi console command. (see #debug-shell)

                      "},{"location":"core-services/sqlite-db-service/#selecting-a-database-instance-for-existing-components","title":"Selecting a database instance for existing components","text":"

                      A database instance is identified by its Kura service PID. The PID for instances created using the Web UI is the string entered in the Name field at step 2 of the previous section.

                      The built-in components that use database functionalities allow to specify which instance to use in their configuration. These components are the DataService component of the cloud stack, the Wire Record Store and Wire Record Query wire components. The configuration of each component contains a property that allows to specify the Kura Service PID of the desired instance.

                      Warning

                      Using SQLite database instances through the deprecated DbFilter and DbStore components is not supported.

                      "},{"location":"core-services/sqlite-db-service/#usage-through-wires","title":"Usage through Wires","text":"

                      It is possible to store and extract Wire Records into/from a SQLite database instance using the Wire Record Store and Wire Record Query wire components.

                      When a Wire Record is received by a Wire Record Store attached to a SQLite based database instance, the data will be stored in a table whose name is the current value of the Record Collection Name configuration parameter of the Wire Component.

                      Each property contained in a Wire Record will be appended to a column with the same name as the property key. A new column will be created if it doesn't already exists.

                      Since it is not possible to establish a one to one mapping between SQLite storage classes and the data types available on the Wires, the implementation will assign a custom type name to the created columns in order to keep track of the inserted Wire Record property type.

                      The custom types will be assigned according to the following table:

                      Wires Data Type Column Type Name Storage Class Type Affinity BOOLEAN BOOLEAN INTEGER INTEGER INT INTEGER LONG BIGINT INTEGER FLOAT FLOAT REAL DOUBLE DOUBLE REAL STRING TEXT TEXT BYTE_ARRAY BLOB BLOB

                      The custom column type makes it possible to preserve the original type when data is extracted with the Wire Record Query component. Please note that the resulting type may change in case of queries that build computed columns.

                      Warning

                      It is not recommended to store Wire Records having properties with the same key and different value type. If the value type changes, the target column will be dropped and recreated with the type derived from the last received record. All existing data in the target column will be lost. The purpose of this is to allow changing the type of a column with a Wire Graph configuration update.

                      "},{"location":"core-services/sqlite-db-service/#debug-shell","title":"Debug shell","text":"

                      It is possible to inspect the contents of the database file using the OSGi console with the following command:

                      sqlitedbg:executeQuery dbServicePid 'query'\n

                      or more simply

                      executeQuery dbServicePid 'query'\n

                      where dbServicePid is the user defined pid of the target database instance and query is an arbitrary SQL query to be executed (make sure to properly quote the query string with the ' character). The command will print the result set or changed row count on the console.

                      It is only possible to access database instances whose Debug Shell Access Enabled configuration parameter is set to true. The default of this parameter is false.

                      The OSGi console is disabled by default, it can be accessed by starting the framework with the /opt/eclipse/kura/bin/start_kura_debug.sh script and running the following command on the gateway:

                      telnet localhost 5002\n

                      Warning

                      This feature is intended for debug purposes only, the Debug Shell Access Enabled parameter as well as the OSGi console should not be left enabled on production systems.

                      "},{"location":"core-services/watchdog-service/","title":"Watchdog Service","text":"

                      The WatchdogService provides methods for starting, stopping, and updating a hardware watchdog if it is present on the system. Once started, the watchdog must be updated to prevent the system from rebooting.

                      To use this service, select the WatchdogService option located in the Services area as shown in the screen capture below.

                      This service provides the following configuration parameters:

                      • enabled - sets whether or not this service is enabled or disabled. (Required field)

                      • pingInterval - defines the maximum time interval between two watchdogs' refresh to prevent the system from rebooting. (Required field)

                      • Watchdog device path - sets the watchdog device path. (Required field)

                      • Reboot Cause File Path - sets the path to the file that will contain the reboot cause information. (Required field)

                      "},{"location":"gateway-configuration/authentication-and-authorization/","title":"Authentication and authorization","text":"

                      Kura 5 introduces a centralized authentication and authorization framework based on the OSGi UserAdmin specification. This framework introduces the concepts of identities and permissions:

                      • Identity: A Kura identity is related to authentication, an identity has a name and a set of associated credentials, for example a password.

                      • Permission: A Kura permission is related to authorization. Zero or more permissions can be assigned to a given identity. Each permission allows to access a set of resources and/or perform certain operations. Permissions can be defined by applications.

                      Examples of applications that use the new authentication and authorization framework are the Web Console and the REST API framework:

                      • Kura Web Console provides multi user support, Web Console users are mapped to Kura identities, the Web Console also defines a set of permissions that allow to restrict the operations that a given identity is allowed to perform.
                      • The Kura REST API framework users are now mapped to Kura identities and REST roles are mapped to Kura permissions. The old ConfigurationService based user and role definition mechanism has been dropped.

                      The authentication and authorization framework only allows to define and store identities and permissions, it does not provide implementation of authentication methods and/or session management. These aspects are left to applications.

                      "},{"location":"gateway-configuration/authentication-and-authorization/#permission-and-identity-representation","title":"Permission and identity representation","text":"

                      Permissions and identities are implemented on top of the UserAdmin User and Group concepts. See OSGi UserAdmin specification for more details on the Role, User and Group concepts.

                      "},{"location":"gateway-configuration/authentication-and-authorization/#identityservice-java-apis","title":"IdentityService Java APIs","text":"

                      Kura 5.5 introduces a new set of Java APIs that allow to manage Kura identities, the implementation is based on the UserAdmin conventions described in this page, and allows to manipulate Kura identities without interacting directly with the UserAdmin. Please refer to the Javadoc for more details.

                      The new APIs also provide the capability to implement and register IdentityConfigurationExtensions in the framework.

                      An IdentityConfigurationExtension can define additional custom configuration parameters for each identity. The custom configuration can be inspected and modified in the Identities section of Kura Web UI and using the identity/v2 rest APIs and MQTT request handler. The IdentityConfigurationExtension implementation can store the additional configuration in the way that is most suitable for the application, for example by adding custom UserAdmin properties or credentials.

                      "},{"location":"gateway-configuration/authentication-and-authorization/#identities","title":"Identities","text":"

                      A Kura identity is represented as a UserAdmin User with the following properties:

                      • The User name must be in the kura.user.${identity_name} form where ${identity_name} is a non empty string representing the identity name. The name of an User representing a Kura identity must start with the kura.user. prefix.

                      • A password may be assigned to an identity by defining a property in User credentials with the following format:

                      • The property name must be kura.password
                      • The property value must be a string containing SHA256 hash of the password, as computed by the
                        String org.eclipse.kura.crypto.CryptoService.sha256Hash(String)\n
                        method.

                      Starting from Kura 5.1, it is possible to force an identity to change the password at next login by setting the following property in User properties: - kura.need.password.change : true encoded as a JSON string. - The property will be cleared automatically after a successful password change on next login.

                      Starting from Kura 5.5 the following restrictions will be applied by the IdentityService:

                      • New Identity Names:
                        • must be at least 3 and at most 255 characters long.
                        • can only be composed by one or more sequences of alphanumeric characters ([A-Za-z0-9]+) separated by the dot or underscore symbols, dot and underscore is not allowed at the beginning or at the end of the permission name, sequences of consecutive dots and/or underscores are not allowed (examples of valid names are foo1.bAr, foo, a.b.c, foo.bar_baz).
                      • New Passwords:
                        • cannot be empty.
                        • must satisfy the password strenght requirements configured on the system.
                        • the maximum allowed length is 255 characters.
                        • cannot contain whitespace characters.
                      "},{"location":"gateway-configuration/authentication-and-authorization/#permissions","title":"Permissions","text":"

                      A Kura permission is represented as a UserAdmin Group with the following properties:

                      • The Group name must be in the kura.permission.${permission_name} form where ${permission_name} is a non empty string representing the permission name. The name of a Group representing a Kura permission must start with the kura.permission. prefix.

                      • Assigning a permission to a specific identity can be done by adding the User representing the identity to the basic members of the Group representing the permission.

                      Starting from Kura 5.5 the following restrictions will be applied by the IdentityService to the name new permissions:

                      • must be at least 3 and at most 255 characters long.
                      • can only be composed by one or more sequences of alphanumeric characters ([A-Za-z0-9]+) separated by the dot symbol, the dot is not allowed at the beginning or at the end of the permission name (examples of valid permission names are foo1.bAr, foo, a.b.c).
                      "},{"location":"gateway-configuration/authentication-and-authorization/#useradmin-persistence","title":"UserAdmin persistence","text":"

                      The org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl Kura service allows to persist the UserAdmin state in Kura configuration snapshot, this includes the defined identities and permissions.

                      The following configuration properties are used to store the UserAdmin concepts in Json format:

                      • Role configuration (id roles.config):

                      Stores the UserAdmin Roles, plain Roles are non used for representing Kura identities and permissions. The value must be a JSON array of Role elements.

                      • User configuration (id users.config):

                      Stores the UserAdmin Users. The value must be a JSON array of User elements.

                      • Group configuration (id groups.config):

                      Stores the UserAdmin Groups. The value must be a JSON array of Group elements.

                      "},{"location":"gateway-configuration/authentication-and-authorization/#json-representation-details","title":"JSON representation details","text":"

                      This section describes the JSON format used in org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl configuration.

                      "},{"location":"gateway-configuration/authentication-and-authorization/#useradmindictvalue","title":"UserAdminDictValue","text":"

                      A value appearing in UserAdmin dictionaries like properties and credentials

                      • type : variant Variants:

                      • a String property

                        • type : string
                      • a byte[] property
                        • type : array, element description: a byte of the array encoded as an unsigned integer
                        • type : number

                      Examples:

                      \"A string property\"\n
                      [1, 2, 3, 4, 5]\n
                      "},{"location":"gateway-configuration/authentication-and-authorization/#useradmindict","title":"UserAdminDict","text":"

                      A UserAdmin property dictionary, there are no well known property names, property values must be of UserAdminDictValue type

                      • type : object

                      Example:

                      {\n  \"stringProperty\": \"A string property\",\n  \"byteArrayProperty\": [1, 2, 3, 4, 5]\n}\n
                      "},{"location":"gateway-configuration/authentication-and-authorization/#role","title":"Role","text":"

                      An object describing and UserAdmin Role

                      • type : object Properties:

                      • name The role name

                      • type : string

                      • properties

                      • optional If the dictionary is empty
                        • UserAdminDict
                      "},{"location":"gateway-configuration/authentication-and-authorization/#user","title":"User","text":"

                      An object describing and UserAdmin User

                      • type : object Properties:

                      • name The role name

                      • type : string

                      • properties

                      • optional If the dictionary is empty

                        • UserAdminDict
                      • credentials

                      • optional If the dictionary is empty
                        • UserAdminDict

                      Example:

                      {\n  \"name\": \"kura.user.appadmin\",\n  \"credentials\": {\n    \"kura.password\": \"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM=\"\n  }\n}\n
                      "},{"location":"gateway-configuration/authentication-and-authorization/#group","title":"Group","text":"

                      An object describing and UserAdmin Group

                      • type : object Properties:

                      • name The role name

                      • type : string

                      • properties

                      • optional If the dictionary is empty

                        • UserAdminDict
                      • credentials

                      • optional If the dictionary is empty

                        • UserAdminDict
                      • basicMembers

                      • optional If the list is empty The list of the group basic members

                        • type : array, element description: A role name
                        • type : string
                      • requiredMembers

                      • optional If the list is empty The list of the group required members
                        • type : array, element description: A role name
                        • type : string

                      Example:

                      {\n  \"name\": \"kura.permission.kura.wires.admin\",\n  \"basicMembers\": [\"kura.user.appadmin\"]\n}\n
                      "},{"location":"gateway-configuration/cellular-configuration/","title":"Cellular Configuration","text":"

                      If it is not configured, the cellular interface is presented on the interface list by modem USB address (i.e. 2-1). This 'fake' interface name is completed by 'proper' interface name (e.g., ppp0) when the first modem configuration is submitted.

                      The cellular interface should be configured by first enabling it in the IPv4 or IPv6 tab, and then setting the Cellular tab. Note that the cellular interface can only be set as WAN using DHCP, Disabled or Not Managed (only for IPv4 connections). The cellular interface configuration options are described below.

                      "},{"location":"gateway-configuration/cellular-configuration/#cellular-configuration_1","title":"Cellular Configuration","text":"

                      The Cellular tab contains the following configuration parameters:

                      • Model: specifies the modem model.

                      • Network Technology: describes the network technology used by this modem.

                        • HSDPA
                        • EVDO
                        • EDGE
                      • Connection Type: specifies the type of connection to the modem.

                      • Modem Identifier: provides a unique name for this modem.

                      • Interface #: provides a unique number for the modem interface (e.g., an interface # of 0 would name the modem interface ppp0).

                      • Dial String: instructs how the modem should attempt to connect. Typical dial strings are as follows:

                        • HSPA modem: atd*99***1#
                        • EVDO/CDMA modem: atd#777
                      • APN: defines the modem access point name.

                        This is an optional parameter. If left empty, the value is automatically picked up from the Mobile Broadband Provider the modem is registered to. If a value is filled, the APN value is explicitly configured.

                        To avoid misconfiguration issues, it is strongly recommended to set it manually.

                        Note

                        APN value configuration

                        A good practice is to set the interface status to Disabled and then Enable For WAN when the APN is explicitly set. NetworkManager, indeed, may fallback to the default value if a wrong APN is specified, causing misleading behaviors. This does not happen if the interface is disabled and re-enabled after APN changes.

                      • Auth Type: specifies the authentication type.

                        • None
                        • Auto
                        • CHAP
                        • PAP
                      • Username: supplies the username; disabled if no authentication method is specified.

                      • Password: supplies the password; disabled if no authentication method is specified.

                      • Modem Reset Timeout: sets the modem reset timeout in minutes. If set to a non-zero value, the modem is reset after n consecutive minutes of unsuccessful connection attempts. If set to zero, the modem keeps trying to establish a PPP connection without resetting. The default value is 5 minutes.

                      • Reopen Connection on Termination: sets the persist option of the PPP daemon that specifies if PPP daemon should exit after connection is terminated. Note that the maxfail option still has an effect on persistent connections.

                      • Connection Attempts Retry Delay: Sets the holdoff parameter to instruct the PPP daemon on how many seconds to wait before re-initiating the link after it terminates. This option only has any effect if the persist option (Reopen Connection on Termination) is set to true. The holdoff period is not applied if the link was terminated because it was idle. The default value is 1 second.

                      • Connection Attempts: sets the maxfail option of the PPP daemon that limits the number of consecutive failed PPP connection attempts. The default value is 5 connection attempts. A value of zero means no limit. The PPP daemon terminates after the specified number of failed PPP connection attempts and restarts by the ModemMonitor thread.

                      • Disconnect if Idle: sets the idle option of the PPP daemon, which terminates the PPP connection if the link is idle for a specified number of seconds. The default value is 95 seconds. To disable this option, set it to zero.

                      • Active Filter: sets the active-filter option of the PPP daemon. This option specifies a packet filter (filter-expression) to be applied to data packets in order to determine which packets are regarded as link activity, and thereby, reset the idle timer. The filter-expression syntax is as described for tcpdump(1); however, qualifiers that do not apply to a PPP link, such as ether and arp, are not permitted. The default value is inbound. To disable the active-filter option, leave it blank.

                      • LCP Echo Interval: sets the lcp-echo-interval option of the PPP daemon. If set to a positive number, the modem sends LCP echo request to the peer at the specified number of seconds. To disable this option, set it to zero. This option may be used with the lcp-echo-failure option to detect that the peer is no longer connected.

                      • LCP Echo Failure: sets the lcp-echo-failure option of the PPP daemon. If set to a positive number, the modem presumes the peer to be dead if a specified number of LCP echo-requests are sent without receiving a valid LCP echo-reply. To disable this option, set it to zero.

                      "},{"location":"gateway-configuration/cellular-configuration/#gps","title":"GPS","text":"

                      The GPS tab allows the user to enable or disable the GPS module provided by the cellular modem. The available properties are:

                      • Enable GPS: enables GPS module for the selected modem.
                      • GPS Mode: specifies the GPS mode.
                        • UNMANAGED: the GPS device of the modem will be setup but not directly managed, therefore freeing the serial port for other services to use. This can be used in order to perform the setup of the GPS and then have another service (like gpsd) parse the NMEA strings in order to extract the position informations.
                        • MANAGED_GPS: the GPS device of the modem will be setup and directly managed (typically by ModemManager) therefore the serial port won't be available for other services to use.

                      GPS modes availability

                      GPS modes available for the modem are dependent on the modem model, modem firmware version and ModemManager version installed on the system. Some modes may not be selectable if the modem does not support them.

                      Therefore, to use the GPS module provided by the cellular modem with Kura's PositionService, the following considerations should be taken into account:

                      • The PositionService should be enabled. Serial settings of the PositionService should not be changed; it will be redirected to the modem GPS port automatically.
                      • To use the gpsd and serial PositionService providers with the GPS module provided by the cellular modem, the GPS mode should be set to UNMANAGED.
                      • To use the modemmanager PositionService provider with the GPS module provided by the cellular modem, the GPS mode should be set to MANAGED_GPS.

                      Refer to the Position Service section for more information.

                      "},{"location":"gateway-configuration/cloud-connections/","title":"Cloud Connections","text":"

                      The Cloud Connections section of the Kura Gateway Administration Console allows to create and manage cloud connections.

                      By default, Kura starts with a single cloud connection, as depicted in the following image:

                      The cloud services page allows to: - create a new cloud connection; - delete an existing cloud connection; - connect a selected cloud stack to the configured cloud platform; - disconnect the selected cloud stack from the connected cloud platform; - refresh the existing cloud connections.

                      When clicking on the New button, a dialog is displayed as depicted in the image below:

                      The user can select one of the existing cloud connection factories and give it a name (depending on the implementation, a name format can be suggested or forced).

                      Selecting a created Cloud Connection it is possible to associate a new publisher/subscriber by clicking the New Pub/Sub button. As for the connection creation case, the user can select one of the existing publisher/subscriber factories and give it a name.

                      "},{"location":"gateway-configuration/cloud-service-configuration/","title":"Cloud Service Configuration","text":"

                      The CloudService provides an easy-to-use API layer for the M2M application to communicate with a remote server. It operates as a decorator for the DataService, providing add-on features over the management of the DataTransport layer.

                      In addition to simple publish/subscribe, the Cloud Connection API simplifies the implementation of more complex interaction flows like request/response or remote resource management. The Cloud Connection abstracts the developers from the complexity of the transport protocol and payload format used in the communication.

                      The Cloud Connection allows a single connection to a remote server to be shared across more than one application in the gateway, providing the necessary topic partitioning. Its functions include:

                      • Adds application topic prefixes to allow a single remote server connection to be shared across applications.

                      • Defines a payload data model and provides default encoding/decoding serializers.

                      • Publishes life-cycle messages when the device and applications start and stop.

                      To use this service, select the CloudService option located in the Cloud Services area as shown in the screen capture below.

                      The CloudService provides the following configuration parameters:

                      • device.display-name: defines the device display name given by the system. (Required field).

                      • device.custom-name: defines the custom device display name if the device.display-name parameter is set to \"Custom\".

                      • topic.control-prefix: defines the topic prefix used for system and device management messages.

                      • encode.gzip: defines if the message payloads are sent compressed.

                      • republish.mqtt.birth.cert.on.gps.lock: when set to true, forces a republish of the MQTT Birth Certificate when a GPS correct position lock is received. The device is then registered with its real coordinates. (Required field).

                      • enable.default.subscriptions: manages the default subscriptions to the gateway management MQTT topics. When disabled, the gateway will not be remotely manageable.

                      • payload.encoding: specifies the encoding for the messages sent by the specific CloudService instance.

                        • Kura Protobuf - when this option is selected, the Kura Protobuf encoding will be used
                        • Simple JSON - the simple JSON encoding will be used instead. More information is available here. An example below.
                      {\n\"sentOn\" : 1491298822,\n\"position\" : {\n    \"latitude\" : 45.234,\n    \"longitude\" : -7.3456,\n    \"altitude\" : 1.0,\n    \"heading\" : 5.4,\n    \"precision\" : 0.1,\n    \"speed\" : 23.5,\n    \"timestamp\" : 1191292288,\n    \"satellites\" : 3,\n    \"status\" : 2\n},\n\"metrics\": {\n    \"code\" : \"A23D44567Q\",\n    \"distance\" : 0.26456E+4,\n    \"temperature\" : 27.5,\n    \"count\" : 12354,\n    \"timestamp\" : 23412334545,\n    \"enable\" : true,\n    \"rawBuffer\" : \"cGlwcG8gcGx1dG8gcGFwZXJpbm8=\"\n},\n\"body\": \"UGlwcG8sIHBsdXRvLCBwYXBlcmlubywgcXVpLCBxdW8gZSBxdWEu\"\n}\n

                      The default CloudService implementations publishes the following lifecycle messages:

                      1. BIRTH message: sent immediately when device is connected to the cloud platform;
                      2. DISCONNECT message: sent immediately before device is disconnected from the cloud platform;
                      3. delayed BIRTH message: sent when new cloud application handler becomes available, a DP is installed or removed, or when the GPS position is locked (can be disabled). These messages are cached for 30 seconds before sending. If no other message of such type arrives the message is sent; otherwise the BIRTH is cached and the timeout restarts. This is to avoid sending multiple messages when the framework starts.
                      "},{"location":"gateway-configuration/data-service-configuration/","title":"Data Service Configuration","text":"

                      The DataService provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. The DataService delegates to the MqttDataTransport service the implementation of the transport protocol that is used to interact with the remote server.

                      The DataService also adds the capability of storing published messages in a persistent store function and sending them over the wire at a later time. The purpose of this feature is to relieve service users from implementing their own persistent store. Service users may publish messages independently on the DataService connection status.

                      Info

                      Starting from Kura 5.3.0, the DataService allows to bind to custom persistent stores. A custom persistent store can be defined by creating an implementation the new org.eclipse.kura.message.store.provider.MessageStoreProvider service interface and registering it as an OSGi service.

                      In order to overcome the potential latencies introduced by buffering messages, the DataService allows a priority level to be assigned to\u200b each published message. Depending on the store configuration, there are certain guarantees that stored messages are not lost due to sudden crashes or power outages.

                      To use this service, select the DataService option located in the Cloud Connections area as shown in the screen capture below.

                      The DataService offers methods and configuration options to manage the connection to the remote server including the following (all required) parameters described below.

                      • Connect Auto-on-startup - When set to true, the service tries to auto-connect to the remote server on start-up and restore the connection every time the device is disconnected. These attempts are made at the frequency defined in the Connect Retry-interval parameter until the connection is established. Using the Connect/Disconnect button disables this function.
                      • Connect Retry-interval - Frequency in seconds to retry a connection of the Data Publishers after a disconnect.
                      • Enable Recovery On Connection Failure - Enables the recovery feature on connection failure. If the device is not able to connect to a remote cloud platform, the service will wait for a specified amount of connection retries. If the recovery fails, the device will be rebooted. Being based on the Watchdog service, it needs to be activated as well.
                      • Connection Recovery Max Failure - Number of failures in Data Publishers connection before forcing a reboot.
                      • Disconnect Quiesce-timeout - Allows the delivery of in-flight messages to be completed before disconnecting from the broker when a disconnection from the broker is being forced.
                      • Message Store Provider Service PID - The Kura service pid of the Message Store instance to be used. The pid of the default instance is org.eclipse.kura.db.H2DbService. The Message Store instance must implement the org.eclipse.kura.message.store.provider.MessageStoreProvider interface.
                      • Store Housekeeper-interval - Defines the interval in seconds used to run the Data Store housekeeper task.
                      • Store Purge-age - defines the age in seconds of completed messages (either published with QoS = 0 or confirmed with QoS > 0) after which they are deleted (minimum 5).
                      • Store Capacity - Defines the maximum number of messages persisted in the Data Store.
                      • In-flight-messages parameters - Define the management of messages that have been published and not yet confirmed, including:
                      • In-flight-messages Republish-on-new-session - Whether to republish in-flight messages on a new MQTT session.
                      • In-flight-messages Max-number - The maximum number of in-flight messages.
                      • In-flight-messages Congestion-timeout - Timeouts the in-flight messages congestion condition. The service will force a disconnect attempting to reconnect (0 to disable).
                      "},{"location":"gateway-configuration/data-service-configuration/#connection-monitors","title":"Connection Monitors","text":"

                      The DataService offers methods and configuration options to monitor the connection to the remote server and, eventually, cause a system reboot to recover from transient network problems.

                      This feature, if enabled, leverages the watchdog service and reboots the gateway if the maximum number of configured connection attempts has been made.

                      A reboot is not requested if the connection to the remote broker succeeds but an authentication error, an invalid client id or an authorization error is thrown by the remote cloud platform and causes a connection drop.

                      The image below shows the parameters that need to be tuned in order to enable this connection monitor feature.

                      To configure this functionality, the System Administrator needs to specify the following configuration elements:

                      • Enable Recovery On Connection Failure: when enabled, activates the recovery feature on connection failure: if the device is not able to connect to a remote cloud platform, the service will wait for a specified amount of connection retries. If the recovery fails, the device will be rebooted. Being based on the Watchdog service, it needs to be activated as well.

                      • Connection Recovery Max Failure: related to the previous parameter. It specifies the number of failures before a reboot is requested. !!! warning To be fully working, this feature needs the enabling of the Watchdog Service.

                      "},{"location":"gateway-configuration/data-service-configuration/#message-publishing-backoff-delay","title":"Message Publishing Backoff Delay","text":"

                      In order to have a finer control on the data flow, when a device reconnects to a remote cloud platform, Kura integrates into the Data Service a Backoff delay feature that limits the rate of messages sent.

                      This feature, enabled by default, integrates the Token Bucket concept to limit the bursts of messages sent to a remote cloud platform.

                      In the image below, the parameters that need to be tuned, in the Data Service, to take advantage of this feature:

                      • Enable Rate Limit - Enables the token bucket message rate limiting.
                      • Rate Limit Average - he average message publish rate in number of messages per unit of time (e.g. 10 messages per MINUTE).

                        Danger

                        The maximum allowed message rate is 1 message per millisecond, so the following limitations are applied:

                        • 86400000 per DAY
                        • 3600000 per HOUR
                        • 60000 messages per MINUTE
                        • 1000 messages per SECOND
                        • Rate Limit Time Unit - The time unit for the Rate Limit Average.
                        • Rate Limit Burst Size - The token bucket burst size.

                      The default setup limits the data flow to 1 message per second with a bucket size of 1 token.

                      Warning

                      This feature needs to be properly tuned by the System Administrator in order to prevent delays in the remote cloud platform due to messages stacked at the edge.

                      If not sure of the number of messages that your gateways will try to push to the remote platform, we suggest to disable this feature.

                      "},{"location":"gateway-configuration/data-service-configuration/#connection-schedule","title":"Connection schedule","text":"

                      Starting from Kura 5.3.0, the Data Service supports a configurable time based connection schedule. If this functionality is enabled, the Data Service will connect at specific time instants represented by a configurable Cron expression, keep the connection open until it becomes idle and then disconnect until the next instant that matches the expression.

                      More in detail, the connection logic is as follows:

                      1. The DataService parses the confgiured Cron expression and schedules a connection attempt at the next instant that matches the expression. When the connection instant is reached, the logic continues from step 2.

                      2. The Data Service will start the auto connect logic. One or more connection attempts will be performed until the connection is established honoring the connect.retry-interval parameter.

                      3. The Data Service starts a timer that ticks after connection.schedule.inactivity.interval.seconds seconds. When the timer ticks the connection will be closed, and the logic resumed from step 1. The timer is reset delaying the disconnection when a message is published or confirmed (for QoS >= 1). The connection will not be closed if there are messages with QoS >= 1 that have not been confirmed yet. If an unexpected connection drop occurs in this phase, the logic will resume from step 2.

                      The Data Service will attempt to detect large time shifts in system clock, if a time shift is detected, the logic will switch to step 1, rescheduling the next connection attempt.

                      The relevant configuration parameters are the following:

                      • Enable Connection Schedule: Enables or disables the connection schedule feature. Please note that in order to enable the connection logic, the Connect Auto-on-startup parameter must be set to true as well.

                      • Connection Schedule CRON Expression: A CRON expression that specifies the instants when the gateway should perform a connection attempt. This parameter is only used if Enable Connection Schedule is set to true. The default expression schedules a connection every day at midnight.

                      • Allow priority message to overide connection schedule - Allows messages beyond a specified priority to force a connection and be sent regardless of connection schedule.

                      • Message schedule priority override threshold - A message with a priority equal to or less than this threshold will cause the framework to automatically re-connect and send regardless of the connection schedule.

                      • Connection Schedule Disconnect Inactivity Interval Second: Specifies an inactivity timeout in seconds. If the timeout expires, the cloud connection will be automatically closed. This parameter is only used if Enable Connection Schedule is set to true.

                      "},{"location":"gateway-configuration/data-service-configuration/#payload-size-limiting","title":"Payload size limiting","text":"

                      Starting from Kura 5.3.0, the DataService allows to limit the maximum payload size for published messages. Attempts to publish a payload larger than the configured threshold will fail with a KuraStoreException. The threshold can be configured with the following parameter:

                      • Maximum Payload Size: The maximum allowed size in bytes for the message payload.

                      In order to keep backwards compatibility, the default value for the parameter is set to 16777216 bytes, which is the maximum allowed size by the current H2DbService implementation.

                      "},{"location":"gateway-configuration/data-transport-service-configuration/","title":"Data Transport Service Configuration","text":"

                      The DataTransport service provides the ability to connect to a remote broker, publish messages, subscribe to topics, receive messages on the subscribed topics, and disconnect from the remote message broker. To use this service, select the MqttDataTransport option located in the Cloud Connections area as shown in the screen captures below.

                      The MqttDataTransport service provides the following configuration parameters:

                      • broker-url: defines the URL of the MQTT broker to connect to. For the Everyware Cloud sandbox, this address is either mqtt://broker-sbx.everyware.io:1883/ or mqtts://broker-sbx.everyware.io:8883/ for an encrypted connection. (Required field).

                      • topic.context.account-name: defines the name of the account to which the device belongs.

                      • username and password: define the username and password that have been assigned to the device by the account administrator (generally username is account-name_broker). (Required field).

                      • client-id: defines the identifier of the MQTT client representing the device when connecting to the MQTT broker. If left empty, it is automatically determined by the client software as the MAC address of the main network interface (in general numbers and uppercase letters without ':'). This identifier has to be unique within your account.

                      • keep-alive: defines the \"keep alive\" interval measured in seconds. It specifies the maximum amount of time that should pass without communication between the client and the server. The client will ensure that at least one message travels across the network within each keep alive period. In the absence of a data-related message during this time period, the client will send a very small MQTT \"ping\" message that the server will acknowledge. The keep alive interval enables the client to detect when the server is no longer available without having to wait for the long TCP/IP timeout. (Required field).

                        Warning

                        The keep-alive interval may \"conflict\" with the TCP idle timeout set at the TCP/IP level. As a best practice the TCP idle timeout should be at least 1,5 times the keep-alive time interval. If the TCP idle timeout is less or equal the keep-alive, the MQTT connection may be dropped due to the TCP idle timeout expiration.

                      • timeout: sets the timeout used for all interactions with the MQTT broker. (Required field).

                      • clean-session: controls the behavior of both the client and the server at the time of connection and disconnection. When this parameter is set to true, the state information is discarded at connection and disconnection; when set to false, the state information is maintained. (Required field).

                      • lwt parameters: define the MQTT \"Last Will and Testament\" (LWT) settings for the client. In the event that the client unexpectedly loses its connection to the server, the server publishes the LWT message (lwt.payload) to the LWT topic on behalf of the client. This allows other clients (subscribed to the LWT topic) to be made aware that the client has disconnected. LWT parameters that may be configured include:

                        • lwt.topic
                        • lwt.payload
                        • lwt.qos
                        • lwt.retain
                      • in-flight.persistence: defines the storage type where in-flight messages are persisted across reconnections. They may be stored in memory, or in a file on the disk. (Required field).

                      • protocol-version: defines the MQTT Protocol version to be used. This value may be 3.1 or 3.1.1.

                      • SSL parameters: define the SSL specific settings for the client. SSL parameters that can be configured include:

                        • ssl.default.protocol
                        • ssl.hostname.verification
                        • ssl.default.cipherSuites
                        • ssl.certificate.alias
                      "},{"location":"gateway-configuration/device-information/","title":"Device Information","text":"

                      The Device section provides several information about the gateway where Kura is running on. This section can be accessed by selecting the Device option located in the System area.

                      "},{"location":"gateway-configuration/device-information/#profile","title":"Profile","text":"

                      The Profile tab shows several information about the gateway, organized under the Device, Hardware, Software and Java Information.

                      "},{"location":"gateway-configuration/device-information/#bundles","title":"Bundles","text":"

                      This tab lists all the bundles installed on Kura, with details about the name, version, id, state and signature status. The signature value will be true if the corresponding bundle is signed, false otherwise.

                      The buttons in the upper part of the tab allows the user to manage the listed bundles:

                      • Start Bundle: starts a bundle that is in Resolved or Installed state;
                      • Stop Bundle: stops a bundle that is in Active state;
                      • Refresh: reloads the bundles states list.

                      "},{"location":"gateway-configuration/device-information/#containers","title":"Containers","text":"

                      The Containers tab lists the containers and images that are currently managed by the Container Orchestration Service. From this tab, the user can start and stop containers and delete images.

                      "},{"location":"gateway-configuration/device-information/#threads","title":"Threads","text":"

                      The Threads tab shows a list of the threads that are currently running in the JVM.

                      "},{"location":"gateway-configuration/device-information/#system-packages","title":"System Packages","text":"

                      The System Packages tab shows the list of all the Linux packages installed on the OS. The package is detailed with the name, version and type (DEB/RPM/APK).

                      "},{"location":"gateway-configuration/device-information/#system-properties","title":"System Properties","text":"

                      The System Properties tab shows a list of relevant properties including OS and JVM parameters.

                      "},{"location":"gateway-configuration/device-information/#command","title":"Command","text":"

                      A detailed description of this tab is presented in the Command Service page.

                      "},{"location":"gateway-configuration/device-information/#system-logs","title":"System Logs","text":"

                      The System Logs tab allows downloading a compressed file containing all the relevant log files from the gateway. The download button creates and downloads a compressed file with the following items:

                      • all the files in /var/log or the content of the folder defined by the kura.log.download.sources property;
                      • the content of the journal for the Kura process (kura-journal.log);
                      • the content of the journal for the whole system (system-journal.log).

                      In addition to this feature, the page also allows the real-time displaying of system logs, if the framework has the availability of one or more components that implement the LogProvider API. The UI also provides a useful button to open a new Kura instance in a new browser window. A reference implementation of the LogProvider API is provided in the org.eclipse.kura.log.filesystem.provider bundle. This bundle exposes in the framework a factory that can be used to read filesystem files. By default, Eclipse Kura creates two log providers at startup: one that reads from /var/log/kura.log and the other that reads from /var/log/kura-audit.log.

                      The device logs are stored in a server-side cache and are collected from the point in time where the log providers get attached to the UI (usually, from the login or after a refresh of the browser's window). When the section \"System Logs\" is accessed, the new log entries are polled from the server's cache and stored client-side.

                      Note

                      The default log provider, that is the first log provider shown in the dropdown menu, is filesystem-kura-log. It is used to collect logs from the /var/log/kura.log file. The default value can be changed using the kura.default.log.manager property in the kura.properties file.

                      "},{"location":"gateway-configuration/ethernet-configuration/","title":"Ethernet Configuration","text":"

                      As described in the Network Configuration section, Ethernet interfaces have four configuration tabs: IPv4, IPv6, DHCPv4 & NAT and Advanced. Each Ethernet interface may be configured either as LAN or WAN; it may also be disabled.

                      If the interface is designated as LAN in the IPv4 tab and is manually configured, the DHCPv4 & NAT tab is enabled to allow DHCP server and/or 'many-to-one' NAT setup; otherwise, the DHCPv4 & NAT tab is disabled.

                      For more information on IPv4, IPv6 and DHCPv4 & NAT settings, please refer to the Network Configuration section. For the advanced settings, see the Advanced Settings section.

                      "},{"location":"gateway-configuration/firewall-configuration/","title":"Firewall Configuration","text":"

                      Kura offers easy management of the Linux firewall iptables and ip6tables included in an IoT Gateway. Additionally, Kura provides the ability to manage network access security to an IoT Gateway for both IPv4 and IPv6 through the following:

                      • Open Ports (local service rules)
                      • Port Forwarding
                      • IP Forwarding and Masquerading (NAT service rules)
                      • 'Automatic' NAT service rules

                      Open Ports, Port Forwarding, and IP Forwarding and Masquerading are configured via respective Firewall configuration tabs. 'Automatic' NAT is enabled for each local (LAN) interface using the DHCP & NAT tab of the respective interface configuration. While the IPv4 Firewall configuration capability is present on all IoT Gateways, the IPv6 Firewall is present only on devices that support IPv6 Networking.

                      "},{"location":"gateway-configuration/firewall-configuration/#firewall-linux-configuration","title":"Firewall Linux Configuration","text":"

                      This section describes the changes applied by Kura at the Linux networking configuration. Please read the following note before proceeding with manual changes of the Linux networking configuration.

                      Warning

                      It is NOT recommended performing manual editing of the Linux networking configuration files when the gateway configuration is being managed through Kura. While Linux may correctly accept manual changes, Kura may not be able to interpret the new configuration resulting in an inconsistent state.

                      When a new firewall configuration is submitted, Kura immediately applies it using the iptables service provided by the OS. Moreover, the rules are stored in the filesystem and a new Kura snapshot is generated containing the new configuration. At the next startup, the firewall service in the OS will re-apply them and Kura will check the firewall configuration against the one contained in the last snapshot. In this way, the user can update the snapshot with the needed rules and apply them to the system using the webUI or modify the snapshot_0.xml before the first start of Kura.

                      In order to allow a better coexistence between Kura and external applications that need to modify firewall rules, Kura writes its rules to a set of custom iptables chains. They are input-kura, output-kura, forward-kura, forward-kura-pf and forward-kura-ipf for the filter table and input-kura, output-kura, prerouting-kura, prerouting-kura-pf, postrouting-kura, postrouting-kura-pf and postrouting-kura-ipf for the nat table. The custom chains are then put in their respective standard iptables chains, as shown in the following:

                      iptables -t filter -I INPUT -j input-kura\niptables -t filter -I OUTPUT -j output-kura\niptables -t filter -I FORWARD -j forward-kura\niptables -t filter -I forward-kura -j forward-kura-pf\niptables -t filter -I forward-kura -j forward-kura-ipf\niptables -t nat -I PREROUTING -j prerouting-kura\niptables -t nat -I prerouting-kura -j prerouting-kura-pf\niptables -t nat -I INPUT -j input-kura\niptables -t nat -I OUTPUT -j output-kura\niptables -t nat -I POSTROUTING -j postrouting-kura\niptables -t nat -I postrouting-kura -j postrouting-kura-pf\niptables -t nat -I postrouting-kura -j postrouting-kura-ipf\n

                      The same custom chains are used in the IPv6 case.

                      Even if many firewall rules can be handled by Kura, it could be that some rules cannot be filled through the Web Console. In this case, custom firewall rules may be added to the /etc/init.d/firewall_cust script manually. These rules are applied/reapplied every time the firewall service starts, that is at the gateway startup. These custom rules should not be applied to the Kura custom chains, but to the standard ones.

                      "},{"location":"gateway-configuration/firewall-configuration/#open-ports","title":"Open Ports","text":"

                      If Kura is running on a gateway, all TCP/UDP ports are closed by default unless they are listed in the Open Ports IPv4 or Open Ports IPv6 tab of the Firewall section in the Gateway Administration Console, or in the /etc/sysconfig/iptables script. Therefore, if a user needs to connect to a specific port on a gateway, it is insufficient to have an application listening on the desired port; the port also needs to be opened in the firewall.

                      To open a port using the Gateway Administration Console, select the Firewall option located in the System area. The Firewall configuration display appears in the main window. With the Open Ports IPv4 or Open Ports IPV6 tab selected, click the New button. The New Open Port Entry form appears.

                      The New Open Port Entry form contains the following configuration parameters:

                      • Port: specifies the port to be opened. (Required field.)
                      • Protocol: defines the protocol (tcp or udp). (Required field.)
                      • Permitted Network: only allows packets originated by a host on this network.
                      • Permitted Interface Name: only allows packets arrived on this interface.
                      • Unpermitted Interface Name: blocks packets arrived on this interface.
                      • Permitted MAC Address: only allows packets originated by this host.
                      • Source Port Range: only allows packets with source port in the defined range.

                      Complete the New Open Port Entry form and click the Submit button when finished. Once the form is submitted, a new port entry will appear. Click the Apply button for the change to take effect.

                      The firewall rules related to the open ports section are stored in the input-kura custom chain of the IPv4/IPv6 filter table.

                      "},{"location":"gateway-configuration/firewall-configuration/#port-forwarding","title":"Port Forwarding","text":"

                      Port forwarding rules are needed to establish connectivity from the WAN side to a specific port on a host that resides on a LAN behind the gateway. In this case, a routing solution may be avoided since the connection is made to a specified external port on a gateway, and packets are forwarded to an internal port on the destination host; therefore, it is not necessary to add the external port to the list of open ports.

                      To add a port forwarding rule, select the Port Forwarding IPv4 or Port Forwarding IPv6 tab on the Firewall display and click the New button. The Port Forward Entry form appears.

                      The Port Forward Entry form contains the following configuration parameters:

                      • Input Interface: specifies the interface through which a packet is going to be received. (Required field).

                      • Output Interface: specifies the interface through which a packet is going to be forwarded to its destination. (Required field).

                      • LAN Address: supplies the IP address of destination host. (Required field).

                      • Protocol: defines the protocol (tcp or udp). (Required field).

                      • External Port: provides the external destination port on gateway unit. (Required field).

                      • Internal Port: provides the port on a destination host. (Required field).

                      • Enable Masquerading: defines whether masquerading is used (yes or no). If enabled, the gateway replaces the IP address of the originating host with the IP address of its own output (LAN) interface. This is needed when the destination host does not have a back route to the originating host (or default gateway route) via the gateway unit. The masquerading option is provided with port forwarding to limit gateway forwarding only to the destination port. (Required field).

                      • Permitted Network: only forwards if the packet is originated from a host on this network.

                      • Permitted MAC Address: only forwards if the packet is originated by this host.

                      • Source Port Range: only forwards if the packet's source port is within the defined range.

                      Complete the Port Forward Entry form and click the Apply button for the desired port forwarding rules to take effect.

                      The firewall rules related to the port forwarding section are stored in the forward-kura-pf custom chain of the IPv4/IPv6 filter table and in the postrouting-kura-pf and prerouting-kura-pf chains of the IPv4/IPv6 nat table.

                      "},{"location":"gateway-configuration/firewall-configuration/#port-forwarding-example","title":"Port Forwarding example","text":"

                      This section describes an example of port forwarding rules applied to the IPv4 case. The initial setup is described below.

                      • A couple of RaspberryPi that shares the same LAN over Ethernet.

                      • The first RaspberryPi running Kura is configured as follows:

                        • The eth0 interface static with IP address of 172.16.0.5.
                        • There is no default gateway.
                      • The second RaspberryPi running Kura is configured as follows:

                        • The eth0 interface LAN/static with IP address of 172.16.0.1/24 and no NAT.
                        • The wlan0 interface is WAN/DHCP client.
                      • A laptop is connected to the same network of the wlan0 of the second RaspberryPi and can ping its wlan0 interface.

                      The purpose of the second RaspberryPi configuration is to enable access to the Administration Console running on the first one (port 80) by connecting to the second RaspberryPi's port 8080 over the wlan. This scenario assumes that IP addresses are assigned as follows:

                      • Second RaspberryPi wlan0 - 10.200.12.6

                      • Laptop wlan0 - 10.200.12.10

                      The following port forwarding entries are added to the second RaspberryPi configuration as described above using the Port Forward Entry form:

                      • Input Interface - wlan0

                      • Output Interface - eth0

                      • LAN Address - 172.16.0.5

                      • Protocol - tcp

                      • External Port - 8080

                      • Internal Port - 80

                      • Masquerade - yes

                      The Permitted Network, Permitted MAC Address, and Source Port Range fields are left blank.

                      The following iptables rules are applied and added to the /etc/sysconfig/iptables file:

                      iptables -t nat -A prerouting-kura-pf -i wlan0 -p tcp -s 0.0.0.0/0 --dport 8080 -j DNAT --to 172.16.0.5:80\niptables -t nat -A postrouting-kura-pf -o eth0 -p tcp -d 172.16.0.5 -j MASQUERADE\niptables -A forward-kura-pf -i wlan0 -o eth0 -p tcp -s 0.0.0.0/0 --dport 80 -d 172.16.0.5 -j ACCEPT\niptables -A forward-kura-pf -i eth0 -o wlan0 -p tcp -s 172.16.0.5 -m state --state RELATED,ESTABLISHED -j ACCEPT\n

                      The following iptables commands may be used to verify that the new rules have been applied:

                      sudo iptables -v -n -L\nsudo iptables -v -n -L -t nat\n

                      At this point, it is possible to try to connect to http://10.200.12.6 and to http://10.200.12.6:8080 from the laptop. Note that when a connection is made to the device on port 80, it is to the Kura configuration page on the device itself (the second RaspberryPi). When the gateway is connected on port 8080, you are forwarded to the Kura Gateway Administration Console on the first RaspberryPi. The destination host can only be reached by connecting to the gateway on port 8080.

                      Another way to connect to the Kura Gateway Administration Console on the first RaspberryPi would be to add an IP Forwarding/Masquerading entry as described in the next section.

                      "},{"location":"gateway-configuration/firewall-configuration/#ip-forwardingmasquerading","title":"IP Forwarding/Masquerading","text":"

                      The advantage of the Automatic NAT method is its simplicity. However, this approach does not handle reverse NATing, and it cannot be used for interfaces that are not listed in the Gateway Administration Console (such as VPN tun0 interface). To set up generic (one-to-many) NATing, select the IP Forwarding/Masquerading IPv4 or IP Forwarding/Masquerading IPv6 tab on the Firewall display. Press the New button and the IP Forwarding/Masquerading form appears.

                      The IP Forwarding/Masquerading form contains the following configuration parameters:

                      • Input Interface: specifies the interface through which a packet is going to be received. (Required field).

                      • Output Interface: specifies the interface through which a packet is going to be forwarded to its destination. (Required field).

                      • Protocol: defines the protocol of the rule to check (all, tcp, or udp). (Required field).

                      • Source Network/Host: identifies the source network or host name (CIDR notation). Set to IPv4 0.0.0.0/0 or IPv6 ::/0 if empty.

                      • Destination Network/Host: identifies the destination network or host name (CIDR notation). Set to IPv4 0.0.0.0/0 or IPv6 ::/0 if empty.

                      • Enable Masquerading: defines whether masquerading is used (yes or no). If set to 'yes', masquerading is enabled. If set to 'no', only FORWARDING rules are be added. (Required field).

                      The rules will be added to the forward-kura-ipf chain in the IPv4/IPv6 filter table and in the postrouting-kura-ipf one in the IPv4/IPv6 nat table.

                      As a use-case scenario, consider the same setup as in port forwarding, but with cellular interface disabled and eth1 interface configured as WAN/DHCP client. In this case, the interfaces of the gateway are configured as follows:

                      • eth0: LAN/Static/No NAT 172.16.0.1/24

                      • eth1: WAN/DHCP 10.11.5.4/24

                      To reach the gateway sitting on the 172.16.0.5/24 from a specific host on the 10.11.0.0/16 network, set up the following Reverse NAT entry:

                      • Input Interface: eth1 (WAN interface)

                      • Output Interface: eth0 (LAN interface)

                      • Protocol: all

                      • Source Network/Host: 10.11.5.21/32

                      • Destination Network/Host: 172.16.0.5/32

                      • Enable Masquerading: yes

                      This case applies the following iptables rules:

                      iptables -t nat -A postrouting-kura-ipf -p tcp -s 10.11.5.21/32 -d 172.16.0.5/32 -o eth0 -j MASQUERADE\niptables -A forward-kura-ipf -p tcp -s 172.16.0.5/32 -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A forward-kura-ipf -p tcp -s 10.11.5.21/32 -d 172.16.0.5/32 -i eth1 -o eth0 -m tcp -j ACCEPT\n

                      Additionally, a route to the 172.16.0.0/24 network needs to be configured on a connecting laptop as shown below:

                      sudo route add -net 172.16.0.0 netmask 255.255.255.0 gw 10.11.5.4\n

                      Since masquerading is enabled, there is no need to specify the back route on the destination host. Note that with this setup, the gateway only forwards packets originating on the 10.11.5.21 laptop to the 172.16.0.5 destination.

                      If the Source Network/Host and Destination Network/Host fields are empty, iptables rules appear as follows:

                      iptables -t nat -A postrouting-kura-ipf -p tcp -s 0.0.0.0/0 -d 0.0.0.0/0 -o eth0 -j MASQUERADE\niptables -A forward-kura-ipf -p tcp -s 0.0.0.0/0 -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A forward-kura-ipf -p tcp -s 0.0.0.0/0 -d 0.0.0.0/0 -i eth1 -o eth0 -j ACCEPT\n

                      The gateway forwards packets from any external host (connected to eth1) to any destination on the local network (eth0 interface).

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/","title":"Gateway Administration Console Authentication","text":"

                      The Gateway Administration Console supports multiple login identities with associated permissions and HTTPS client side authentication with certificates.

                      The identity and permission configuration and credentials is stored externally the UserAdmin service (see Authentication and Authorization for more details).

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#permissions","title":"Permissions","text":"

                      The Gateway Administration Console defines the following permissions, that allow to restrict the operations that an identity is allowed to perform:

                      • kura.cloud.connection.admin: Allows to manage cloud connections using Cloud Connections tab.
                      • kura.packages.admin: Allows to install deployment packages using the Packages tab.
                      • kura.device: Allows to interact with the Device and Status tabs.
                      • kura.network.admin: Allows to manage network connectivity and firewall configuration using the Network and Firewall tabs.
                      • kura.wires.admin: Allows to manage Wire Graph and Driver and Asset configurations using the Wires and Drivers and Assets tabs.
                      • kura.admin: This permission implies all other permissions, including the ones defined by external applications.
                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#default-identities","title":"Default identities","text":"

                      Kura provides the following identities by default:

                      Name Password Permissions admin admin kura.admin appadmin appadmin kura.cloud.connection.admin, kura.packages.admin, kura.wires.admin netadmin netadmin kura.cloud.connection.admin, kura.device, kura.network.admin

                      It is possible to modify/remove the default identity configuration.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#login","title":"Login","text":"

                      The login screen can be accessed by entering the https://${device.ip} URL in a browser window, where ${device.ip} is the IP address. Replace https with http if HTTPS support has been disabled on the gateway.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#identity-name-and-password","title":"Identity name and password","text":"

                      In order to login with identity name and password, select Password as authentication method and enter the credentials.

                      Note

                      Password authentication method might not be available if it has been disabled by the administrator using the Security -> Web Console section.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#forced-password-change-on-login","title":"Forced password change on login","text":"

                      Kura supports forcing an identity to change the password at login, before accessing the Administration Console. This functionality is enabled by default after a fresh installation. At login the following prompt will appear forcing the user to define a new password.

                      This functionality can be configured by an admin identity using the Identities section of the Administration Console.

                      The forced password change can also be disabled by editing the snapshot_0.xml file removing the kura.need.password.change property for the desired identity in the org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl configuration before Kura first boot. If this functionality is enabled, REST API username and password authentication is will be disabled for the specific identity until the password is updated, REST API certificate authentication will still work.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#certificate-authentication","title":"Certificate authentication","text":"

                      In order to perform HTTPS certificate authentication, select the Certificate authentication method and click Login, the browser may prompt to select the certificate to use.

                      Note

                      Certificate authentication method might not be available if no HTTPS with client authentication ports have been configured or if it has been explicitly disabled by the administrator using the Security -> Web Console section.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#identity-and-permission-management","title":"Identity and Permission management","text":"

                      The Gateway Administration Console allows the management of identity and permission configuration in a dedicated view, accessible by navigating to the Identities section:

                      The section above allows to:

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#create-new-identities","title":"Create new identities","text":"

                      New identities can be created by clicking the New Identity button.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#remove-existing-identities","title":"Remove existing identities","text":"

                      Existing identities can be removed by selecting the corresponding entry in the list and pressing the Delete Identity button.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#manage-password-authentication","title":"Manage password authentication","text":"

                      Password authentication can be enabled or disabled by changing the Password authentication enabled parameter. Changing this parameter will not modify the existing stored password. Enabling password authentication for a new identity requires to define a new password. The password can be set/modified by clicking the Change password button.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#assign-or-remove-permissions","title":"Assign or remove permissions","text":"

                      Permissions can be assigned or removed by ticking the corresponding entries in the Permissions table. No changes will be applied to the gateway until the Apply button is pressed.

                      "},{"location":"gateway-configuration/gateway-administration-console-authentication/#certificate-based-authentication","title":"Certificate based authentication","text":"

                      The Gateway Administration Console supports HTTPS certificate based client side authentication. The authentication process works as follows:

                      • One or more Https client certificate must be added to keystore, this can be done using the Certificate Management section.

                      • The user must provide a certificate or certificate chain signed by one of the CAs added as Https client certificate.

                      • The common name field of the leaf certificate provided by the user must be the name of the identity that should be used for the session.

                      • HTTPS with certificate based authentication must be enabled in the HTTP/HTTPS Configuration section.

                      Log in with certificate can be performed by selecting the Certificate authentication method in Gateway Administration Console login screen or by connecting directly to the HTTPS port with client side authentication specified in gateway configuration.

                      "},{"location":"gateway-configuration/gateway-administration-console/","title":"Gateway Administration Console","text":""},{"location":"gateway-configuration/gateway-administration-console/#accessing-the-kura-gateway-administration-console","title":"Accessing the Kura Gateway Administration Console","text":"

                      Kura provides a web-based, user interface for the administration and management of your IoT gateway. The Kura Gateway Administration Console enables you to monitor the gateway status, manage the network configuration, and manage the installed application and services. Access to the Kura Gateway Administration Console requires that a unit running Eclipse Kura is reachable via its Ethernet primary interface.

                      Connections on HTTP port 443 for these interfaces are allowed by default through the built-in firewall. The Kura Gateway Administration Console can be accessed by typing the IP address of the gateway into the browser's URL bar. Once the URL is submitted, the user is required to log in and is then redirected to the Administration Console (e.g., https://192.168.2.8/admin/console) shown in the screen capture below. The default login name and password is admin/admin.

                      Warning

                      It is recommended to change the default password after initial setup and before deployment, as well as limiting access to the Administration Console to a trusted local network interface using appropriate firewall rules.

                      "},{"location":"gateway-configuration/gateway-administration-console/#password-change","title":"Password change","text":"

                      Once logged in, the user can modify its password (recommended after the first login). To access the option, click on the button near the username in the header section. A dropdown menu appears with the logout and the password modification options. When clicking on \"Change password\", the following dialog will appear:

                      After confirming the changes, the user will be logged out.

                      "},{"location":"gateway-configuration/gateway-administration-console/#accessing-the-kura-gateway-administration-console-over-a-cellular-link","title":"Accessing the Kura Gateway Administration Console over a Cellular Link","text":"

                      In order to connect to the Gateway Administration Console via a cellular interface, the following requirements must be met:

                      • The service plan must allow for a static, public IP address to be assigned to the cellular interface.
                      • The used ports must not be blocked by the provider.
                      • The user must add Open Port entries for the cellular interface. This may be done either through the Firewall tab.

                      If some of the used ports are blocked by the service provider, there is an option to reconfigure the gateway to use another port (i.e., 8080). In order to do so, the following requirements must be met:

                      • The HttpService configuration must be changed to use the new ports.
                      • The new ports must be open in the firewall for all network interfaces.
                      "},{"location":"gateway-configuration/gateway-administration-console/#https-related-warnings","title":"HTTPS related warnings","text":"

                      Most browsers will probably warn the user that the connection is not secure when Gateway Administration Console is accessed using HTTPS.

                      In order to remove the warning, the browser must be able to verify the identity of the gateway as an HTTPS server. The verification process will fail with default server certificate provided by Kura because it is self-signed and it is not suitable for hostname verification.

                      Fixing this might require to configure the browser to trust the certificate provided by the gateway and/or using a server certificate signed by a CA trusted by the browser and assigning a DNS name to the gateway in order to pass hostname verification.

                      "},{"location":"gateway-configuration/gateway-administration-console/#system-use-notification-banner","title":"System Use Notification Banner","text":"

                      For security reasons, it may be needed to display to the user a banner that describes the intended system use before authenticating. The system use notification message is customisable by authorised personnel in the Security section of the Kura Web UI, in the Web Console tab.

                      "},{"location":"gateway-configuration/gateway-status/","title":"Gateway Status","text":"

                      The status of the gateway may be viewed from the Status window, which is accessed by selecting the Status option located in the System area. The Status window provides a summary of the key information regarding the status of the gateway including its IoT Cloud connection and network configuration.

                      The values reported in the page can be reloaded using the Refresh button. This will read the current values from the system and update the page. Since the update procedure can take time, the update can be performed at most every 30 seconds.

                      "},{"location":"gateway-configuration/gateway-status/#cloud-services","title":"Cloud Services","text":"

                      This section provides a summary of the IoT Cloud connection status including the following details:

                      • Account - defines the name of the account used by the MqttDataTransport service when an MQTT connection is opened.
                      • Broker URL - defines the URL of the MQTT broker.
                      • Client ID - specifies the client identifier used by the MqttDataTransport service when an MQTT connection is opened.
                      • Service Status - provides the status of the DataService and DataTransport connection. Valid values are CONNECTED or DISCONNECTED.
                      • Username - supplies the name of the user used by the MqttDataTransport service when an MQTT connection is opened.
                      "},{"location":"gateway-configuration/gateway-status/#ethernet-wireless-and-cellular-settings","title":"Ethernet, Wireless, and Cellular Settings","text":"

                      This section provides information about the currently configured network interfaces.

                      "},{"location":"gateway-configuration/gateway-status/#position-status","title":"Position Status","text":"

                      This section provides the GPS status and latest known position (if applicable) including the following details:

                      • Longitude - longitude as reported by the PositionService in radians.
                      • Latitude - latitude as reported by the PositionService in radians.
                      • Altitude - altitude as reported by the PositionService in meters.

                      Warning

                      The status reported in the page may not be synchronized with the real state of the system. In this case, use the Refresh button to updated the values in the page.

                      "},{"location":"gateway-configuration/keys-and-certificates/","title":"Keys and Certificates","text":"

                      The framework manages directly different key pairs and trusted certificates from different keystores. To simplify the management of such complex objects, the framework provides a dedicated section of its Administrative Web UI, a set of REST APIs for local management and a request handler (KEYS-V1 and KEYS-V2) for cloud remote interaction.

                      "},{"location":"gateway-configuration/keys-and-certificates/#web-ui","title":"Web UI","text":"

                      The Certificates List tab in the Security section of the Kura Web UI provides a simple way for the user to get the list of all the managed keys and certificates of the framework:

                      The page allows the user to add a new Keypair or trusted certificate or to delete an existing element.

                      Every key pair or trusted certificate is listed by its alias, identified by the corresponding type and further identified by the keystore that is managing that element.

                      If the user needs to add a new entry to one of the managed KeystoreService instances, can click on the Add button on the top left part of the page. The user will be guided through a process that will allow to identify the type of entry to add:

                      It can be either a:

                      • Private/Public Key Pair
                      • Trusted Certificate

                      If the user decides to add a key pair, then the wizard will provide a page like the following:

                      Here the user can specify: - Key Store: the KeystoreService instance that will store and maintain the key pair - Storage Alias: the alias that will be used to identify the key pair - Private Key: the private key part of the key pair - Certificate: the public key part of the key pair

                      After clicking on the Apply button, the new entry will be stored in the selected Keystore and listed along the other entries managed by the framework.

                      The following cryptographic algorithms are supported for Key Pairs: - RSA - DSA

                      Instead, if the user wants to load a Trusted Certificate, the Ui will change as follows:

                      Here the user can specify: - Key Store: the KeystoreService instance that will store and maintain the trusted certificate - Storage Alias: the alias that will be used to identify the trusted certificate - Certificate: the trusted certificate

                      The following cryptographic algorithms are supported for Trusted Certificates: - RSA - DSA - EC

                      "},{"location":"gateway-configuration/keys-and-certificates/#rest-apis","title":"REST APIs","text":""},{"location":"gateway-configuration/keys-and-certificates/#keystoresv1","title":"keystores/v1","text":"

                      The org.eclipse.kura.core.keystore bundle exposes a REST endpoint under the /services/keystores/v1 path. The Kura REST APIs for Keys and Certificates support the following calls and are allowed to any user with rest.keystores permission.

                      Method Path Roles allowed Encoding Request parameters Description GET / keystores JSON None Returns the list of all the KeystoreService instances. GET /entries keystores JSON None Returns the list of all the entries managed by the KeystoreService instances. GET /entries?keystoreServicePid={keystoreServicePid} keystores JSON keystoreServicePid Returns the list of all the entries managed by the specified KeystoreService instance. GET /entries?alias={alias} keystores JSON alias Returns the list of all the entries specified by the defined alias and managed in all the available KeystoreService instances in the framework. GET /entries/entry?keystoreServicePid={keystoreServicePid}&alias={alias} keystores JSON keystoreServicePid and alias Returns the entry identified by the specified keystoreServicePid and alias. POST /entries/csr keystores JSON The reference to the key pair in a specified KeystoreService instance that will be used to generate the CSR. The request has to be associated with additional parameters that identify the algorithm used to compute and sign the CSR and the DN or the corresponding public key that needs to be countersigned. Generates a CSR for the specified key pair in the specified KeystoreService instance, based on the parameters provided in the request. POST /entries/certificate keystores JSON The reference to the KeystoreService instance and the alias that will be used for storage. A type filed identifies the type of key that needs to be managed. This request allows the user to upload a TrustedCertificate. POST /entries/keypair keystores JSON To generate a new KeyPair directly in the device, the request format need to follow the references in the following paragraphs. This request allows the user to generate a new KeyPair into the device. DELETE /entries keystores JSON A JSON identifying the resource to delete. The format of the request is described in in one of the following sections. Deletes the entry in the specified KeystoreService instance."},{"location":"gateway-configuration/keys-and-certificates/#list-all-the-keystoreservices","title":"List All the KeystoreServices","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1

                      Response:

                      [\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"type\": \"jks\",\n        \"size\": 4\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.crypto.CryptoService\",\n        \"type\": \"jks\",\n        \"size\": 3\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    },\n    {\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.DMKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    }\n]\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-managed-entries","title":"Get all the Managed Entries","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries

                      Response:

                      [\n    {\n        \"subjectDN\": \"OU=Go Daddy Class 2 Certification Authority, O=\\\"The Go Daddy Group, Inc.\\\", C=US\",\n        \"issuer\": \"OU=Go Daddy Class 2 Certification Authority,O=The Go Daddy Group\\\\, Inc.,C=US\",\n        \"startDate\": \"Tue, 29 Jun 2004 17:06:20 GMT\",\n        \"expirationDate\": \"Thu, 29 Jun 2034 17:06:20 GMT\",\n        \"algorithm\": \"SHA1withRSA\",\n        \"size\": 2048,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"alias\": \"ca-godaddyclass2ca\",\n        \"type\": \"TRUSTED_CERTIFICATE\"\n    },\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-entries-by-keystoreservice","title":"Get All the Entries by KeystoreService","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries?keystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore

                      Response:

                      [\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"certificateChain\": [\n            \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>\\n-----END CERTIFICATE-----\"\n        ],\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#get-all-the-entries-by-alias","title":"Get All the Entries by Alias","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries?alias=localhost

                      Response:

                      [\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"certificateChain\": [\n            \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>\\n-----END CERTIFICATE-----\"\n        ],\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#get-specific-entry","title":"Get Specific Entry","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries/entry?keystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore&alias=localhost

                      Response:

                      {\n    \"algorithm\": \"RSA\",\n    \"size\": 4096,\n    \"certificateChain\": [\n        \"-----BEGIN CERTIFICATE-----\\n<CERTIFICATE>-----END CERTIFICATE-----\"\n    ],\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\": \"localhost\",\n    \"type\": \"PRIVATE_KEY\"\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#get-the-csr-for-a-keypair","title":"Get the CSR for a KeyPair","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries/csrkeystoreServicePid=org.eclipse.kura.core.keystore.HttpsKeystore&alias=localhost`

                      Request body:

                      { \n    \"keystoreServicePid\":\"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\":\"localhost\",\n    \"signatureAlgorithm\" : \"SHA256withRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#store-trusted-certificate","title":"Store Trusted Certificate","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries/certificate

                      Request body:

                      {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"myCertTest99\",\n    \"certificate\":\"-----BEGIN CERTIFICATE-----\n        <CERTIFICATE>\n        -----END CERTIFICATE-----\"\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#generate-keypair","title":"Generate KeyPair","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries/keypair

                      Request body:

                      {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"algorithm\" : \"RSA\",\n    \"size\": 1024,\n    \"signatureAlgorithm\" : \"SHA256WithRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#delete-entry","title":"Delete Entry","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v1/entries

                      Request body:

                      {\n    \"keystoreServicePid\" : \"MyKeystore\",\n    \"alias\" : \"mycerttestec\"\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#keys-v1-request-handler","title":"KEYS-V1 Request Handler","text":"

                      Mapping the previously defined REST APIs, the framework exposed to the remote cloud platforms a request handler named KEYS-V1 that allows the remote user to list and manage the keystores, the keys and the certificates in the framework.

                      The request handler exposes also the capability to generate on the edge a CSR that can be countersigned remotely by a trusted CA.

                      "},{"location":"gateway-configuration/keys-and-certificates/#keystoresv2","title":"keystores/v2","text":"

                      Starting from Kura 5.4.0, the keystores/v2 REST API is also available, it supports all of the request endpoints of keystores/v1 plus an additional endpoint that allows to upload and modify private key entries:

                      Method Path Roles allowed Encoding Request parameters Description POST /entries/privatekey keystores JSON To upload a new private key entry directly in the device, the request format need to follow the references in the following paragraphs. This request allows the user to upload a new private key entry into the device or to modify the certificate chain of an existing one."},{"location":"gateway-configuration/keys-and-certificates/#uploading-a-private-key-entry","title":"Uploading a Private Key Entry","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v2/entries/privatekey

                      Request body:

                      The request should include the private key in unencrypted PEM format and the certificate chain in PEM format, the first certificate in the certificateChain list must use the public key associated with the private key supplied as the privateKey parameter.

                      The device will overwrite the entry with the provided alias if it already exists.

                      WARINING: Please use this endpoint through a secure connection.

                      {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"privateKey\":\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                      "},{"location":"gateway-configuration/keys-and-certificates/#updating-a-private-key-entry","title":"Updating a Private Key Entry","text":"

                      Request: URL - https://<gateway-ip>/services/keystores/v2/entries/privatekey

                      Request body:

                      In order to update the certificate chain associated to a specific private key entry it is possible to use the same format as previous request and omit the privateKey parameter.

                      In this case the certificate chain of the existing entry will be replaced with the one specified in the request and the existing private key will be retained.

                      This request can be useful for example to create a CSR on the device, sign it externally and then updating the corresponding entry with the resulting certificate.

                      {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                      "},{"location":"gateway-configuration/keystores-management/","title":"Keystores Management","text":"

                      The framework manages different types of cryptographic keys and certificates. In order to simplify the interaction with those objects, Kura provides a KeystoreService API and a specific section in the Kura Web UI that lists all the available KeystoreService instances.

                      From the Security section, a user with Security permissions can access the Keystore Configuration section. A list of all the framework managed keystores will be available to the user with the Service PID that will be used by other components to reference the selected keystore. Associated to the Service PID, the UI shows the Factory PID that identifies the specific KeystoreService API implementation that is providing the service to the framework.

                      In order to modify the configuration of a specific keystore service instance, the user can select one of the available rows, obtaining the corresponding keystore service configuration.

                      The following KeystoreService factories are available:

                      "},{"location":"gateway-configuration/keystores-management/#filesystemkeystoreserviceimpl","title":"FilesystemKeystoreServiceImpl","text":"

                      The org.eclipse.kura.core.keystore.FilesystemKeystoreServiceImpl factory provides a KeystoreService implementation that stores the private keys and certificates as a file. The user can customise the following options:

                      • Keystore Path: identifies the path in the filesystem. If the keystore file does not exists, a new file will be created. The value cannot be empty.
                      • Keystore Password: the corresponding keystore password.
                      • Randomize Password: a boolean flag that allows the user to specify if the keystore password needs to be randomised at the next framework boot. If set true, the framework will try to access the identified keystore and randomise the password. The new password will be persisted in the framework snapshot. Once successfully randomised, the flag will be automatically set to false by the framework.
                      "},{"location":"gateway-configuration/keystores-management/#pkcs11keystoreserviceimpl","title":"PKCS11KeystoreServiceImpl","text":"

                      The org.eclipse.kura.core.keystore.PKCS11KeystoreServiceImpl factory provides a KeystoreService implementation that allows to access a PKCS11 token through the SunPKCS11 implementation.

                      At the moment this type of KeystoreService provides read only access to the underlying token, operations such as adding or removing entries will fail.

                      It is possible to use the entries provided by a PKCS11KeystoreServiceImpl for SSL authentication.

                      The available configuration options closely match the parameters provided by the SunPKCS11 implementation, see the official documentation for more details.

                      In particular, the official documentation contains a section that explains how the PKCS11 objects are mapped to Java KeyStore entries.

                      The only required parameter is the PKCS11 Implementation Library Path parameter. It is usually also necessary to specify the token user pin as the Pin parameter.

                      The configuration parameters are mapped to the SunPKCS11 provider parameters in the following way:

                      Kura Parameter SunPKCS11 Parameter Notes Slot slot Slot List Index slotListIndex Enabled Mechanisms enabledMechanisms The curly braces must be omitted Disabled Mechanisms disabledMechanisms The curly braces must be omitted. Attributes attributes The value of this field will be appended to the provider configuration."},{"location":"gateway-configuration/network-advanced/","title":"Advanced Network Settings","text":"

                      The Advanced configuration tab is designed for users who seek a higher level of control and customization over their network experience. Whether you are an IT professional, network administrator, or enthusiast looking to fine-tune specific aspects of your network, this tab provides access to advanced parameters like Maximum Transmission Unit (MTU) and Promiscuous Mode. Adjusting these settings allows for optimized performance in unique network environments, troubleshooting capabilities, and enhanced security monitoring. Please exercise caution when modifying these settings, ensuring that you have a clear understanding of their implications on your network's behavior.

                      Tip

                      Some parameters requires a minimum version of NetworkManager, as described below and in Kura UI. The System Component Inventory can be used to check what version is available on your device.

                      The Advanced tab allow configuration of:

                      • MTU (Maximum Transmission Unit)
                        • MTU determines the maximum size of data packets transmitted over your network. While most users can leave this at the default setting, adjusting the MTU may be necessary for optimizing performance in specialized environments or overcoming network constraints.
                        • Use Case: Modify MTU if you encounter issues related to packet size, especially in scenarios involving VPNs, specific network equipment, or other unique requirements.
                      • Promiscuous Mode
                        • Promiscuous mode is a specialized network mode where your device captures and analyzes all network packets, regardless of their destination. This is particularly useful for network monitoring, troubleshooting, and security analysis.
                        • Use Case: Enable promiscuous mode when you need to closely inspect network traffic, diagnose connectivity issues, or enhance your device's capabilities for security monitoring.
                      "},{"location":"gateway-configuration/network-advanced/#advanced-configuration","title":"Advanced Configuration","text":"

                      The Advanced tab contains the following configuration parameters:

                      • IPv4 MTU - defines the Maximum Trasmission unit for IPv4 traffic on the selected interface, a value of 0 or empty delegates the MTU definition to the link layer.
                      • IPv6 MTU - defines the Maximum Trasmission unit for IPv6 traffic on the selected interface, a value of 0 or empty delegates the MTU definition to the link layer. Requires NetworkManager 1.40 or above.

                      Warning

                      The MTU value for a VLAN is limited by the configured MTU of its parent interface as an upper bound.

                      • Promiscuous Mode - enable promiscuous mode for the selected interface; Requires NetworkManager 1.32 or above.
                        • System default - This delegates to the underlying OS setting. In most cases the system default equates to Disabled
                        • Enabled
                        • Disabled
                      "},{"location":"gateway-configuration/network-configuration/","title":"Network Configuration","text":"

                      To configure the gateway network interfaces using the Gateway Administration Console, select the Network option located in the System area. With this option selected, the Network display appears with a list of available interfaces. Configuration tabs for the selected interface appear on the right side of the screen. By default, the loopback (lo) interface is selected when the network interfaces are displayed. Choose the desired network interface (e.g., eth0, eth1, wlan0, ppp0) and apply the necessary configuration changes using the tabs on the right. Submit the modified configuration by clicking the Apply button.

                      In case of typing errors, the Reset button can be used to reload the prior configuration on the screen. Since the network configuration shown on the screen may not be synchronized with the current state of the system, it can be updated pressing the Refresh button. This can be used also to force the reload of specific parameters like the RSSI or dynamic IP addresses. The refresh procedure reads all the needed parameters from the system and can take several seconds before updating.

                      Tip

                      It is recommended that the IPv4 or IPv6 tab is configured first since it defines how the interface is going to be used.

                      "},{"location":"gateway-configuration/network-configuration/#tcpip-configuration","title":"TCP/IP Configuration","text":"

                      The IPv4 and IPv6 tabs contain the following configuration parameters:

                      • Status
                        • Disabled: disables the selected interface (i.e., administratively down).
                        • Enabled for LAN: designates the interface for a local network. It can be set as a DHCP server for hosts on the local network and can serve as a default gateway for those hosts; however, it cannot be set as an actual gateway interface for this device. That is, packets must be routed from this interface to another interface that is configured as WAN. The interface is automatically brought up at boot.
                        • Enabled for WAN: designates the interface as a gateway to an external network. The interface is automatically brought up at boot.
                        • Not Managed: the interface will be ignored by Kura (available only for IPv4).
                        • Layer 2 Only: only the Layer 2 portion of the interface will be configured. The interface is automatically brought up at boot (available only for IPv4).
                      • WAN Priority - configure the network failover. See here for more details.
                      • Configure
                        • Manually: allows manual entry of the IP Address and Netmask fields, if the interface is configured as LAN; allows manual entry of the IP Address, Netmask, Gateway, and DNS Servers fields, if the interface is designated as WAN.
                        • Using DHCP/DHCPv6: configures the interface as a DHCP client obtaining the IP address from a network DHCP server.
                        • Stateless Address Auto-Configuration (SLAAC): automatically assign an IP address (available only for IPv6).
                      • Address Generation Mode - defines the method used to automatically generate the IP address with SLAAC (available only for IPv6)
                        • EUI64: use EUI-64 method for address generation
                        • Stable-privacy: use RFC7217 method for address generation
                      • IP Address - defines the IP address of the interface, if manually configured.
                      • Subnet Mask - defines the subnet mask of the interface, if manually configured.
                      • Gateway - specifies the default gateway for the unit. (Required field if the interface is designated as WAN and manually configured.)
                      • DNS Servers - provides a list of DNS servers, if the interface is designated as WAN and is manually configured.
                      • Privacy - configure privacy extension for SLAAC (available only for IPv6).
                        • Disabled: disables the privacy extension.
                        • Prefer public addresses: use public address for outgoing traffic.
                        • Prefer temporary addresses: prefer temporary address for outgoing traffic.

                      If the network interface is configured as Enabled for LAN and manually configured (i.e., not a DHCP client) in the IPV4 tab, the DHCPv4 & NAT tab allows the DHCP server to be configured and/or NAT (IP forwarding with masquerading) to be enabled.

                      "},{"location":"gateway-configuration/network-configuration/#more-details-about-the-not-managed-interface-status-tbd-not-applicable-in-nm","title":"More details about the Not Managed interface Status - (TBD: not applicable in NM)","text":"

                      When a network interface is configured as Not Managed, Kura will ignore it and the configuration will not be touched. The user can configure the interface with the network tools provided by the OS, allowing unusual network setups.

                      Regarding DNS, both Kura and the external tools store the DNS addresses in the /etc/resolv.conf file. So, if multiple interfaces are configured to get the DNS information and store it in the same file, the device can be misconfigured. To avoid that, the following table presents who is responsible to update the DNS file depending on the network interfaces configurations.

                      Is there at least an interface set as WAN? Is there at least one interface set as Not Managed? Does Kura manage resolv.conf? NO NO YES NO YES NO YES NO YES YES YES YES

                      So, the only way to configure the DNS addresses with external tools, is to configure at least one interface as Not Managed and not to set any interface as Enabled For Wan using Kura. If at least one WAN interface is configured by Kura, it will take the control of the /etc/resolv.conf/ file. Finally, if any interface is configured in Enabled For Wan or Not Managed mode, Kura will empty the file.

                      To avoid device misconfigurations when Not Managed interfaces are used, don't use the dns-nameservers directive in the /etc/network/interfaces file. Please add the DNS addresses directly to the /etc/resolv.conf file.

                      "},{"location":"gateway-configuration/network-configuration/#dhcpv4-nat-configuration","title":"DHCPv4 & NAT Configuration","text":"

                      The DHCPv4 & NAT tab contains the following configuration parameters:

                      • Router Mode
                        • DHCP and NAT: indicates that both DHCP server and NAT are enabled.
                        • DHCP Only: indicates that DHCP server is enabled and NAT is disabled.
                        • NAT Only: indicates that NAT is enabled and DHCP server is disabled.
                        • Off: indicates that both DHCP server and NAT are disabled.
                      • DHCP Beginning Address: specifies the first address of DHCP pool (i.e., first available client IP address).
                      • DHCP Ending Address: specifies the last address of DHCP pool (i.e., last IP address that can be assigned to a client).
                      • DHCP Subnet Mask: defines the subnet mask that is assigned to a client.
                      • DHCP Default Lease Time: sets the default time (in minutes) that the client retains the provided IP address. It must be greater than 0.
                      • DHCP Max Lease Time: sets the maximum time (in minutes) that the client retains the provided IP address. It must be greater than 0.
                      • Pass DNS Servers through DHCP: enables DNS Proxy (i.e., passing DNS servers through DHCP).

                      If NAT is enabled and there is another interface designated as WAN (e.g., ppp0), the following iptables rules are added to the custom automatic NAT service rules section of the /etc/init.d/firewall script:

                      # custom automatic NAT service rules (if NAT option is enabled for LAN interface)\niptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE\niptables -A FORWARD -i ppp0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT\niptables -A FORWARD -i eth0 -o ppp0 -j ACCEPT\n

                      Also, IP forwarding is enabled in the kernel as follows:

                      # allow fowarding if any masquerade is defined\necho 1 > /proc/sys/net/ipv4/ip_forward\n

                      The rules shown above create an Overloaded (i.e., many-to-one) NAT. This type of network address translation maps multiple IP addresses on the LAN side to a single IP address on the WAN side, allowing internet access from hosts on a local network via a gateway (WAN) interface. Note that for NAT rules to be added, it is insufficient to enable NATing through the DHCPv4 & NAT tab of the LAN interface; there must also be another interface designated as WAN.

                      "},{"location":"gateway-configuration/network-configuration/#network-linux-configuration","title":"Network Linux Configuration","text":"

                      When applying a new network configuration, Kura changes the configuration files of the Linux networking subsystem. Please read the following note before proceeding with manual changes of the Linux networking configuration.

                      Warning

                      It is NOT recommended performing manual editing of the Linux networking configuration files when the gateway configuration is being managed through Kura. While Linux may correctly accept manual changes, Kura may not be able to interpret the new configuration resulting in an inconsistent state.

                      "},{"location":"gateway-configuration/network-configuration/#network-configuration-properties","title":"Network Configuration properties","text":"

                      The Network configuration can be modified using the Kura Gateway Administration Console, as described above, the Configuration Service or appling a proper snapshot.

                      The following table describes all the properties related to the Network Configuration. It includes the name of the property, the type, a description and the default value (if applicable). The network configuration pid is org.eclipse.kura.net.admin.NetworkConfigurationService.

                      "},{"location":"gateway-configuration/network-configuration/#common-properties","title":"Common properties","text":"Name Type Description Default value net.interfaces String Comma-separated list of the interface names in the device net.interface.<interface>.type String The type of the network interface; possible values are: ETHERNET, WIFI, MODEM, VLAN, LOOPBACK and UNKNOWN UNKNOWN net.interface.<interface>.config.wifi.mode String For wifi interfaces, specify the modality; possible values are INFRA, MASTER and UNKNOWN UNKNOWN net.interface.<interface>.config.nat.enabled Boolean Enable the NAT feature false net.interface.<interface>.config.promisc Integer Enable the Promiscuous Mode; possible values are: -1 (System default), 0 (Disabled), 1 (Enabled) -1"},{"location":"gateway-configuration/network-configuration/#ipv4-properties","title":"IPv4 properties","text":"Name Type Description Default value net.interface.<interface>.config.ip4.status String The status of the interface for the IPv4 configuration; possibile values are: netIPv4StatusDisabled, netIPv4StatusUnmanaged, netIPv4StatusL2Only, netIPv4StatusEnabledLAN, netIPv4StatusEnabledWAN, netIPv4StatusUnknown netIPv4StatusDisabled (see note below) net.interface.<interface>.config.ip4.wan.priority Integer (NetworkManager only) Priority used to determine which interface select as primary WAN. Allowed values range from -1 to 2147483647, inclusive. See Network Failover for further details -1 net.interface.<interface>.config.ip4.address String The IPv4 address assigned to the network interface net.interface.<interface>.config.ip4.prefix Short The IPv4 netmask assigned to the network interface -1 net.interface.<interface>.config.ip4.gateway String The IPv4 address of the default gateway net.interface.<interface>.config.ip4.dnsServers String Comma-separated list of dns servers net.interface.<interface>.config.ip4.mtu Integer The Maximum Transition Unit (MTU) for this interface

                      Note

                      For physical interfaces the default status is netIPv4StatusDisabled. For virtual ones, instead, the default status is defined by the kura.net.virtual.devices.config property in the kura.properties file.

                      "},{"location":"gateway-configuration/network-configuration/#ipv4-dhcp-server-properties","title":"IPv4 DHCP Server properties","text":"Name Type Description Default value net.interface.<interface>.config.dhcpServer4.enabled Boolean Specify if the DHCP server is enabled false net.interface.<interface>.config.dhcpServer4.rangeStart String First IP address available for clients net.interface.<interface>.config.dhcpServer4.rangeEnd String Last IP address available for clients net.interface.<interface>.config.dhcpServer4.defaultLeaseTime Integer The default lease time -1 net.interface.<interface>.config.dhcpServer4.maxLeaseTime Integer The maximum lease time -1 net.interface.<interface>.config.dhcpServer4.prefix Short The netmask for the available IP addresses -1 net.interface.<interface>.config.dhcpServer4.passDns Boolean Specify if the DNS server addresses has to be passed through DHCP false"},{"location":"gateway-configuration/network-configuration/#ipv4-dhcp-client-properties","title":"IPv4 DHCP Client properties","text":"Name Type Description Default value net.interface.<interface>.config.dhcpClient4.enabled Boolean Specify if the DHCP client is enabled false"},{"location":"gateway-configuration/network-configuration/#ipv6-properties","title":"IPv6 properties","text":"Name Type Description Default value net.interface.<interface>.config.ip6.status String The status of the interface for the IPv6 configuration; possibile values are: netIPv6StatusDisabled, netIPv6StatusUnmanaged, netIPv6StatusL2Only, netIPv6StatusEnabledLAN, netIPv6StatusEnabledWAN, netIPv6StatusUnknown netIPv6StatusDisabled (see note below) net.interface.<interface>.config.ip6.wan.priority Integer (NetworkManager only) Priority used to determine which interface select as primary WAN. Allowed values range from -1 to 2147483647, inclusive. See Network Failover for further details -1 net.interface.<interface>.config.ip6.address.method String The IPv6 configuration method; possible values are: AUTO, DHCP, MANUAL. AUTO net.interface.<interface>.config.ip6.address String The IPv6 address assigned to the network interface net.interface.<interface>.config.ip6.prefix Short The IPv6 netmask assigned to the network interface -1 net.interface.<interface>.config.ip6.gateway String The IPv6 address of the default gateway net.interface.<interface>.config.ip6.dnsServers String Comma-separated list of dns servers net.interface.<interface>.config.ip6.addr.gen.mode String The IPv6 address generation mode; possible values are EUI64, STABLE_PRIVACY net.interface.<interface>.config.ip6.privacy String The IPv6 Privacy Extensions for SLAAC; possible values are DISABLED, ENABLED_PUBLIC_ADD, ENABLED_TEMP_ADD net.interface.<interface>.config.ip6.mtu Integer The Maximum Transition Unit (MTU) for Ipv6 traffic on this interface. Requires NetworkManager 1.40 or newer

                      Note

                      For physical interfaces the default status is netIPv6StatusDisabled. For virtual ones, instead, the default status is defined by the kura.net.virtual.devices.config property in the kura.properties file.

                      "},{"location":"gateway-configuration/network-configuration/#wifi-master-access-point-properties","title":"WiFi Master (Access Point) properties","text":"Name Type Description Default value net.interface.<interface>.config.wifi.master.driver String The driver used for the connection net.interface.<interface>.config.wifi.master.passphrase Password The password for the access point net.interface.<interface>.config.wifi.master.ssid String The SSID of the access point net.interface.<interface>.config.wifi.master.securityType String The security protocol for the wireless network; possible values are SECURITY_NONE, SECURITY_WEP, SECURITY_WPA, SECURITY_WPA2, SECURITY_WPA_WPA2 SECURITY_NONE net.interface.<interface>.config.wifi.master.mode String The mode of the wireless connection; for the access point mode set it to MASTER MASTER net.interface.<interface>.config.wifi.master.channel String The channel to be used for the access point 1 net.interface.<interface>.config.wifi.master.radioMode String Specify the 802.11 radio mode; possible values are RADIO_MODE_80211a, RADIO_MODE_80211b, RADIO_MODE_80211g, RADIO_MODE_80211nHT20, RADIO_MODE_80211_AC RADIO_MODE_80211b net.interface.<interface>.config.wifi.master.ignoreSSID Boolean Specify if the SSID broadcast is ignored false net.interface.<interface>.config.wifi.master.groupCiphers String Group ciphers i.e. group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP net.interface.<interface>.config.wifi.master.pairwiseCiphers String Pairwise ciphers i.e. pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP"},{"location":"gateway-configuration/network-configuration/#wifi-infra-station-mode-properties","title":"WiFi Infra (Station Mode) properties","text":"Name Type Description Default value net.interface.<interface>.config.wifi.infra.ssid String The SSID of the wireless network to connect to net.interface.<interface>.config.wifi.infra.channel String The channel of the wireless network to connect to 1 net.interface.<interface>.config.wifi.infra.bgscan String Set the background scans; possible values have the form <mode>:<shortInterval>:<rssiThreshold>:<longInterval> where mode (String) is one of NONE, SIMPLE, or LEARN, shortInterval (Integer) sets the Bgscan short interval (secs), rssiThreshold (Integer) sets the Bgscan Signal strength threshold (dBm), and longInterval (Integer) sets the Bgscan long interval (secs) net.interface.<interface>.config.wifi.infra.passphrase Password The password for the wireless network net.interface.<interface>.config.wifi.infra.ignoreSSID Boolean Specify if a scan for SSID is required before attempting to associate false net.interface.<interface>.config.wifi.infra.mode String The mode of the wireless connection; for station mode set to INFRA INFRA net.interface.<interface>.config.wifi.infra.pingAccessPoint Boolean Enable pinging the access point after connection is established false net.interface.<interface>.config.wifi.infra.driver String The driver used for the connection net.interface.<interface>.config.wifi.infra.securityType String The security protocol for the wireless network; possible values are SECURITY_NONE, SECURITY_WEP, SECURITY_WPA, SECURITY_WPA2, SECURITY_WPA_WPA2 SECURITY_NONE net.interface.<interface>.config.wifi.infra.groupCiphers String Group ciphers i.e. group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP net.interface.<interface>.config.wifi.infra.pairwiseCiphers String Pairwise ciphers i.e. pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms set, possible values are CCMP, TKIP, and CCMP_TKIP CCMP_TKIP"},{"location":"gateway-configuration/network-configuration/#cellular-modem-properties","title":"Cellular Modem properties","text":"Name Type Description Default value net.interface.<interface>.config.enabled Boolean Enable the interface false net.interface.<interface>.config.idle Integer The idle option of the PPP daemon 95 net.interface.<interface>.config.username String The username used for the connection net.interface.<interface>.config.password Password The password used for the connection net.interface.<interface>.config.pdpType String The PdP type; possible values are IP, PPP and IPv6 IP net.interface.<interface>.config.maxFail Integer The maxfail option of the PPP daemon 5 net.interface.<interface>.config.authType String The authentication type; possible values are NONE, AUTO, CHAP and PAP NONE net.interface.<interface>.config.lpcEchoInterval Integer The lcp-echo-interval option of the PPP daemon 0 net.interface.<interface>.config.activeFilter String The active-filter option of the PPP daemon inbound net.interface.<interface>.config.lpcEchoFailure Integer The lcp-echo-failure option of the PPP daemon 0 net.interface.<interface>.config.diversityEnabled Boolean Enable the LTE diversity antenna false net.interface.<interface>.config.resetTimeout Integer The modem reset timeout in minutes 5 net.interface.<interface>.config.gpsEnabled Boolean Enable the GPS device in the modem if available false net.interface.<interface>.config.gpsMode String Select the GPS mode to activate for the modem if available kuraModemGpsModeUnmanaged net.interface.<interface>.config.persist Boolean The persist option of the PPP daemon true net.interface.<interface>.config.apn String The modem Access Point Name net.interface.<interface>.config.dialString String The dial string used for connecting to the APN net.interface.<interface>.config.holdoff Integer The holdoff option of the PPP daemon (in seconds) 1 net.interface.<interface>.config.pppNum Integer Assigned ppp interface number 0"},{"location":"gateway-configuration/network-configuration/#gps-mode","title":"GPS Mode","text":"

                      The GPS mode can be set to one of the following values:

                      • kuraModemGpsModeUnmanaged: the GPS device of the modem will be setup but not directly managed, therefore freeing the serial port for other services to use. This can be used in order to perform the setup of the GPS and then have another service (like gpsd) parse the NMEA strings in order to extract the position informations. This is the default value.
                      • kuraModemGpsModeManagedGps: the GPS device of the modem will be setup and directly managed (typically by ModemManager) therefore the serial port won't be available for other services to use.

                      For older versions compatibility, if the net.interface.<interface>.config.gpsMode property is not set, the GPS mode will be automatically set to the kuraModemGpsModeUnmanaged equivalent.

                      "},{"location":"gateway-configuration/network-configuration/#vlan-properties","title":"VLAN properties","text":"Name Type Description Default value net.interface.<interface>.config.vlan.parent String Physical interface Vlan is bound to net.interface.<interface>.config.vlan.id Integer Vlan tag identifier, between 0 and 4094 net.interface.<interface>.config.vlan.ingress String Incoming traffic priorities in the format from:to, as comma separated pairs of unsigned integers (Optional) net.interface.<interface>.config.vlan.egress String Outgoing traffic priorities in the format from:to, as comma separated pairs of unsigned integer (Optional) net.interface.<interface>.config.vlan.flags String Configuration flags, between 0 and 15 (Optional) 1"},{"location":"gateway-configuration/network-configuration/#8021x-properties","title":"802.1x properties","text":"Name Type Description net.interface.<interface>.config.802-1x.eap String The EAP method to be used when authenticating to the network with 802.1x. Supported methods: \"Kura8021xEapTls\", \"Kura8021xEapPeap\", \"Kura8021xEapTtls\". net.interface.<interface>.config.802-1x.innerAuth String Specifies the \"phase 2\" inner authentication method when an EAP method that uses an inner TLS tunnel is specified in the \"eap\" property. Supported methods: \"Kura8021xInnerAuthNone\", \"Kura8021xInnerAuthMschapv2\". net.interface.<interface>.config.802-1x.identity String Identity string for EAP authentication methods. Typically the user's user or login name. net.interface.<interface>.config.802-1x.password String Password used for EAP authentication methods. net.interface.<interface>.config.802-1x.client-cert-name String Name referring to the corresponding Kura keystore entry containing the client certificate if used by the EAP method specified in the \"eap\" property. Typically is set to the same name as the net.interface.<interface>.config.802-1x.private-key-name when using EAP-TLS. net.interface.<interface>.config.802-1x.private-key-name String Name referring to the corresponding Kura keystore entry containing the private key when the \"eap\" property is set to \"Kura8021xEapTls\". Typically is set to the same name as the net.interface.<interface>.config.802-1x.client-cert-name. net.interface.<interface>.config.802-1x.ca-cert-name String Name referring to the corresponding Kura keystore entry containing the CA certificate if used by the EAP method specified in the \"eap\" property. This parameter is optional but this allows man-in-the-middle attacks and is NOT recommended. net.interface.<interface>.config.802-1x.anonymous-identity String Anonymous identity string for EAP authentication methods. Used as the unencrypted identity with EAP types that support different tunneled identity like EAP-TTLS (Optional)"},{"location":"gateway-configuration/network-configuration/#network-configuration-recipes","title":"Network Configuration recipes","text":"

                      This section presents some snapshot examples to perform basic operations on networking. The snippets can be modified adapting them to the required configuration (i.e. changing the interface name in the property to be applied).

                      Warning

                      Be aware that an inconsitent or wrong configuration can compromise the network functionality of the gateway. Try the new configuration on a test device before appling it in a production environment!

                      Moreover, if a property is not present in the new snapshot, the old value is used for the configuration. So, the best practice is to set all the needed properties in the snapshot.

                      "},{"location":"gateway-configuration/network-configuration/#disable-a-network-interface","title":"Disable a network interface","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusDisabled</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#configure-an-ethernet-interface-for-wan-with-dhcp-client-enabled-and-custom-dns-server","title":"Configure an ethernet interface for WAN with DHCP client enabled and custom DNS server","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value>1.2.3.4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#configure-an-ethernet-interface-for-lan-with-dhcp-server-enabled-and-nat-disabled","title":"Configure an ethernet interface for LAN with DHCP server enabled and NAT disabled","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.type\" type=\"String\">\n                <esf:value>ETHERNET</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.rangeEnd\" type=\"String\">\n                <esf:value>192.168.4.110</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.defaultLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.passDns\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.rangeStart\" type=\"String\">\n                <esf:value>192.168.4.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.dhcpServer4.maxLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.4.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.ip4.gateway\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.enp5s0.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#configure-a-wireless-interface-as-access-point-with-dhcp-server-and-nat-enabled","title":"Configure a wireless interface as access point with DHCP server and NAT enabled","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.type\" type=\"String\">\n                <esf:value>WIFI</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.gateway\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.address\" type=\"String\">\n                <esf:value>172.16.1.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.rangeStart\" type=\"String\">\n                <esf:value>172.16.1.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.maxLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.defaultLeaseTime\" type=\"Integer\">\n                <esf:value>900</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.rangeEnd\" type=\"String\">\n                <esf:value>172.16.1.110</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.passDns\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.mode\" type=\"String\">\n                <esf:value>MASTER</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.driver\" type=\"String\">\n                <esf:value>nl80211</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.wlp1s0.config.wifi.master.passphrase\" type=\"Password\">\n                <esf:value>ZW5hYmxlbWVwbGVhc2U=</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.ssid\" type=\"String\">\n                <esf:value>kura_gateway_19</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.securityType\" type=\"String\">\n                <esf:value>SECURITY_WPA2</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.mode\" type=\"String\">\n                <esf:value>MASTER</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.channel\" type=\"String\">\n                <esf:value>11</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.radioMode\" type=\"String\">\n                <esf:value>RADIO_MODE_80211g</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.ignoreSSID\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.master.pairwiseCiphers\" type=\"String\">\n                <esf:value>CCMP</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#configure-a-wireless-interface-as-station-mode-with-dhcp-client-enabled","title":"Configure a wireless interface as station mode with DHCP client enabled","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.type\" type=\"String\">\n                <esf:value>WIFI</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.mode\" type=\"String\">\n                <esf:value>INFRA</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.ssid\" type=\"String\">\n                <esf:value>MyWirelessNetwork</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.bgscan\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.wlp1s0.config.wifi.infra.passphrase\" type=\"Password\">\n                <esf:value>MyPasswordBase64</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.ignoreSSID\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.mode\" type=\"String\">\n                <esf:value>INFRA</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.pingAccessPoint\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.driver\" type=\"String\">\n                <esf:value>nl80211</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.wlp1s0.config.wifi.infra.securityType\" type=\"String\">\n                <esf:value>SECURITY_WPA2</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#enable-a-cellular-interface","title":"Enable a cellular interface","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>  \n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.type\" type=\"String\">\n                <esf:value>MODEM</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ip4.dnsServers\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.idle\" type=\"Integer\">\n                <esf:value>95</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"true\" name=\"net.interface.1-1.config.password\" type=\"Password\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.pdpType\" type=\"String\">\n                <esf:value>IP</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.ipAddress\" type=\"String\">\n                <esf:value/>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.maxFail\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.authType\" type=\"String\">\n                <esf:value>NONE</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.lcpEchoInterval\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.activeFilter\" type=\"String\">\n                <esf:value>inbound</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.lcpEchoFailure\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.diversityEnabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.resetTimeout\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.gpsEnabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.persist\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.dialString\" type=\"String\">\n                <esf:value>atd*99***2#</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.1-1.config.apn\" type=\"String\">\n                <esf:value>web.omnitel.it</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-configuration/#create-a-vlan","title":"Create a VLAN","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?><esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>wlan0,lo,ens33,vlanFull,ens34</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.id\" type=\"Integer\">\n                <esf:value>41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.flags\" type=\"Integer\">\n                <esf:value>1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.ingress\" type=\"String\">\n                <esf:value>1:2,3:4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.vlan.egress\" type=\"String\">\n                <esf:value>5:6</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.41.100</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/network-failover/","title":"Network Failover","text":"

                      For devices configured to use NetworkManager, it is possible to configure multiple WAN interfaces and a basic network failover functionality.

                      As in the picture below, the Kura UI allows for multiple WAN interfaces to be defined. Each WAN interface can be configured with a WAN Priority. WAN Priority is used to determine which interface will be selected for primary WAN. In the case where the primary WAN interface loses connection, then the next highest priority interface is assigned.

                      Kura uses NetworkManager's implementation to achieve network failover (see NetworkManager). Lower values correspond to higher priority. Allowed values range from -1 to 2147483647. Value -1 means that the metric is chosen automatically based on the device type (see NetworkManager DBUS properties).

                      To observe changes to the applied configuration, use the following command on your device's shell:

                      route -n\n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n0.0.0.0         192.168.2.1     0.0.0.0         UG    100    0        0 eth0\n172.16.1.0      0.0.0.0         255.255.255.0   U     600    0        0 wlan0\n172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0\n192.168.2.0     0.0.0.0         255.255.255.0   U     100    0        0 eth0\n

                      The metric flag will correspond to the set WAN Priority. NetworkManager will always prioritize lower metric routes.

                      "},{"location":"gateway-configuration/network-failover/#operating-modes","title":"Operating modes","text":"

                      The NetworkManager failover mechanism can work at two different levels:

                      • by detecting disruptions at physical level (i.e. an ethernet cable that disconnects);
                      • by performing a connectivity check to an upstream URI.

                      NetworkManager brings a network interface down when it detects the loss of its physical link. In such case, the next highest priority interface is selected as the main one.

                      When the connectivity check fails, NetworkManager penalizes the metric of that interface in the routing table. NetworkManager continues to perform connectivity checks over all the other interfaces. As soon as the connectivity is restored over a previously failing interface, the metric is also restored to the original value and the routing table goes back to the original state.

                      "},{"location":"gateway-configuration/network-failover/#configuring-the-connectivity-check","title":"Configuring the connectivity check","text":"

                      The connectivity check is enabled by default in Kura and is configured to probe the connection to http://network-test.debian.org/nm every 60 seconds. To set a specific URI and a different interval edit /etc/NetworkManager/conf.d/99kura-nm.conf (reference NetworkManager):

                      [connectivity]\nuri=http://network-test.debian.org/nm\ninterval=60\nresponse=\"NetworkManager is online\"\n

                      The minimun interval is 60 seconds, if no interval is specified it defaults to 300 seconds.

                      The response should match what the URI is returning when probed. Some examples of web pages with NetworkManager responses:

                      URI Response http://network-test.debian.org/nm NetworkManager is online https://fedoraproject.org/static/hotspot.txt OK http://nmcheck.gnome.org/check_network_status.txt NetworkManager is online https://www.pkgbuild.com/check_network_status.txt NetworkManager is online

                      To disable the connectivity check feature:

                      • remove the [connectivity] section from the configuration file; or
                      • set interval=0; or
                      • remove uri; or
                      • set an empty URI, like uri=
                      "},{"location":"gateway-configuration/network-hardware/","title":"Hardware Tab","text":"

                      Each interface in the Network page contains inside its specific box the tab Hardware: this collects the most significant information about the current state of the interface.

                      In this way the user can have a quick and complete view of what are the settings and physical specifications of a network interface.

                      These information are respectively:

                      • Status: the current state of the network interface, from the framework point of view. Some examples of status are UNMANAGED, ACTIVATED, FAILED, UNKNOWN.
                      • Name: name of the interface
                      • Type: physical interface type like WIFI or ETHERNET
                      • Hardware Address: MAC address of the interface
                      • Serial #: serial number of the hardware interface
                      • Driver: Linux driver used for the interface
                      • Version: Linux driver version
                      • Firmware: firmware version of the interface module
                      • MTU: Maximum Transmission Unit, is the measurement in bytes of the largest data packets that the interface can accept
                      • USB Device: path of the usb device
                      • Received Signal Strength (dBm): power of the received radio signal from a wireless interface
                      • Current WLAN Channel: actual channel used by the wifi connection

                      These entries are filled only if the associated data is available. This means, for example, that no information will be shown under Current WLAN Channel if the interface is of Ethernet type. Conversely, if the interface is Wifi type and no information is shown, it is possible that there has been some problem with the configuration or connection of the interface.

                      So this tab, could be also an intuitive way to check if the interface is working properly or not.

                      "},{"location":"gateway-configuration/network-threat-manager/","title":"Network Threat Manager","text":"

                      Eclipse Kura provides a set of features to detect and prevent network attacks. The Security section in the Gateway Administration Console shows the Network Threat Manager tab where is it possible to activate these functions.

                      Warning

                      The Network Threat Manager tab is not available for the No Network version of Eclipse Kura.

                      "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection","title":"Flooding protection","text":"

                      The flooding protection function is used to prevent DDos (Distributed Denial-of-Service) attacks using the firewall. When enabled, the feature adds a set of firewall rules to the mangle table.

                      "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection-for-ipv4","title":"Flooding protection for IPv4","text":"

                      The following rules are added to the mangle table and they are implemented to block invalid or malicious network packets:

                      iptables -A prerouting-kura -m conntrack --ctstate INVALID -j DROP\niptables -A prerouting-kura -p tcp ! --syn -m conntrack --ctstate NEW -j DROP\niptables -A prerouting-kura -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags SYN,RST SYN,RST -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,RST FIN,RST -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags FIN,ACK FIN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,URG URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,FIN FIN -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ACK,PSH PSH -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL ALL -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL NONE -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP\niptables -A prerouting-kura -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP\niptables -A prerouting-kura -p icmp -j DROP\niptables -A prerouting-kura -f -j DROP\n

                      To further filter the incoming TCP fragmented packets, specific system configuration files are configured. The flooding.protection.enabled property is used to enable the feature.

                      "},{"location":"gateway-configuration/network-threat-manager/#flooding-protection-for-ipv6","title":"Flooding protection for IPv6","text":"

                      The same rules applied to the IPv4 are used for preventing attack on IPv6. In addition, some rules are implemented to drop specific IPv6 headers and limit the incoming ICMPv6 packets. Moreover, the incoming TCP fragmented packets are dropped configuring specific system files.

                      The following rules are applied to the mangle table:

                      ip6tables -A prerouting-kura -m conntrack --ctstate INVALID -j DROP\nip6tables -A prerouting-kura -p tcp ! --syn -m conntrack --ctstate NEW -j DROP\nip6tables -A prerouting-kura -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,SYN FIN,SYN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags SYN,RST SYN,RST -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,RST FIN,RST -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags FIN,ACK FIN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,URG URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,FIN FIN -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ACK,PSH PSH -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL ALL -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL NONE -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL FIN,PSH,URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j DROP\nip6tables -A prerouting-kura -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP\nip6tables -A prerouting-kura -p ipv6-icmp -m ipv6-icmp --icmpv6-type 128 -j DROP\nip6tables -A prerouting-kura -p ipv6-icmp -m ipv6-icmp --icmpv6-type 129 -j DROP\nip6tables -A prerouting-kura -m ipv6header --header dst --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header hop --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header route --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header frag --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header auth --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header esp --soft -j DROP\nip6tables -A prerouting-kura -m ipv6header --header none --soft -j DROP\nip6tables -A prerouting-kura -m rt --rt-type 0 -j DROP\nip6tables -A output-kura -m rt --rt-type 0 -j DROP\n

                      Also in this case, to enable the feature and add the rules to the firewall, the flooding.protection.enabled.ipv6 property has to be set to true. If the device doesn't support IPv6, this property is ignored.

                      Warning

                      To recover the device state when the IPv6 flooding protection feature is disabled, a reboot is required. So, to disable the feature, set the flooding.protection.enabled.ipv6 property to false tha reboot the device.

                      "},{"location":"gateway-configuration/ssl-configuration/","title":"SSL Configuration","text":"

                      A SSL Service instance manages the configuration of the SSL connections. It uses the associated KeystoreService to access the trust certificates, private keys pairs needed to setup a SSL connection. It also enforces best practices that are not enabled by default in the Java VM, such as, enabling hostname verification, disabling the legacy SSL-2.0-compatible Client Hello, and disabling the Nagle algorithm.

                      The list of all available SSL Service instances is available in the SSL Configuration tab of the Security section, accessible only by the users with the corresponding permission.

                      By default, the framework creates a SSLManagerService instance with the org.eclipse.kura.ssl.SslManagerService PID. This instance is generally used by all the core services of the framework.

                      A new SSL Service instance can be created using the New Button, by specifying the desired factory and the Service PID that will be associated with this new instance.

                      An instance of the default org.eclipse.kura.ssl.SslManagerService factory has the following configuration parameters:

                      • KeystoreService Target Filter - specifies, as an OSGi target filter, the pid of the KeystoreService used to manage the SSL key store (Required field).

                      • Truststore Target Filter - specifies, as an OSGi target filter, the pid of the KeystoreService used to manage the SSL trust store. If the target service cannot be found, the service configured with the Keystore Target Filter parameter will be used as truststore.

                      • ssl.default.protocol - defines the allowed SSL protocol.

                      • ssl.hostname.verification - indicates whether hostname verification is enabled or disabled.

                      • ssl.default.cipherSuites - defines the allowed cipher suites.

                      By selecting the Select available targets button, the user can associate the SSLManagerService instance with the corresponding KeystoreService instances available in the framework runtime.

                      "},{"location":"gateway-configuration/ssl-configuration/#server-ssl-certificate","title":"Server SSL Certificate","text":"

                      The device requires a public key in its trust store in order to authenticate the broker and be able to setup an SSL connection. Kura is distributed with a pre-initialized SSL keystore that contains only some of the major Certification Authorities (CA) public keys.

                      If the broker uses a certificate signed by a different CA, or uses an auto-signed certificate, the system administrator must setup Kura with the correct certificates used to trust the remote cloud broker.

                      The inclusion of public certificates is accomplished with the Server SSL Certificate feature. To do so, the SSL Certificates form must be completed by providing a certificate or a certificates chain to be trusted and defining the alias used to register this new data in the device's trust store.

                      With this feature, when the device tries to instantiate an SSL connection with the broker, it receives the broker's public key chain. The SSL connection is secured only if the received chain is trusted. This connection can only happen if one of the certificates that compose the broker chain are available in the device's trust store.

                      When instantiating the device's trust store, the user decides whether to add a single certificate (leaf or CA certificate) or the full chain.

                      In the latter case, the chain should be provided by specifying the leaf certificate, followed by the CA certificate that is signing it, and so on, until the root CA is reached. An example of this scenario is depicted in the following image:

                      "},{"location":"gateway-configuration/ssl-configuration/#device-ssl-certificate-mutual-authentication","title":"Device SSL Certificate & Mutual Authentication","text":"

                      Mutual authentication is a technique that allows authentication of the device that is connecting to the broker. The form available in Certificates List may be used to specify the keys needed to enable mutual authentication.

                      This authentication may be accomplished by specifying a couple of certificates (private and public keys) to be used by the client device to authenticate itself to the broker. This authentication is possible because the broker has the root CA certificate that has been used to sign the couple held by the device. In this way, the authenticity of the couple of certificates held by the device may be verified, and therefore, enable the two communicating parts (the broker and the device) to trust each other.

                      To enable mutual authentication, the user must complete the form with a well-formed key pair (public and private), and with an alias value that corresponds with the account name used to connect to the broker.

                      "},{"location":"gateway-configuration/ssl-configuration/#key-pair-generation","title":"Key Pair Generation","text":"

                      The keys may be generated using specific software, such as OpenSSL or Keytool. This section describes how to use OpenSSL to generate a couple of private and public keys.

                      The private key may be created using the following command:

                      openssl genrsa -out certsDirectory/certs/certificate.key 1024\n

                      This command creates a new, 1024-bit private key in the specified path. This key is used to generate a Certificate Signing Request (CSR) file, which is used by a CA to authenticate the certificate's creator. A CSR file is created with OpenSSL using the following command:

                      openssl req -new -key certsDirectory/certs/certificate.key -out certsDirectory/crl/certificate.csr\n

                      If the user is creating their own certificate chain, the CSR file may be signed using a personal CA. This process may be accomplished using OpenSSL with the following command:

                      openssl ca -config certsDirectory/openssl.cnf -days 3650 -keyfile certsDirectory/ca/ca.key -cert certsDirectory/ca/ca.pem -out certsDirectory/certs/certificate.pem -infiles certsDirectory/crl/certificate.csr\n

                      The parameters are defined as follows:

                      • -config: specifies the OpenSSL configuration file that must be used to sign the certificate.

                      • -days: specifies how long the certificate is valid.

                      • -keyfile and -cert: allow the specification of the CA that will sign the CSR file.

                      • -out: identifies the location and the name of the signed certificate that will be created.

                      • -infiles: identifies the location of the CSR file that has to be signed.

                      Tip

                      The private key may not be placed into the Kura Gateway Administration Console without a format conversion.

                      OpenSSL offers the following command to convert the input private key to a non-encrypted PKCS#8 format that may be processed by the Kura code:

                      openssl pkcs8 -topk8 -inform PEM -outform PEM -in inPrivateKey.key -out outKey.pem -nocrypt\n
                      "},{"location":"gateway-configuration/vlan-configuration/","title":"VLAN Configuration","text":"

                      A VLAN, or Virtual Local Area Network, is a network segmentation technology that allows a single physical network to be logically divided into multiple isolated networks. These virtual networks operate as if they are independent, even though they share the same physical infrastructure. This is achieved via a VLAN ID, or VLAN tag, a numerical label added to network frames to identify the specific Virtual Local Area Network (VLAN) to which they belong. It's a critical component in VLAN technology, allowing network switches and routers to differentiate and route traffic within a VLAN. VLAN tags are added to the Ethernet frame's header, indicating which virtual network a data packet should be directed to when it traverses the physical network infrastructure. Therefore, VLANs must also be supported and configured on the network equipment a device is connected to.

                      A VLAN can be named freely, as long as it's 15 or less characters. A typical VLAN naming format is physicalInterfaceName.vlanId (eg. a vlan with id 100 on the interface eth0 would be named eth0.100).

                      This is achieved by NetworkManager by creating a virtual device bound to the underlying physical interface when Kura sets up a new VLAN connection.

                      "},{"location":"gateway-configuration/vlan-configuration/#vlan-configuration-via-kura-snapshot-upload","title":"VLAN Configuration via Kura Snapshot upload","text":"

                      Currently, VLAN configuration is supported via uploading snapshot.xml fragments.

                      Warning

                      When creating a new VLAN be sure to include the net.interfaces parameter, containing both the previously existing network interfaces, either virtual or physical, and the name of the new VLAN to be created.

                      "},{"location":"gateway-configuration/vlan-configuration/#basic-vlan-configuration-example","title":"Basic VLAN configuration example","text":"

                      The following example creates a VLAN with ID 40 over the ethernet interface ens33, naming it ens33.40, using a predefined IP address, enabled for LAN.

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>lo,ens33,ens34,ens33.40</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.vlan.id\" type=\"Integer\">\n                <esf:value>40</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.address\" type=\"String\">\n                <esf:value>10.0.55.37</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/vlan-configuration/#complete-vlan-configuration-example","title":"Complete VLAN configuration example","text":"

                      The following example creates a VLAN with ID 41 over the ethernet interface ens33, naming it ens33.41, using a predefined IP address, enabled for WAN. This example also sets the 'flags' and 'traffic priority' optional parameters as described in Network Manager API documentation.

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interfaces\" type=\"String\">\n                <esf:value>lo,ens33,ens34,ens33.41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.dhcpServer4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.nat.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.dhcpClient4.enabled\" type=\"Boolean\">\n                <esf:value>false</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.type\" type=\"String\">\n                <esf:value>VLAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.gateway\" type=\"String\">\n                <esf:value>192.168.41.254</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledWAN</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.vlanFull.config.ip4.dnsServers\" type=\"String\">\n                <esf:value>8.8.8.8</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.parent\" type=\"String\">\n                <esf:value>ens33</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.id\" type=\"Integer\">\n                <esf:value>41</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.address\" type=\"String\">\n                <esf:value>192.168.41.1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.ip4.prefix\" type=\"Short\">\n                <esf:value>24</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.flags\" type=\"Integer\">\n                <esf:value>1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.ingress\" type=\"String\">\n                <esf:value>1:2,3:4</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.41.config.vlan.egress\" type=\"String\">\n                <esf:value>5:6</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/vlan-configuration/#vlan-management","title":"VLAN Management","text":"

                      Once a VLAN is created it can be managed via the Kura UI just like any other Ethernet interface.

                      Warning

                      Setting a VLAN status to \"Disabled\" deletes its configuration in NetworkManager and the related virtual interface from the system. Although it will is no longer be visible on the UI, all the configurations are left in Kura. Therefore the VLAN can be restored by setting the net.interface.<interface>.config.ip4.status to netIPv4StatusEnabledLAN or netIPv4StatusEnabledWAN via snapshot upload, then resume configuration via UI.

                      As an example, the configuration to reactivate a disabled VLAN named ens33.40 would be as follows:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.net.admin.NetworkConfigurationService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"net.interface.ens33.40.config.ip4.status\" type=\"String\">\n                <esf:value>netIPv4StatusEnabledLAN</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n
                      "},{"location":"gateway-configuration/web-console-configuration/","title":"Web Console Configuration","text":"

                      The Web Console exposes a set of configuration parameters that can be used to increase the overall UI security. The Web Console configuration can be accessed in the Security section.

                      "},{"location":"gateway-configuration/web-console-configuration/#web-server-entry-point","title":"Web Server Entry Point","text":"

                      This parameter allows to configure the relative path that the user will be redirected to when accessing http(s)://gateway-ip/. Note: this parameter does not change the Kura Web UI relative path, that is always /admin/console. The default value set is /admin/console

                      "},{"location":"gateway-configuration/web-console-configuration/#session-max-inactivity-interval","title":"Session max inactivity interval","text":"

                      The session max inactivity interval in minutes. If no interaction with the Web UI is performed for the value of this parameter in minutes, a new login will be requested. The default value set is 15 minutes

                      "},{"location":"gateway-configuration/web-console-configuration/#access-banner-enabled","title":"Access Banner Enabled","text":"

                      For security reasons, it may be needed to display to the user a banner that describes the intended system use before authenticating.

                      Once enabled and configured, the Kura Web UI will display a banner before every access attempt, as depicted in the image below.

                      "},{"location":"gateway-configuration/web-console-configuration/#password-management","title":"Password Management","text":"

                      This section is related to the definition of required parameters that must be respected when defining a new password, for example when a user changes its password at first access.

                      "},{"location":"gateway-configuration/web-console-configuration/#minimum-password-length","title":"Minimum password length","text":"

                      The minimum length to be enforced for new passwords. Set to 0 to disable. The default value set is 8 characters

                      "},{"location":"gateway-configuration/web-console-configuration/#require-digits-in-new-password","title":"Require digits in new password","text":"

                      If set to true, new passwords will be accepted only if containing at least one digit. The default value is false

                      "},{"location":"gateway-configuration/web-console-configuration/#require-special-characters-in-new-password","title":"Require special characters in new password","text":"

                      If set to true, new passwords will be accepted only if containing at least one non alphanumeric character The default value is false

                      "},{"location":"gateway-configuration/web-console-configuration/#require-uppercase-and-lowercase-characters-in-new-passwords","title":"Require uppercase and lowercase characters in new passwords","text":"

                      If set to true, new passwords will be accepted only if containing both uppercase and lowercase alphanumeric characters. The default value is false

                      "},{"location":"gateway-configuration/web-console-configuration/#allowed-ports","title":"Allowed ports","text":"

                      If set to a non empty list, Web Console access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. It is needed for the end user to make sure that the allowed ports are open in HttpService and Firewall configuration.

                      "},{"location":"gateway-configuration/web-console-configuration/#authentication-method-password-enabled","title":"Authentication Method \"Password\" Enabled","text":"

                      Defines whether the \"Password\" authentication method is enabled or not. The default value is true

                      "},{"location":"gateway-configuration/web-console-configuration/#authentication-method-certificate-enabled","title":"Authentication Method \"Certificate\" Enabled","text":"

                      Defines whether the \"Certificate\" authentication method is enabled or not The default value is true

                      "},{"location":"gateway-configuration/web-console-configuration/#sslmanagerservice-target-filter","title":"SslManagerService Target Filter","text":"

                      It is possible to specify the SslManagerService containing the certificates truststore required to establish a new https connection, this is needed, for example, for fetching package descriptions from Eclipse Marketplace. The default target is the org.eclipse.kura.ssl.SslManagerService

                      "},{"location":"gateway-configuration/wifi-configuration-8021x/","title":"Wi-Fi 802.1x Configuration","text":"

                      To enable the Enterprise Wi-Fi, the Wireless Security property in the Wireless tab has to be set to WPA2/WPA3-Enterprise. This feature is available only if the Wireless Mode is Station Mode. The following is a list of currently supported 802.1x authentication methods.

                      • TTLS-MSCHAPv2
                      • PEAP-MSCHAPv2
                      • EAP-TLS
                      "},{"location":"gateway-configuration/wifi-configuration-8021x/#ttls-mschapv2","title":"TTLS-MSCHAPv2","text":"
                      1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
                      2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise
                      3. select the 802.1x tab
                      4. Set Enteprise EAP -> TTLS
                      5. Set Inner Authentication -> MSCHAPV2
                      6. Set Identity (Username)
                      7. Set Password
                      8. Press 'Apply'

                      The configuration should look like the following:

                      "},{"location":"gateway-configuration/wifi-configuration-8021x/#peap-mschapv2","title":"PEAP-MSCHAPv2","text":"
                      1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
                      2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise
                      3. select the 802.1x tab
                      4. Set Enteprise EAP -> PEAP
                      5. Set Inner Authentication -> MSCHAPV2
                      6. Set Identity (Username)
                      7. Set Password
                      8. Press 'Apply'

                      The configuration should look like the following:

                      "},{"location":"gateway-configuration/wifi-configuration-8021x/#eap-tls","title":"EAP-TLS","text":"

                      To connect via EAP-TLS you will need the following items in unencrypted PEM format:

                      • Certificate Authority (CA) Certificate
                      • Client Certificate + Private Key (PKCS8)
                      "},{"location":"gateway-configuration/wifi-configuration-8021x/#enrolling-secrets-in-the-keystore-service","title":"Enrolling secrets in the Keystore service.","text":"
                      1. Navigate to Security under the System tab.
                      2. Under the Keystore Configuration add a new keystore, and keep note of the name.
                      3. After the Keystore is created, be sure to change the path to a persistent directory.
                      4. Navigate to the Certificate List and create a new Certificate. Insert the PEM and Apply, keep note of the name.
                      5. Now press add and create a new Private Key. Insert both the certificates in the PEM in the dialogue and press apply. keep note of the name.
                      "},{"location":"gateway-configuration/wifi-configuration-8021x/#wifi-setup","title":"Wifi Setup","text":"
                      1. Set up gateway Wi-Fi as described in the Wi-Fi configuration guide.
                      2. Ensure Wireless Security is set to WPA2/WPA3-Enterprise.
                      3. Select the 802.1x tab.
                      4. Set Enteprise EAP -> TLS.
                      5. Set Identity (Username).
                      6. Set Keystore Pid to the name of the keystore created above.
                      7. Set Certificate Authority Certificate (CA-Cert) to the name of the certificate created above.
                      8. Set the Client Private Key to the name of the Private Key created above.

                      When completed the Wi-Fi configuration should look like the following:

                      "},{"location":"gateway-configuration/wifi-configuration/","title":"Wi-Fi Configuration","text":"

                      From a configuration standpoint, the Wi-Fi interface (e.g., wlan0) may be viewed as an extension of Ethernet. In addition to the IPv4, IPv6 and DHCPv4 & NAT configuration tabs, it has the Wireless tab that allows for the configuration of wireless settings. These configuration options are described below.

                      Warning

                      Before using wifi make sure that you have correctly set the Regulatory Domain on the gateway. You can check the current configuration using the iw reg get command. To set the Regulatory Domain please refer to the specific section in the Gateway Configurations.

                      "},{"location":"gateway-configuration/wifi-configuration/#wireless-configuration","title":"Wireless Configuration","text":"

                      The Wireless tab contains the following configuration parameters:

                      • Wireless Mode: defines the mode of operation.

                        • Access Point: creates a wireless access point.
                        • Station Mode: connects to a wireless access point.
                      • Network Name: specifies the Service Set Identifier (SSID).

                        • In Access Point mode, this is the SSID that identifies this wireless network.
                        • In Station mode, this is the SSID of a wireless network to connect to.
                      • Band: defines the frequency band to use

                        • 2.4GHz: use 2.4GHz band
                        • 5GHz: use 5GHz band
                        • 2.4GHz/5GHz: use either in 2.4Ghz or 5Ghz depending on the choosen channel
                      • Wireless Security: sets the security protocol for the wireless network.

                        • None: No Wi-Fi security
                        • WEP: Wired Equivalent Privacy
                        • WPA: Wi-Fi Protected Access
                        • WPA2: Wi-Fi Protected Access II
                        • WPA2/WPA3-Enterprise: Wi-Fi Protected Access II & III with 802.1x. Please see here for further details. This fearture is available only in station mode
                      • Wireless Password: sets the password for the wireless network.

                        • WEP: 64-bit or 128-bit encryption key
                        • WPA/WPA2: pre-shared key
                      • Verify Password: sets the password verification field.

                        • In Access Point mode, allows the wireless password to be retyped for verification.
                        • In Station mode, this field is disabled.
                      • Pairwise Ciphers: lists accepted pairwise (unicast) ciphers for WPA/WPA2.

                        • In Access Point mode, this option is disabled.
                        • In Station mode,
                          • CCMP (AES-based encryption mode with strong security)
                          • TKIP (Temporal Key Integrity Protocol)
                          • CCMP and TKIP
                      • Group Ciphers: lists accepted group (broadcast/multicast) ciphers for WPA/WPA2.

                        • In Access Point mode, this option is disabled.
                        • In Station mode,
                          • CCMP (AES-based encryption mode with strong security)
                          • TKIP (Temporal Key Integrity Protocol)
                          • CCMP and TKIP
                      • Bgscan Module: requests background scans for the purpose of roaming within an ESS (i.e., within a single network block with all the APs using the same SSID).

                        • None: background scan is disabled
                        • Simple: periodic background scans based on signal strength
                        • Learn: learn channels used by the network and try to avoid bgscans on other channels
                      • Bgscan Signal Strength Threshold: defines a threshold (in dBm) that determines which one of the following two parameters (i.e., Short Interval or Long Interval) will be effective.

                      • Bgscan Short Interval: defines the interval between background scans (in seconds) if the actual signal level of the currently connected access point is worse than signal_strength.

                      • Bgscan Long Interval: defines the interval between background scans (in seconds) if the actual signal level of the currently connected access point is better than signal_strength.

                      • Ping Access Point & renew DHCP lease if not reachable: enables pinging the access point after connection is established.

                        • In Access Point mode, this option is disabled.
                        • In Station mode, if set to true, the unit will ping the access point and attempt to renew the DHCP lease if the access point is not reachable.
                      • Ignore Broadcast SSID: operates as follows if set to true:

                        • In Access Point mode, sends an empty SSID in beacons and ignores probe request frames that do not specify full SSID.
                        • In Station mode, does not scan for the SSID before attempting to associate.
                      • Channels list: allows the selection of desired channel frequencies. The availability of the desired frequency is subject to the Regdom set on the device. For a list of limitations in different countries you can consult the following page: List of WLAN channels. Channels marked as No Irradiation and Radar Detection can be used only if DFS (Dynamic Frequency Selection) is supported by the Wi-Fi chip.

                        • In Access Point mode, only one channel may be selected.
                        • In Station mode, the list of available channels depends on the selected Radio Mode. The selected radio mode also affects the ability to select a network in the scan window (if the channel associated with the network is not enabled in the regulatory domain an error message will be shown).

                      "},{"location":"gateway-configuration/wifi-configuration/#wi-fi-station-mode-configuration","title":"Wi-Fi Station Mode Configuration","text":"

                      In addition to the options described above, the Wireless configuration display the Access Point Scan button that help to configure Wi-Fi in the Station mode.

                      • Access Point Scan: clicking this button triggers access point scan operations. Upon a successful scan, a table containing access points within range is presented. This table contains the following information:

                        • SSID
                        • MAC Address
                        • Signal Strength (in dBm)
                        • Channel
                        • Frequency
                        • Security

                        If you select one of these access points, respective wireless controls (i.e., Network Name, Wireless Security, and Channel) are filled with information obtained during the scan operation. The Force Wireless Network Scan button triggers a manual scan for available access points.

                      "},{"location":"getting-started/docker-quick-start/","title":"Docker Quick Start","text":""},{"location":"getting-started/docker-quick-start/#installation","title":"Installation","text":"

                      Eclipse Kura is also available as a Docker container available in Docker Hub.

                      To download and run, use the following command:

                      docker run -d -p 443:443 -t eclipse/kura\n

                      This command will start Kura in background and the Kura Web Ui will be available through port 443.

                      Once the image is started you can navigate your browser to https://localhost and log in using the credentials admin : admin.

                      "},{"location":"getting-started/docker-quick-start/#command-toolbox","title":"Command Toolbox","text":"

                      Following, a set of useful Docker command that can be used to list and manage Docker containers. For more details on Docker commands, please reference the official Docker documentation

                      "},{"location":"getting-started/docker-quick-start/#list-docker-images","title":"List Docker Images","text":"

                      To list all the installed Docker images run:

                      docker images\n
                      "},{"location":"getting-started/docker-quick-start/#list-running-docker-containers","title":"List Running Docker Containers","text":"

                      To list all the available instances (both running and powered off) run:

                      docker ps -a\n
                      "},{"location":"getting-started/docker-quick-start/#startstop-a-docker-container","title":"Start/Stop a Docker Container","text":"
                      docker stop <container id>\ndocker start <container id>\n

                      where <container id> is the instance identification number.

                      "},{"location":"getting-started/install-kura/","title":"Install Kura","text":"

                      Eclipse Kura\u2122 is provided using a Debian Linux package. Visit the Kura download page to find the correct installation file for the target system.

                      "},{"location":"getting-started/install-kura/#installer-types","title":"Installer types","text":"

                      Several installers can be found on such page, and they fall into one of the following categories:

                      1. standard installers, like kura-6.0.0_generic-arm32_installer.deb; and
                      2. installers with suffix nn, like kura-6.0.0_generic-arm32-nn_installer.deb

                      Profiles of types (1) ship a Kura version with networking functionalities. In particular, they can be installed on targets with NetworkManager, a commonly available tool for managing Linux networking. Kura leverages this tool for networking functionalities. Refer to the Kura installers section for further information.

                      Installers of type (2) with the suffix nn are No Networking profiles that do not bundle the Kura Network Manager: all the network configurations need to be done outside of Kura. Functionalities missing in NN profiles compared to the full Kura profiles:

                      • Networking interfaces management
                      • Firewall configuration management
                      • Network Threat management
                      "},{"location":"getting-started/install-kura/#kura-installers","title":"Kura installers","text":"

                      A user can deploy Kura on a target system using the installer tailored for the device architecture. The installer file looks like:

                      kura-<kura-version>_generic-<arch>_installer.deb/rpm\n

                      where <arch> is one of the supported architectures: x86_64, arm32, and arm64. Kura can work on systems that have available the dependencies listed in the Kura dependencies section, and that have at least one physical ethernet interface.

                      "},{"location":"getting-started/install-kura/#java-heap-memory-assignment","title":"Java Heap Memory Assignment","text":"

                      The Eclipse Kura\u2122's installer incorporates an adaptive Heap Memory allocation system during installation. The allocation follows a formula based on your gateway's available memory. If your gateway has less than 1024MB of RAM, Kura will set -Xms and -Xmx to 256MB. For gateways with more than 1024MB, one quarter of the total RAM will be assigned to -Xms and -Xmx.

                      "},{"location":"getting-started/install-kura/#initial-network-configuration","title":"Initial network configuration","text":"

                      During the installation of Eclipse Kura with network management support, the initial network configuration will be generated dynamically. The existing wired and wireless network interface names are detected and sorted in ascending lexicographic order at the installation.

                      If only one ethernet interface is detected (e.g. eth0), it will be configured as follows:

                      Interface Configuration Ethernet interface (e.g. eth0) - Status: Enabled for WAN- Configure: Using DHCP

                      If multiple ethernet interfaces are detected, instead, the first two interfaces will be configured as presented in the table below. The other ethernet interfaces will be disabled.

                      Interface Configuration First Ethernet interface (e.g. eth0) - Status: Enabled for LAN- Configure: Manually- IP address: 172.16.0.1- Router Mode: DHCP and NAT Second Ethernet interface (e.g. eth1) - Status: Enabled for WAN- Configure: Using DHCP

                      Finally, if a wireless interface (e.g. wlan0) is detected, it will configured as shown below. The other wireless interfaces will be disabled.

                      Interface Configuration Wireless interface (e.g. wlan0) - Status: Enabled for LAN- Configure: Manually- IP address: 172.16.1.1- Passphrase: testKEYS- Router Mode: DHCP and NAT

                      For example, if the system contains the following interfaces: wlp2s0, wlp3s0, enp3s0, eno1, ens2; then eno1 will be enabled for LAN with a DHCP server, enp3s0 will be enabled for WAN in DHCP client mode, wlp2s0 will be configured as an AP, and all other network interfaces will be disabled.

                      Warning

                      On systems that do not use systemd's predictable interface naming scheme (see Freedesktop reference) the primary network interface name might change whenever a re-enumeration is triggered (for example, after a reboot or after plugging in an external network adapter).

                      The advice is to install Kura on systems that use a reliable naming convention for network interfaces.

                      Systemd consistent network interface naming assigns the name prefix based on the physical location of the device, see Understanding the Predictable Network Interface Device Names for further reference.

                      "},{"location":"getting-started/install-kura/#initial-firewall-configuration","title":"Initial firewall configuration","text":"

                      Similarly to the initial network configuration, the initial firewall setup is adapted based on the network interface detected on the system. In case of multiple ethernet and wireless interfaces, the configuration will be as shown in the screenshot below.

                      If the wireless interface is not present, the firewall entries for the wlan0 are dropped.

                      Please note that installing Eclipse Kura with network configuration support will replace the current network and firewall configuration with the one shown above.

                      "},{"location":"getting-started/install-kura/#other-kura-services","title":"Other Kura services","text":"

                      Eclipse Kura\u2122 do not contain gateway specific customizations, this implies that the values of some configuration parameters may be incorrect and/or missing and must be manually filled after installation. For example the user might want to:

                      • Configure the other network interfaces, if any.
                      • Setup additional firewall rules.
                      • Edit the /opt/eclipse/kura/framework/jdk.dio.properties with the correct GPIO mappings. By default this file is empty.
                      "},{"location":"getting-started/install-kura/#kura-dependencies","title":"Kura dependencies","text":"

                      To have all the Kura features working, the following dependencies are required:

                      • General: setserial, zip, gzip, unzip, procps, usbutils, socat, gawk, sed, inetutils-telnet.
                      • Security: polkit or policykit-1, ssh or openssh, openssl, busybox, openvpn.
                      • Bluetooth: bluez or bluez5, bluez-hcidump or bluez5-noinst-tools.
                      • Time: ntpdate, chrony, chronyc, cron or cronie.
                      • Networking: network-manager or networkmanager, bind9 or bind, dnsmasq or isc-dhcp-server or (dhcp-server and dhcp-client), iw, iptables, modemmanager, hostapd, wpa-supplicant, ppp, iproute2.
                      • Logs: logrotate.
                      • Gps: gpsd.
                      • Python: python3.
                      • Java: openjdk-17-jre-headless or temurin-17-jdk or openjdk-8-jre-headless or temurin-8-jdk.
                      • Others: dos2unix
                      "},{"location":"getting-started/install-kura/#reference-devices","title":"Reference devices","text":"

                      Eclipse Kura\u2122 has been tested on the following devices and provides full configuration of all the available interfaces and GPIO mappings.

                      Device Architecture OS Raspberry Pi 3/4 arm32 Raspberry Pi OS \"Bookworm\" Raspberry Pi 3/4 arm64 Raspberry Pi OS \"Bookworm\" Raspberry Pi 3/4 arm64 Ubuntu 20.04 ZimaBoard/Blade x86_64 TBD NVIDIA Jetson AGX Orin\u2122 arm64 TBD

                      Check out the quick start guides for the detailed installation steps and set-up procedures:

                      • Raspberry Pi - Raspberry Pi OS Quick Start
                      • Raspberry Pi - Ubuntu 20 Quick Start
                      • Docker Quick Start
                      • Nvidia Jetson AGX Orin Quick Start
                      • ZimaBoard/Blade Quick Start
                      "},{"location":"getting-started/nvidia-jetson-orin-quick-start/","title":"NVIDIA Jetson AGX Orin\u2122 - Quick Start","text":""},{"location":"getting-started/nvidia-jetson-orin-quick-start/#overview","title":"Overview","text":""},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/","title":"Raspberry Pi - Raspberry Pi OS Quick Start","text":""},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#overview","title":"Overview","text":"

                      This section provides Eclipse Kura\u2122 quick installation procedures for the Raspberry Pi and the Kura development environment.

                      Warning

                      This quickstart will install the version of Kura with the administrative web UI and network configuration support but not CAN bus support. For more information on this please visit the Eclipse Kura download page

                      This quickstart has been tested using the latest Raspberry Pi OS 32 and 64 bits images which are available for download through the official Raspberry Pi foundation site and the Raspberry Pi Imager.

                      Warning

                      Recent versions of Raspberry Pi OS 32 bit on Raspberry PI 4 will use by default a 64 bit kernel with a 32 bit userspace. This can cause issues to applications that use the result of uname -m to decide which native libraries to load (see https://github.com/raspberrypi/firmware/issues/1795). This currently affects for example the Kura SQLite database connector. It should be possible to solve by switching to the 32 bit kernel setting arm_64bit=0 in /boot/config.txt and restarting the device.

                      For additional details on OS compatibility refer to the Kura\u2122 release notes.

                      "},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#enable-ssh-access","title":"Enable SSH Access","text":"

                      The ssh server is disabled by default on Raspbian images released after November 2016, in order to enable it follow the instructions available here.

                      If you're using the Raspberry Pi Imager you can directly enable SSH before writing the operating system into the SD card by clicking on the \"setting\" icon.

                      "},{"location":"getting-started/raspberry-pi-raspberryos-quick-start/#eclipse-kuratm-installation","title":"Eclipse Kura\u2122 Installation","text":"

                      To install Eclipse Kura with its dependencies on the Raspberry Pi, perform the following steps:

                      1. Boot the Raspberry Pi with the latest\u00a0Raspberry Pi OS image.

                      2. Make sure your device is connected to the Internet. The best installation experience can be obtained when the device is cabled to the local network and the Internet. By default, the Raspberry Pi OS configures the ethernet interface eth0 in DHCP mode.

                      3. Upgrade the system:

                        sudo apt update\n
                        sudo apt upgrade\n

                        Tip

                        Optional: Since version 5.3.0 Kura also supports Eclipse Temurin\u2122 as an alternative JVM. To install it you need to perform these additional steps:

                        sudo apt-get install -y wget apt-transport-https gnupg\n
                        sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
                        sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
                        sudo apt-get update\n
                        sudo apt-get install temurin-17-jdk\n

                      4. Download the Kura package with:

                        wget http://download.eclipse.org/kura/releases/<version>/kura-<kura-version>_generic-<arch>_installer.deb\n

                        Note: replace <version> in the URL above with the version number of the latest release (e.g. 5.5.0) and <arch> with your device architecture

                      5. Install Kura with:\u00a0

                        sudo apt-get install ./kura-<kura-version>_generic-<arch>_installer.deb\n
                      6. For a correct configuration of the Wlan interface, it is necessary to set the Locale and the WLAN Country through the raspi-config command:

                        sudo raspi-config\n

                        From the raspi-config main menu select Localisation Options:

                        Then modify the Locale and WLAN Country with with the proper settings for your location. For example, an user located in Italy could set the values as the ones in the table:

                        Setting Value L1 Locale it_IT.UTF-8 UTF-8 L4 WLAN Country IT Italy
                      7. (Optional) To correctly use the GPIO pins, the user is asked to update the jdk.dio.properties file with the proper configuration, based on its own device.

                        This is required since the sysfs interface has been deprecated, and some OS distribution may have already suppressed it. Moreover, the kernel complains if a static base number is assigned to a GPIO controller: indeed, when it assigns the numbers automatically, it usually starts from 511. More information can be found here.

                        In order to set the correct configuration the user can perform the following steps:

                        • Execute on the device the command cat /sys/kernel/debug/gpio, looking for entries similar to gpio-ABC (GPIxx): from this information it is possible to retrieve which number the GPIO controller was assigned to by the OS (in this case the GPIO controller number xx is assigned with the number ABC). The image below represent an example of this file

                        • Modify the /opt/eclipse/kura/framework/jdk.dio.properties with the number and controllers found in the previous step:
                        573 = deviceType: gpio.GPIOPin, pinNumber:573, name:GPI02\n574 = deviceType: gpio.GPIOPin, pinNumber:574, name:GPIO3\n575 = deviceType: gpio.GPIOPin, pinNumber:575, name:GPIO4\n576 = deviceType: gpio.GPIOPin, pinNumber:576, name:GPIO5\n577 = deviceType: gpio.GPIOPin, pinNumber:577, name:GPIO6\n578 = deviceType: gpio.GPIOPin, pinNumber:578, name:GPIO7\n579 = deviceType: gpio.GPIOPin, pinNumber:579, name:GPIO8\n580 = deviceType: gpio.GPIOPin, pinNumber:580, name:GPIO9\n581 = deviceType: gpio.GPIOPin, pinNumber:581, name:GPIO10\n582 = deviceType: gpio.GPIOPin, pinNumber:582, name:GPIO11\n583 = deviceType: gpio.GPIOPin, pinNumber:583, name:GPIO12\n584 = deviceType: gpio.GPIOPin, pinNumber:584, name:GPIO13\n585 = deviceType: gpio.GPIOPin, pinNumber:585, name:GPIO14\n586 = deviceType: gpio.GPIOPin, pinNumber:586, name:GPIO15\n587 = deviceType: gpio.GPIOPin, pinNumber:587, name:GPIO16\n588 = deviceType: gpio.GPIOPin, pinNumber:588, name:GPIO17\n589 = deviceType: gpio.GPIOPin, pinNumber:589, name:GPIO18\n590 = deviceType: gpio.GPIOPin, pinNumber:590, name:GPIO19\n591 = deviceType: gpio.GPIOPin, pinNumber:591, name:GPIO20\n592 = deviceType: gpio.GPIOPin, pinNumber:592, name:GPIO21\n593 = deviceType: gpio.GPIOPin, pinNumber:593, name:GPIO22\n594 = deviceType: gpio.GPIOPin, pinNumber:594, name:GPIO23\n595 = deviceType: gpio.GPIOPin, pinNumber:595, name:GPIO24\n596 = deviceType: gpio.GPIOPin, pinNumber:596, name:GPIO25\n597 = deviceType: gpio.GPIOPin, pinNumber:597, name:GPIO26\n598 = deviceType: gpio.GPIOPin, pinNumber:598, name:GPIO27\n\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\nuart.UART = baudRate:19200, parity:0, dataBits:8, stopBits:1, flowControl:0\n

                        You can also check your GPIO device configuration executing the command pinout

                      8. Reboot the Raspberry Pi with:

                        sudo reboot\n

                        Kura starts on the target platform after reboot.

                      9. Kura setups a local web ui that is available using a browser via:

                        https://<device-ip>\n

                        The browser will prompt the user to accept the connection to an endpoint with an untrusted certificate:

                        Once trusted the source, the user will be redirected to a login page where the following credentials: username: admin password: admin

                      "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/","title":"Raspberry Pi - Ubuntu 20 Quick Start","text":""},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#overview","title":"Overview","text":"

                      This section provides Eclipse Kura\u2122 quick installation procedures for the Raspberry Pi.

                      Warning

                      This quickstart will install the version of Kura with the administrative web UI and network configuration support but not CAN bus support. For more information on this please visit the Eclipse Kura download page

                      This quickstart has been tested using the latest Ubuntu 20.04.3 LTS Live Server for arm64 architecture flashed on the SD card through Raspberry Pi Imager.

                      The official images can be also found on the Project Page. Further information on the Ubuntu installation for Raspberry Pi can be found here.

                      Warning

                      Please note that, at the time of this writing, only 64 bit OS image is supported.

                      "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#enable-ssh-access","title":"Enable SSH Access","text":"

                      On Ubuntu 20.04.3 the ssh access is enabled only for the standard ubuntu user. If you desire to remote login as root user, edit the file /etc/ssh/sshd_config (using the root permission) adding the line PermitRootLogin yes

                      "},{"location":"getting-started/raspberry-pi-ubuntu-20-quick-start/#eclipse-kuratm-installation","title":"Eclipse Kura\u2122 Installation","text":"

                      To install Eclipse Kura with its dependencies on the Raspberry Pi, perform the following steps:

                      1. Boot the Raspberry Pi with the latest\u00a0Ubuntu 20.04.3 LTS Server image.

                      2. Make sure your device is connected to internet. By default, eth0 lan network interface is configured in DHCP mode.

                      3. Upgrade the system:

                        sudo apt update\n
                        sudo apt upgrade\n

                        Tip

                        Optional: Since version 5.3.0 Kura also supports Eclipse Temurin\u2122 as an alternative JVM. To install it you need to perform these additional steps:

                        sudo apt-get install -y wget apt-transport-https gnupg\n
                        sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
                        sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
                        sudo apt-get update\n
                        sudo apt-get install temurin-17-jdk\n

                      4. Download the Kura package with:

                        wget http://download.eclipse.org/kura/releases/<version>/kura-<kura-version>_generic-<arch>_installer.deb\n

                        Note: replace <version> in the URL above with the version number of the latest release (e.g. 5.5.0) and <arch> with your device architecture

                      5. Install Kura with:

                        sudo apt install ./kura-<kura-version>_generic-<arch>_installer.deb\n

                        All the required dependencies will be downloaded and installed.

                      6. Set the right Wi-Fi regulatory domain based on your current world region editing the /etc/default/crda and adding the ISO 3166-1 alpha-2 code of your region.

                      7. (Optional) Configure the GPIO replacing the content of the file /opt/eclise/kura/framework/jdk.dio.properties with the following text:

                        0 = deviceType: gpio.GPIOPin, pinNumber:0, name:GPIO0\n1 = deviceType: gpio.GPIOPin, pinNumber:1, name:GPIO1\n2 = deviceType: gpio.GPIOPin, pinNumber:2, name:GPI02\n3 = deviceType: gpio.GPIOPin, pinNumber:3, name:GPIO3\n4 = deviceType: gpio.GPIOPin, pinNumber:4, name:GPIO4\n5 = deviceType: gpio.GPIOPin, pinNumber:5, name:GPIO5\n6 = deviceType: gpio.GPIOPin, pinNumber:6, name:GPIO6\n7 = deviceType: gpio.GPIOPin, pinNumber:7, name:GPIO7\n8 = deviceType: gpio.GPIOPin, pinNumber:8, name:GPIO8\n9 = deviceType: gpio.GPIOPin, pinNumber:9, name:GPIO9\n10 = deviceType: gpio.GPIOPin, pinNumber:10, name:GPIO10\n11 = deviceType: gpio.GPIOPin, pinNumber:11, name:GPIO11\n12 = deviceType: gpio.GPIOPin, pinNumber:12, name:GPIO12\n13 = deviceType: gpio.GPIOPin, pinNumber:13, name:GPIO13\n14 = deviceType: gpio.GPIOPin, pinNumber:14, name:GPIO14\n15 = deviceType: gpio.GPIOPin, pinNumber:14, name:GPIO15\n16 = deviceType: gpio.GPIOPin, pinNumber:16, name:GPIO16\n17 = deviceType: gpio.GPIOPin, pinNumber:17, name:GPIO17\n18 = deviceType: gpio.GPIOPin, pinNumber:18, name:GPIO18\n19 = deviceType: gpio.GPIOPin, pinNumber:19, name:GPIO19\n20 = deviceType: gpio.GPIOPin, pinNumber:20, name:GPIO20\n21 = deviceType: gpio.GPIOPin, pinNumber:21, name:GPIO21\n22 = deviceType: gpio.GPIOPin, pinNumber:22, name:GPIO22\n23 = deviceType: gpio.GPIOPin, pinNumber:23, name:GPIO23\n24 = deviceType: gpio.GPIOPin, pinNumber:24, name:GPIO24\n25 = deviceType: gpio.GPIOPin, pinNumber:25, name:GPIO25\n26 = deviceType: gpio.GPIOPin, pinNumber:26, name:GPIO26\n27 = deviceType: gpio.GPIOPin, pinNumber:27, name:GPIO27\n\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\nuart.UART = baudRate:19200, parity:0, dataBits:8, stopBits:1, flowControl:0\n
                      8. Reboot the Raspberry Pi with:

                        sudo reboot\n

                        Kura starts on the target platform after reboot.

                      9. Kura setups a local web ui that is available using a browser via:

                        https://<device-ip>\n

                        The browser will prompt the user to accept the connection to an endpoint with a self signed certificate, select Accept the risk and continue:

                        Once trusted the source, the user will be redirected to a login page where the following credentianls: username: admin password: admin

                      "},{"location":"getting-started/zima-board-quick-start/","title":"ZimaBoard/Blade Quick Start","text":""},{"location":"getting-started/zima-board-quick-start/#overview","title":"Overview","text":""},{"location":"java-application-development/configurable-application/","title":"Configurable Application","text":""},{"location":"java-application-development/configurable-application/#overview","title":"Overview","text":"

                      This section provides a simple example of how to create an OSGi bundle that implements the ConfigurableComponent interface in Kura. This bundle will interact with the Kura ConfigurationService via the ConfigurableComponent interface. It also uses the MQTT services in Kura to connect to the Cloud, which allows for a local configuration mechanism using a Web user-interface (UI). In this example, you will learn how to perform the following functions:

                      • Create a plugin project

                      • Implement the ConfigurableComponent interface

                      • Use the Kura web UI to modify the bundle\u2019s configuration

                      • Export a single OSGi bundle (plug-in)

                      "},{"location":"java-application-development/configurable-application/#prerequisites","title":"Prerequisites","text":"
                      • Requires Kura development environment set-up (Setting up Kura Development Environment)

                      • Implements the use of Kura web user-interface (UI)

                      "},{"location":"java-application-development/configurable-application/#configurable-component-example","title":"Configurable Component Example","text":""},{"location":"java-application-development/configurable-application/#create-plug-in","title":"Create Plug-in","text":"

                      In Eclipse, create a new Plug-in project by selecting File | New | Project. Select Plug-in Development | Plug-in Project and click Next.

                      Your screen should display the New Plug-in Project dialog box as shown in the following screen capture. Enter your project a name, such as \u201corg.eclipse.kura.example.configurable\u201d. Under Target Platform, ensure that the an OSGi framework option button is selected and set to standard as shown below. You can also (optionally) add projects to a working set. To continue, click Next.

                      In the next New Plug-in Project menu (shown below), change the Name field to something more descriptive, such as \u201cConfigurable Component Example.\u201d Make sure that the Execution Environment list is set to match the JVM version running on the target device (JavaSE-1.6 or JavaSE-1.7). To determine the JVM version running on the target device, log in to its administrative console and enter the command

                      java \u2013version

                      Also, uncheck the Generate an activator, a Java class that controls the plug-in\u2019s life cycle option button. For the purposes of this example, a single class will be used. An Activator class will not be created; instead, OSGi Declarative Services will be used to start and stop the bundle.

                      Finally, click Finish.

                      You should see the new project in the Package Explorer (or Project Explorer) in Eclipse. Also, you will see the MANIFEST.MF was automatically opened in the Manifest Editor. An OSGi bundle is a regular Java .jar file that contains Java code and resources and a custom Manifest and an Activator. The manifest will be modified in the next section.

                      "},{"location":"java-application-development/configurable-application/#add-dependencies-to-manifest","title":"Add Dependencies to Manifest","text":"

                      First, you will use the Manifest Editor in Eclipse to add some dependencies. Click the Dependencies tab at the bottom of the editor screen and then click the Automated Management of Dependencies heading to expand it.

                      Under Automated Management of Dependencies, click Add. In the Select a Plug-in field, enter org.eclipse.osgi.services. Select the plug-in name and click OK.

                      Note that this operation is very much like adding standalone jars to the buildpath by including the \u2018-cp\u2019 argument to javac. However, in this case you are telling Eclipse where to find these dependencies the \u201cOSGi way\u201d, so it is aware of them at compile time.

                      Click Add again and use the same procedure to add the following dependencies:

                      • slf4j.api
                      • org.eclipse.kura.api

                      You should now see the list of dependencies. Save changes to the Manifest.

                      "},{"location":"java-application-development/configurable-application/#create-java-class","title":"Create Java Class","text":"

                      Now you are ready to start writing a simple Java class. Right-click the org.eclipse.kura.example.configurable project. Select New | Class. Set the Package field to org.eclipse.kura.example.configurable, set the Name field to ConfigurableExample, and then click Finish.

                      Write the following code for the new class. You can copy and paste the code provided below into your newly created Java class file.

                      package org.eclipse.kura.example.configurable;\n\npublic class ConfigurableExample implements ConfigurableComponent {\n    private static final Logger s_logger = LoggerFactory.getLogger(ConfigurableExample.class);\n    private static final String APP_ID = \"org.eclipse.kura.example.configurable.ConfigurableExample\";\n    private Map<String, Object> properties;\n\n    protected void activate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n    }\n\n    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started with config!\");\n        updated(properties);\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n    }\n\n    public void updated(Map<String, Object> properties) {\n        this.properties = properties;\n        if(properties != null && !properties.isEmpty()) {\n            Iterator<Entry<String, Object>> it = properties.entrySet().iterator();\n            while (it.hasNext()) {\n                Entry<String, Object> entry = it.next();\n                s_logger.info(\"New property - \" + entry.getKey() + \" = \" +\n                entry.getValue() + \" of type \" + entry.getValue().getClass().toString());\n            }\n        }\n    }\n}\n

                      The activate() method is the entry point when the bundle is started. Note this class has two forms of the activate() method. The second method (with the \u201cMap properties\u201d parameter) enables a default configuration to be specified at bundle start time. The deactivate() method is the entry point when the bundle is stopped. You have also specified an updated() method. These methods define how the bundle receives a new configuration from the Kura configuration manager. Kura handles robust configuration management routines automatically once you implement the ConfigurableComponent interface and the updated() method."},{"location":"java-application-development/configurable-application/#resolve-dependencies","title":"Resolve Dependencies","text":"

                      At this point, there will be errors in your code because of unresolved imports.

                      Select the menu Source | Organize Imports to resolve these errors. Because you added dependencies to your dependency list in the Manifest, you will be prompted to choose one of the following two potential sources for importing a few classes.

                      For the \u201cEntry\u201d class, select java.util.Map.Entry as shown below and click Next.

                      For the \u201cLogger\u201d class, select org.slf4j.Logger as shown below and click Finish.

                      Resolving the imports should clear the errors in the class as shown in the screen capture that follows. Save the changes to the ConfigurableExample class.

                      The complete set of code (with import statements) is shown below.

                      package org.eclipse.kura.example.configurable;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport org.osgi.service.component.ComponentContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.eclipse.kura.configuration.ConfigurableComponent;\n\npublic class ConfigurableExample implements ConfigurableComponent {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(ConfigurableExample.class);\n    private static final String APP_ID* = \"org.eclipse.kura.configurable.ConfigurableExample\";\n    private Map<String, Object> properties;\n\n    protected void activate(ComponentContext componentContext) {\n      s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n    }\n\n    protected void activate(ComponentContext componentContext, Map<String, Object> properties) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has started with config!\");\n        updated(properties);\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n    }\n\n    public void updated(Map<String, Object> properties) {\n        this.properties = properties;\n        if(properties != null && !properties.isEmpty()) {\n            Iterator<Entry<String, Object>> it = properties.entrySet().iterator();\n            while(it.hasNext()) {\n                Entry<String, Object> entry = it.next();\n                s_logger.info(\"New property - \" + entry.getKey() + \" = \" +\n                entry.getValue() + \" of type \" + entry.getValue().getClass().toString());\n            }\n        }\n    }\n}\n

                      Switch back to the Manifest Editor. Under Automated Management of Dependencies, ensure the Import-Package option button is selected. Click the add dependencies link to automatically add packages to the dependencies list (under Imported Packages) based on the \u201cimport\u201d statements in your example code. Save changes to the Manifest again.

                      "},{"location":"java-application-development/configurable-application/#create-component-class","title":"Create Component Class","text":"

                      Right-click the example project and select New | Folder. Create a new folder named \u201cOSGI-INF\u201d.

                      Now, right-click the example project\u2019s \u201cOSGI-INF\u201d folder and select New | Other. From the wizard, select Plug-in Development | Component Definition and click Next.

                      Next to the Class field, click Browse and type the name of your newly created class in the Select entries field. In this case, type the word \u201cConfigurable\u201d, and you will see matching items. Select the ConfigurableExample class and click OK.

                      In the Enter or select the parent folder field, make sure \u201c/OSGI-INF\u201d is at the end of the existing entry (e.g., org.eclipse.kura.example.configurable/OSGI-INF). Set the Name field equal to the Class field as shown below:

                      Click Finish.

                      After the Component class has been created, it will open in the Workspace. On the Services tab, click the Add button under Provided Services. Enter \u201cconfigurable\u201d and select the interface \u201corg.eclipse.kura.example.configurable\u201d. This is required for components that are configurable through the Kura ConfigurationService, so that they expose a Service.

                      In the Overview tab, the Name and Class fields should already point to your Java class. Make the following settings:

                      • Set the Activate field to activate and set the Deactivate field to deactivate. This tells the component where these OSGi activation methods are located.

                      • Set the Configuration Policy to require.

                      • Set the Modified field to updated. This tells the component which method to call when the configuration is updated.

                      • Uncheck the box This component is enabled when started, then check both boxes This component is enabled when started and This component is immediately activated.

                      Click the Add Property button. Enter a property with the name \u201cservice.pid\u201d and value \u201corg.eclipse.kura.example.configurable.ConfigurableExample\u201d as shown in the screen capture below.

                      Verify that the completed Overview tab looks like the screen shot shown below and save the Component class definition file:

                      Check the Source tab of the component.xml file and carefully verify that each of the property values and tags match what is shown below:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    activate=\"activate\"\n    configuration-policy=\"require\"\n    deactivate=\"deactivate\"\n    enabled=\"true\"\n    immediate=\"true\"\n    modified=\"updated\"\n    name=\"org.eclipse.kura.example.configurable.ConfigurableExample\">\n\n    <implementation class=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    <service>\n        <provide interface=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    </service>\n    <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n</scr:component>\n

                      If Kura 3.0 or newer versions are used and the \"org.eclipse.kura.core.configuration.legacyServiceTracking\" system property is set to false or not set, proceed as follows.

                      "},{"location":"java-application-development/configurable-application/#create-the-default-configuration","title":"Create the Default Configuration","text":"

                      With the component definition file created, you also need to specify the configurable parameters of this bundle. This is done using the \u201cmetatype\u201d definition. Right-click the OSGI-INF directory for the example project in the Package Explorer window and select New | Folder. Name the folder \u201cmetatype\u201d. Next, right-click the metatype folder and select New | File.

                      Name the file \u201corg.eclipse.kura.example.configurable.ConfigurableExample.xml\u201d as shown in the following screen capture:

                      At this point, you have to write the \u2018metatype\u2019 file that defines the parameters, default values, types, etc. Click on the Source button and paste the following XML text into ConfigurableExample.xml for this example. Save changes to ConfigurableExample.xml.

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n    <OCD id=\"org.eclipse.kura.example.configurable.ConfigurableExample\"\n        name=\"ConfigurableExample\"\n        description=\"This is a sample metatype file for a simple configurable component\">\n\n        <AD id=\"param1.string\"\n            name=\"param1.string\"\n            type=\"String\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"Some Text\"\n            description=\"String configuration parameter\"/>\n\n        <AD id=\"param2.float\"\n            name=\"param2.float\"\n            type=\"Float\"\n            cardinality=\"0\"\n            required=\"false\"\n            default=\"20.5\"\n            min=\"5.0\"\n            max=\"40.0\"\n            description=\"Float configuration parameter\"/>\n\n        <AD id=\"param3.integer\"\n            name=\"param3.integer\"\n            type=\"Integer\"\n            cardinality=\"0\"\n            required=\"true\"\n            default=\"2\"\n            min=\"1\"\n            description=\"Integer configuration parameter\"/>\n    </OCD>\n\n    <Designate pid=\"org.eclipse.kura.example.configurable.ConfigurableExample\">\n        <Object ocdref=\"org.eclipse.kura.example.configurable.ConfigurableExample\"/>\n    </Designate>\n</MetaData>\n

                      In the MANIFEST.MF of this bundle, you must also make sure the XML file gets packaged into the bundle when you export the plug-in. Click the Build tab, and in the Binary Build section of the Manifest editor verify that all the checkboxes for items under META-INF and OSGI-INF are checked. Save the Manifest after making this change.

                      "},{"location":"java-application-development/configurable-application/#run-the-bundle","title":"Run the Bundle","text":"

                      At this point, you can run the bundle using the emulator in Eclipse (Linux or OS X only). To do so, expand the org.eclipse.kura.emulator project in the package explorer and browse to src/main/resources. As appropriate for you platform type, right-click Kura_Emulator_[OS].launch (where \u201c[OS]\u201d specifies your operating system) and select Run as | KURA_EMULATOR_[OS].launch. Doing so will start the Kura emulator and your new bundle in the console window of Eclipse.

                      "},{"location":"java-application-development/configurable-application/#view-the-bundle-configuration-in-the-local-web-ui","title":"View the Bundle Configuration in the Local Web UI","text":"

                      With the bundle running, open a browser window on the same computer as the Eclipse development environment and browse to the Kura web UI at http://127.0.0.1:8080. Once connected to the Kura web UI, a log in window appears prompting you to enter the Name and Password as shown below:

                      Enter the appropriate name and password (default is admin/admin) and click Log in. The Kura Admin web UI appears with the ConfigurableExample in the Services area on the left side of the browser window as shown below:

                      From the Kura Admin web UI, you can change the parameters that are used by the Kura configuration manager and in turn call the updated() method of the newly created bundle. To do so, click ConfigurableExample and the configurable component parameters will be displayed as shown below:

                      Make any necessary changes and click the Apply button near the top left of the configuration pane for the modifications to take affect. Every time a change is made to the configuration, a new snapshot is generated along with an ID.

                      "},{"location":"java-application-development/connected-application/","title":"Connected Application","text":""},{"location":"java-application-development/connected-application/#overview","title":"Overview","text":"

                      This section describes the prepackaged heater demo bundle that comes with the Kura development environment and demonstrates how to perform the following functions:

                      • Run the Kura Emulator

                      • Connect to the Cloud

                      • Gain an understanding of ConfigurableComponents in Kura

                      • Modify configurations of custom bundles

                      "},{"location":"java-application-development/connected-application/#prerequisites","title":"Prerequisites","text":"
                      • Setting up Kura Development Environment

                      • Using the Kura web UI

                      "},{"location":"java-application-development/connected-application/#heater-demo-introduction","title":"Heater Demo Introduction","text":"

                      The org.eclipse.kura.demo.heater bundle is a simple OSGi bundle that represents a thermostat and heater combination. The application utilizes the Kura ConfigurableComponent interface to be able to receive configuration updates through the local Kura web UI. In addition, this bundle utilizes OSGi declarative services and the Kura CloudClientListener. This tutorial demonstrates how to modify configurations of custom bundles and shows how those configuration changes can dynamically impact the behavior of the bundle through the Kura web UI.

                      "},{"location":"java-application-development/connected-application/#code-walkthrough","title":"Code Walkthrough","text":"

                      The following sections will highlight three important API layers when creating an application that will publish to the cloud. These layers are:

                      • DataTransportService
                      • Available for standard MQTT messaging. Allows consumers of the service to connect to brokers, publish messages, and receive messages on subscribed topics
                      • DataService
                      • Delegates data transport to the DataTransportService
                      • Provides extended features for managing broker connections, buffering of published messages, and priority based delivery of messages
                      • CloudService
                      • Further extends the functionality of DataService
                      • Provides means for more complex flows (i.e. request/response)
                      • Manages single broker connection across multiple applications
                      • Provides payload data model with encoding/decoding serializers
                      • Publishes life cycle manages for devices and applications
                      "},{"location":"java-application-development/connected-application/#acquiring-cloudclient","title":"Acquiring CloudClient","text":"

                      The CloudService can manage multiple applications over a shared MQTT connection by treating each application as a client. The example code uses the \"setCloudService\" and \"unsetCloudService\" methods for referencing and releasing the CloudService. In the bundles activate method, the service reference in conjunction with a unique application ID can then be used to obtain a CloudClient. The relevant code is shown below (ommitted sections are denoted by ==OMMITTED==):

                      ==OMMITTED==\n// Cloud Application identifier\nprivate static final String APP_ID = \"heater\";\n\n==OMMITTED==\n\npublic void setCloudService(CloudService cloudService) {\n    m_cloudService = cloudService;\n}\n\npublic void unsetCloudService(CloudService cloudService) {\n    m_cloudService = null;\n}\n\n==OMMITTED==\n\n// Acquire a Cloud Application Client for this Application\ns_logger.info(\"Getting CloudClient for {}...\", APP_ID);\nm_cloudClient = m_cloudService.newCloudClient(APP_ID);\n
                      "},{"location":"java-application-development/connected-application/#publishingsubscribing","title":"Publishing/Subscribing","text":"

                      The private \"doPublish\" method is used to publish messages at a fixed rate. The method demonstrates how to use the CloudClient and KuraPayload to publish MQTT messages.

                      ==OMMITTED==\n\n// Allocate a new payload\nKuraPayload payload = new KuraPayload();\n\n// Timestamp the message\npayload.setTimestamp(new Date());\n\n// Add the temperature as a metric to the payload\npayload.addMetric(\"temperatureInternal\", m_temperature);\npayload.addMetric(\"temperatureExternal\", 5.0F);\npayload.addMetric(\"temperatureExhaust\",  30.0F);\n\nint code = m_random.nextInt();\nif ((m_random.nextInt() % 5) == 0) {\n    payload.addMetric(\"errorCode\", code);\n}\nelse {\n    payload.addMetric(\"errorCode\", 0);\n}\n\n// Publish the message\ntry {\n    m_cloudClient.publish(topic, payload, qos, retain);\n    s_logger.info(\"Published to {} message: {}\", topic, payload);\n}\ncatch (Exception e) {\n    s_logger.error(\"Cannot publish topic: \"+topic, e);\n}\n

                      Similarly, the CloudClient can be used to subscribe to MQTT topics. Although not shown in the example code, the following snippet could be added to subscribe to all published messages:

                      m_cloudClient.subscribe(topic, qos);\n
                      "},{"location":"java-application-development/connected-application/#callback-methods","title":"Callback Methods","text":"

                      The example class implements CloudClientListener, which provides methods for several common callback methods. The below snippet shows the relevant code for creating the listeners for the demo application.

                      ==OMMITTED==\n\npublic class Heater implements ConfigurableComponent, CloudClientListener\n\n==OMMITTED==\n\nm_cloudClient.addCloudClientListener(this);\n

                      The available methods for implementation are:

                      • onControlMessageArrived: Method called when a control message is received from the broker.
                      • onMessageArrived: Method called when a data message is received from the broker.
                      • onConnectionLost: Method called when the client has lost connection with the broker.
                      • onConnectionEstablished: Method called when the client establishes a connection with the broker.
                      • onMessageConfirmed: Method called when a published message has been fully acknowledged by the broker (not applicable for qos 0 messages).
                      • onMessagePublished: Method called when a message has been transfered from the publishing queue to the DataTransportService.

                      For more information on the various Kura APIs, please review the Kura APIs

                      "},{"location":"java-application-development/connected-application/#run-the-bundle","title":"Run the Bundle","text":"

                      By default, the heater demo bundle does not run automatically. To run the bundle and Kura in the Emulator, locate the org.eclipse.kura.emulator project. Expand it to show the src/main/resources folder.

                      Right-click the correct Kura_Emulator_[OS].launch file, depending on which operating system you are running. In the context menu, select the Run As option, and click on Run Configurations.

                      Under OSGi Framework (Run Configurations window shown below), click on the Kura_Emulator_[OS] entry. In the Bundles tab under Workspace, enable the org.eclipse.kura.demo.heater checkbox to enable it as shown below:

                      Click the Apply and Run buttons to start the Kura Emulator. Once this setting has been made, you only need to right-click on the launch file and select Run As and the Kura_Emulator_[OS] option to run with the same settings.

                      This will start Kura running locally and will display a Console window in Eclipse. The Console window will show the OSGi diagnostics as various bundles start and execute.

                      "},{"location":"java-application-development/connected-application/#configure-the-mqtt-client","title":"Configure the MQTT Client","text":"

                      With the heater demo bundle running, open a browser window on the same computer as the Eclipse development environment and browse to the Kura web UI at http://127.0.0.1:8080. Once connected to the Kura web UI, a log in window appears prompting you to enter the Name and Password as shown below:

                      Enter the appropriate name and password (default is admin/admin) and click Log in. The Kura Admin web UI appears as shown below:

                      From the Kura web UI, click on MqttDataTransport in the Services pane on the lower left of the browser window. You will see a menu similar to the one shown in the following screen capture:

                      Fill in the following fields then click the Apply button:

                      Field Value broker-url: The url for the MQTT broker (this example shows the MQTT broker-url mqtt://iot.eclipse.org:1883/ hosted by the Eclipse Foundation) topic.context.account-name: Your [account_name] username: Typically [account_name]_broker password: The password for your user client-id The client identifier to be used when connecting to the MQTT broker (optional)

                      Now that the account credentials are set in the MqttDataTransport service, the DataService needs to be configured to connect by default. To do so, click DataService in the Services area on the left of the browser window. For the \u2018connect.auto-on-startup\u2019 parameter, select true as shown below:

                      "},{"location":"java-application-development/connected-application/#modify-bundle-configuration-in-local-web-ui","title":"Modify Bundle Configuration in Local Web UI","text":"

                      Bundles changes may be made directly in the emulator web UI. Since you are running an emulated device in Eclipse, you can do this by browsing to http://127.0.0.1:8080 (same URL where the MQTT client was configured in the previous section of this tutorial). If the bundle was running on a real device and you had network access to it, you would browse to http://[ip_address_of_device].

                      From the Kura web UI, select the Heater bundle from the configurable services on the left and modify the parameters as needed (shown in the screen capture below). By default, the heater demo is configured according to the following characteristics and assumptions about its operational environment:

                      • Start operation is at 6:00am (06:00).

                      • End operation is at 10:00pm (22:00).

                      • It is colder outside than inside the heated chamber (hard-coded to 5 degrees in the application).

                      • Output of the heater is constant at 30 degrees (hard-coded).

                      • When in operational mode, the temperature will drop inside if the heater is off.

                      • The heater turns off when it is about to exceed the setPoint defined in the configuration.

                      • After the temperature drops to four times the increment point (a made-up value to show dropping temperature, hard-coded in the application), the heater turns back on, and the temperature starts increment at the rate of the \u2018temperature.increment\u2019 rate.

                      Click Apply for changes to take affect. The updated() method is called after settings are applied for the new configuration.

                      After completing this tutorial, it is highly recommended that you review the heater demo source code in Eclipse to see how it is put together. Kura automatically generates the user configuration interface through implementation of the ConfigurableComponent interface and some small additions to the component.xml file (called heater.xml). This powerful feature provides both a local and remote configuration user interface with no additional development requirements.

                      "},{"location":"java-application-development/contributing/","title":"Contributing","text":"

                      Contributing to Eclipse Kura project is very easy.

                      The steps required to submit code to the project can be found on Github Contributing Page.

                      If you face any issues, or just want to get involved with the kura community feel free to join us on:

                      • Github Issues: for bug reporting.
                      • Github Discussions: for receiving feedback, making new proposals and generally talking about the project.
                      "},{"location":"java-application-development/contributing/#kura-dev-meeting","title":"Kura Dev Meeting","text":"

                      If you want to get involved in the development process you can join us at the Kura Dev Meeting. The meeting is held every 2 weeks on Wednesday, at 5 PM CEST on Microsoft Teams.

                      You can join by using this link.

                      The scheduled dates for the meeting can be found here. Unzip the calendar file and double-click on it to add the scheduled dates to your calendar.

                      "},{"location":"java-application-development/deploy-and-debug-applications/","title":"Deploy and Debug Applications","text":""},{"location":"java-application-development/deploy-and-debug-applications/#overview","title":"Overview","text":"

                      This section provides a simple example of how to test and deploy OSGi bundles and deployment packages in a Kura environment. These instructions use the \u201cHello World\u201d OSGi project created in the previous section. In this example, you will learn how to perform the following functions:

                      • Use local OSGi emulation mode in Eclipse

                      • Deploy a bundle to a remote target running the OSGi Framework

                      • Install a Deployment Package to a remote target running the OSGi Framework

                      • Manage OSGi bundles on a target device

                      • Set bundle Logger levels in Kura

                      "},{"location":"java-application-development/deploy-and-debug-applications/#prerequisites","title":"Prerequisites","text":"
                      • Setting up Kura Development Environment

                      • Hello World Using the Kura Logger

                      "},{"location":"java-application-development/deploy-and-debug-applications/#testing-the-osgi-plug-in","title":"Testing the OSGi Plug-in","text":"

                      Once you have created an OSGi plug-in, you can test it in Local Emulation Mode and/or deploy it to a Remote Target Device.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#local-emulation-mode","title":"Local Emulation Mode","text":"

                      The Kura user workspace can be used in Eclipse in local emulation mode (Linux/OS X only; this feature is not currently supported under Windows). To deploy the code to a running system, see the section Remote Target Device.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#run-kura-in-emulator-mode","title":"Run Kura in Emulator Mode","text":"

                      In the Eclipse workspace, locate the org.eclipse.kura.emulator project. Expand it to show the src/main/resources folder.

                      Right-click the Kura_Emulator.launch file. In the context menu, select the Run as option, and select the Kura_Emulator*. This will start Kura running locally and will display a Console window in the bottom pane in Eclipse. The Console window will show the OSGi diagnostics as various bundles start and execute.

                      Because the org.eclipse.kura.example.hello_osgi bundle is in the workspace with a valid activate() method, it is automatically started with the Kura OSGi framework. Note the INFO message highlighted below that shows the bundle\u2019s activate() method was run.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#list-osgi-bundles-in-local-mode","title":"List OSGi Bundles in Local Mode","text":"

                      With the OSGi framework running in the Eclipse console (refer to the previous section), click in the Console window. Press Enter/Return and then type the \u2018ss\u2019 command to show a list of installed bundles. Note the bundle ID number for the org.eclipse.kura.example.hello_osgi bundle.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#startstop-bundle-in-local-mode","title":"Start/Stop Bundle in Local Mode","text":"

                      In the OSGi Console window in Eclipse, run the

                      start ## or stop ##

                      commands to start or stop a bundle, where the \u201c##\u201d is either the bundle ID number or the bundle name (such as \u201cstart org.eclipse.kura.example.hello_osgi\u201d). Note that the INFO messages for both the activate() and deactivate() messages appear in the Console window when the bundle is started or stopped.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#installuninstall-bundle-in-local-mode","title":"Install/Uninstall Bundle in Local Mode","text":"

                      In the OSGi Console window in Eclipse, bundles can be installed or uninstalled. To uninstall the example bundle, issue the command \u2018uninstall ##\u2019, where \u201c##\u201d is either the bundle ID number or the bundle name, such as:

                      uninstall 47 or uninstall org.eclipse.kura.example.hello_osgi

                      A message will appear indicating that the bundle has been stopped.

                      Once the bundle has been uninstalled from the local OSGi console, it cannot be started or installed by number or name. Instead, it must be installed by using the plug-in JAR file created earlier. Issue the \u2018install\u2019 command to install a bundle into the Emulation environment:

                      install file:/[*path_to_bundle*]/[*bundle_name*].jar

                      where \u201c[path_to_bundle]/[bundle_name].jar\u201d should be replaced with the name of the bundle exported earlier (the section Hello World Using the Kura Logger), as shown in the example below:

                      install file:/Users/Nina/Documents/myPlugins/plugins/plugins/plugins/plugins/plugins/org.eclipse.kura.example.hello_osgi_ 1.0.0.201409101740.jar

                      Then the bundle can be started or stopped, as described in the previous section, Start/Stop Bundle in Local Mode. Optionally, you can add the flag \u2018-start\u2019 to the \u2018install\u2019 command to automatically start the bundle after installation.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#remote-target-device","title":"Remote Target Device","text":"

                      One or more OSGi bundles can be deployed to a remote device running Kura, either by installing separate bundle files or deployment packages using Eclipse.

                      Warning

                      These steps require Kura to be running on the target device.

                      This method of deployment is temporary on the remote target device and is not persistent after a restart. To make the deployment permanent, see Making Deployment Permanent.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#connect-to-remote-osgi-framework","title":"Connect to Remote OSGi Framework","text":"

                      To deploy a bundle to the remote target device, you will need to connect Eclipse to the OSGi framework running on the device. This is done using mToolkit. See Kura Setup for instructions on installing mToolkit into the Eclipse development environment.

                      • Select the Eclipse menu Window | Show View | Other.

                      • Select mToolkit -> Frameworks entry to open the mToolkit Frameworks view.

                      • Enter a name for the framework definition and the IP address of the target device.

                      Close the dialog by clicking the OK button.

                      Warning

                      The remote target device must have port 1450 open in its firewall, in order to allow mToolkit o make a connection to its OSGi framework. If this port is not opened, refer to the section Open Port for OSGi Remote Connection.

                      Right-click the framework icon name and select Connect Framework. The list of installed bundles and deployment packages should be retrieved shortly. (Use the Disconnect Framework option to disconnect from the remote target framework when finished.)

                      "},{"location":"java-application-development/deploy-and-debug-applications/#open-port-for-osgi-remote-connection","title":"Open Port for OSGi Remote Connection","text":"

                      In order to allow mToolkit to make a remote connection to the OSGi framework on the target device, the device must allow the incoming port in its firewall. To set this option, open a Web browser and log into Kura using its current IP address, such as:

                      http://10.11.5.4

                      Click the Firewall icon and then click the Open Ports tab. If port 1450 is not shown in the list of allowed ports, click the New button under Open Ports. Enter the port 1450 and select protocol TCP. Then click Submit.

                      Now, click Apply to apply changes to the remote device.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#install-single-bundle-to-target-device","title":"Install Single Bundle to Target Device","text":"

                      With the Eclipse environment connected to the remote OSGi target framework, a single bundle can be installed on the remote device.

                      In the mToolkit Frameworks view, right-click the Framework name and select Install Bundle. (This requires that you have exported the bundle as a deployable plug-in JAR file. See the section Hello World Using the Kura Logger.)

                      Use the Browse button to select the JAR file and click OK to install it to the target device.

                      The newly installed bundle should be shown in the Frameworks view under Bundles.

                      To control operation of the bundle through the OSGi Frameworks view, right-click the bundle name. The following actions can be performed:

                      • Start \u2013 start the bundle

                      • Stop \u2013 stop the bundle

                      • Update \u2013 reinstall the bundle

                      • Install Bundle \u2013 install a different bundle

                      • Uninstall Bundle \u2013 remove this bundle from the target device

                      • Show Bundle IDs / Show Bundle Versions \u2013 show additional information about bundles

                      You can also verify operation of the bundle on the target device itself. See the section Manage Bundles on Target Device.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#install-deployment-package-to-target-device","title":"Install Deployment Package to Target Device","text":"

                      With the Eclipse environment connected to the remote OSGi target framework, a deployment package can be installed on the remote device.

                      NOTE: If you have just installed the individual bundle in the previous section, you should uninstall it before proceeding. Doing so will avoid any confusion in having the same bundle installed twice.

                      In the mToolkit Frameworks view, right-click the Framework name and select Install Deployment Package. (This step requires that you have exported the bundle as a deployable plug-in JAR file. See the section Hello World Using the Kura Logger for instructions on exporting the OSGi bundle.)

                      Open the resources/dp folder in the Workspace filesystem directory, select the .dp file (not the \u201c.dpp\u201d file), and click OK.

                      The deployment package will be installed on the target device and shown in the Frameworks view under Deployment Packages. (The deployment package can also be uninstalled from the Framework view.)

                      The bundle included in the deployment package can also be viewed under Bundles and can be controlled remotely (start/stop) as with other bundles. The operation of the bundle can also be verified on the target device itself. See the section Manage Bundles on Target Device.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#connect-to-osgi-on-target-device","title":"Connect to OSGi on Target Device","text":"

                      You can manage the OSGi framework on a target device by logging into a console on the device using a connected keyboard and VGA monitor or over a network connection using SSH (PuTTY in Windows or \u2018ssh\u2019 from Linux or Mac).

                      At the command prompt, display the Kura log file with:

                      tail -f /var/log/kura.log

                      Connect to the OSGi framework by typing the following commands:

                      telnet localhost 5002

                      There are many commands available in the OSGi console for managing bundles. Following are just a few useful commands:

                      Command Description ss Lists names and ID of bundles help Displays the help menu of OSGi commands lb Lists all installed bundles and IDs h [bundle IDs] Displays bundle headers (i.e., Bundle Manifest Version, Name, Required Execution Environment, Symbolic Name, Version, Import Package, Manifest Version, and Service Component) exit Exits the OSGi console and stops Kura disconnect Exits the OSGi console, but leaves Kura running"},{"location":"java-application-development/deploy-and-debug-applications/#manage-bundles-on-target-device","title":"Manage Bundles on Target Device","text":"

                      From the OSGi command line, you can display a list of bundles with the \u2018ss\u2019 command as shown in the example below:

                      ss

                      In this example, the org.eclipse.kura.example.hello_osgi bundle ID is 64.

                      You can run the \u2018start ##\u2019 or \u2018stop ##\u2019 commands to start or stop a bundle, where the \u201c##\u201d is either the bundle ID number or the bundle name (such as \u201cstart org.eclipse.kura.example.hello_osgi\u201d). To verify that the bundled is stopped, you can issue the \u2018ss\u2019 command. If the bundled is stopped, the activity will show RESOLVED (as shown below). If the bundle is started, the activity will show ACTIVE (as shown above).

                      "},{"location":"java-application-development/deploy-and-debug-applications/#set-kura-logger-levels","title":"Set Kura Logger Levels","text":"

                      Kura logger levels are defined in a configuration file. The messages that appear require a log statement in the application and that the log level of the statement matches the log level of the application (such as logger.info or logger.debug).

                      To set or change logger levels, the Kura logger configuration file may be modified using the vi editor. From the Linux command prompt, enter the following command:

                      vi /opt/eclipse/kura/kura/log4j.properties

                      At the bottom of the \u201clog4j.properties\u201d file, there will be one or more \u201clog4j\u201d logger property entries, which determine the logger level used by the bundles at startup.

                      In the example screen capture shown below, the \u201clog4j.logger.org.eclipse.kura\u201d property has been set to \u201cINFO\u201d, which applies to all bundles that start with \u201corg.eclipse.kura.\u201d Additional, more specific, properties may be defined as required for your particular logging needs. The property entries will take on the defined logger level at startup. The logger levels are hierarchical, so that those in a deeper level of the hierarchy will apply; otherwise, the more general logger level will override them.

                      Once you have made the necessary changes, save and close the file using the \u2018:wq\u2019 command. Restart Kura, and check the log levels in the OSGi console again to make sure that the desired levels have taken effect.

                      "},{"location":"java-application-development/deploy-and-debug-applications/#making-deployment-permanent","title":"Making Deployment Permanent","text":"

                      The mToolkit deployment of a package is a temporary installation and does not make the package permanent. Once a set of bundles has been tested on the remote target device and is ready for permanent deployment, the software can be installed on a device with deployment packages from the command line of a target device using the instructions below:

                      1. Copy the deployment package file (*.dp) to the target device, into the folder:

                      /opt/eclipse/kura/kura/packages

                      1. Edit the dpa.properties file through the vi editor by entering the following command:

                      vi /opt/eclipse/kura/kura/dpa.properties

                      1. Add an entry in the dpa.properties file to include the new package name, such as:

                      package_name=file\\:/opt/eclipse/kura/kura/packages/package_filename.dp

                      where, \u201cpackage_name\u201d and \u201cpackage_filename\u201d should be replaced with the actual name of the deployment package.

                      1. Save and close the file using the \u2018:wq\u2019 command.

                      2. Then restart Kura, and the new package should be installed in addition to the default Kura package.

                      In conclusion, this section described how to test a bundle in an Emulation environment within the Eclipse IDE and how to install bundles and Deployment Packages to a remote target system running Kura.

                      "},{"location":"java-application-development/development-environment-setup/","title":"Development Environment Setup","text":"

                      In this document we'll cover the required steps to setup the Development Environment for contributing to the Eclipse Kura project. If, instead, you want to develop applications or bundles running on Eclipse Kura refer to the Eclipse Kura Workspace setup guide.

                      The Eclipse Kura development environment may be installed on Windows, Linux, or Mac OS. The setup instructions will be the same across each OS though each system may have unique characteristics.

                      Info

                      The local emulation of Eclipse Kura code is only supported in Linux and Mac, not in Windows.

                      This document will cover the use of Eclipse Oomph installer which is the easiest way to install and configure the Eclipse IDE to start contributing to Eclipse Kura.

                      The setup requires three basic steps:

                      1. Requirements installation
                      2. Eclipse Oomph setup
                      3. Eclipse Kura maven build
                      "},{"location":"java-application-development/development-environment-setup/#requirements","title":"Requirements","text":"

                      Before building Eclipse Kura, you need to have the following programs installed in your system:

                      • JDK 1.8 (or JDK 17)
                      • Maven 3.5.x (or greater)

                      Recommended additional software:

                      • Git
                      "},{"location":"java-application-development/development-environment-setup/#installing-prerequisites-in-mac-os","title":"Installing Prerequisites in Mac OS","text":"

                      To install Java 8, download the JDK tar archive from the Adoptium Project Repository.

                      Once downloaded, copy the tar archive in /Library/Java/JavaVirtualMachines/ and cd into it. Unpack the archive with the following command:

                      sudo tar -xzf <archive-name>.tar.gz\n
                      The tar archive can be deleted afterwards.

                      Depending on which terminal you are using, edit the profiles (.zshrc, .profile, .bash_profile) to contain:

                      # Adoptium JDK 8\nexport JAVA_8_HOME=/Library/Java/JavaVirtualMachines/<archive-name>/Contents/Home\nalias java8='export JAVA_HOME=$JAVA_8_HOME'\njava8 \n
                      Reload the terminal and run java -version to make sure it is installed correctly.

                      Using Brew you can easily install Maven from the command line:

                      brew install maven@3.5\n
                      Run mvn -version to ensure that Maven has been added to the PATH. If Maven cannot be found, try running brew link maven@3.5 --force or manually add it to your path with:
                      export PATH=\"/usr/local/opt/maven@3.5/bin:$PATH\"\n

                      "},{"location":"java-application-development/development-environment-setup/#installing-prerequisites-in-linux","title":"Installing Prerequisites in Linux","text":"

                      For Java

                      sudo apt install openjdk-8-jdk\n

                      For Maven

                      You can follow the tutorial from the official Maven site. Remember that you need to install 3.5.x version or greater.

                      "},{"location":"java-application-development/development-environment-setup/#eclipse-oomph-setup","title":"Eclipse Oomph setup","text":"

                      Download the latest Eclipse Installer appropriate for your platform from the Eclipse Downloads page and start it.

                      Switch to \"Advanced Mode\" (top right hamburger menu) and select \"Eclipse IDE for Eclipse Committers\" and configure the \"Product Version\" to be the version 2023-03 or newer.

                      Select the Eclipse Kura installer from the list. If this is not available, add a new installer from https://raw.githubusercontent.com/eclipse/kura/develop/kura/setups/kura.setup, then check and press the \"Next\" button.

                      Variables setup

                      • Select the \"Developer Type\":
                        • \"User\": if you want to develop applications or bundles running on Eclipse Kura, select this option. It will install only the APIs and the examples.
                        • \"Developer\": if you are a framework developer, select this option. It will download and configure the Eclipse Kura framework (for the purpose of this document we'll use this option)
                      • Set the JRE 1.8 location value to the installed local jdk-8 VM
                      • Update Eclipse Kura Git repository username (prefer the anonymous HTTPS option, link to your fork) and customize further settings if you like (e.g. Root install folder, Installation folder name). To show these options, make sure that the \"Show all variables\" checkbox is enabled.

                      If you plan to contribute to Eclipse Kura you might want to create a fork, see our contributing guide for further informations. For the purpose of this tutorial we'll work with a fictional fork for the username user. To clone the repo use the link appropriate for your fork, in our case it will be: https://github.com/user/kura.git

                      Keep in mind that the \"Root install folder\" is where the Eclipse executable will be installed and the Eclipse Kura sources will be downloaded (in the git subfolder).

                      Press Next, leave all Bootstrap Tasks selected and press the Finish button

                      Accept all the licenses and wait for the installation to finish.

                      At first startup Eclipse IDE will checkout the code, perform a full build and configure a few Working Sets

                      When the tasks are completed, go to into the Package Explorer and Target Platform > Target-Definition > Kura Target Platform Equinox 3.16.0, and press \"Set as Target Platform\" located at the top right of the window:

                      "},{"location":"java-application-development/development-environment-setup/#eclipse-kura-maven-build","title":"Eclipse Kura maven build","text":"

                      Navigate to the git folder created within the Eclipse workspace (~/iot-kura-workspace in the example above) and build the target platform:

                      mvn -f target-platform/pom.xml clean install\n

                      Then build the core components:

                      mvn -f kura/pom.xml clean install\n

                      Build the examples (optional):

                      mvn -f kura/examples/pom.xml clean install\n

                      Build the target profiles:

                      mvn -f kura/distrib/pom.xml clean install -DbuildAll\n

                      Note

                      You can skip tests by adding -Dmaven.test.skip=true in the commands above and you can compile a specific target by specifying the profile (e.g. -Praspberry-pi-armhf).

                      "},{"location":"java-application-development/development-environment-setup/#build-scripts","title":"Build scripts","text":"

                      Alternatively you can use the build scripts available in the root directory.

                      ./build-all.sh\n

                      or

                      ./build-menu.sh\n

                      and select the profiles you want to build.

                      "},{"location":"java-application-development/hello-world-application/","title":"Hello World Application","text":""},{"location":"java-application-development/hello-world-application/#overview","title":"Overview","text":"

                      This section provides a simple example of how to create a Kura \u201cHello World\u201d OSGi project using Eclipse. With this example, you will learn how to perform the following functions:

                      • Create a plugin project

                      • Consume the Kura Logger service

                      • Write an OSGi Activator

                      • Export a single OSGi bundle (plug-in)

                      • Create a Deployment Package

                      "},{"location":"java-application-development/hello-world-application/#prerequisites","title":"Prerequisites","text":"

                      Setting up the Kura Development Environment

                      "},{"location":"java-application-development/hello-world-application/#hello-world-using-the-kura-logger","title":"Hello World Using the Kura Logger","text":""},{"location":"java-application-development/hello-world-application/#create-hello-world-plug-in","title":"Create Hello World Plug-in","text":"

                      In Eclipse, create a new Plug-in project by selecting File | New | Project. Select Plug-in Development | Plug-in Project and click Next.

                      Your screen should display the New Plug-in Project dialog box as shown in the following screen capture. Enter your project a name, such as \u201corg.eclipse.kura.example.hello_osgi\u201d, in the appropriate field. Under Target Platform, ensure that the an OSGi framework option button is selected and set the variable to standard as shown below. You can also (optionally) add projects to a working set. To continue, click Next.

                      In the next New Plug-in Project menu (shown below), change the Name field to a descriptive name, such as \u201cHello World Example with Logger\u201d.

                      Also, verify that the Execution Environment list is set to match the Java JVM version running on the target device (JavaSE-1.8 or JavaSE-11). To determine the JVM version running on the target device, log in to its administrative console and enter the command

                      java \u2013version

                      Finally, uncheck the Generate an activator, a Java class that controls the plug-in\u2019s life cycle option button. For the purposes of this example, a single class will be used. An Activator class will not be created; instead, OSGi Declarative Services will be used to start and stop the bundle.

                      Click Finish.

                      If the Open Associated Perspective pop-up window (shown below) appears for adding Plug-ins and Error Log views, select Yes or No depending on your development requirements.

                      You should see the new project in the My Projects working set in the Package Explorer (or Project Explorer). Also, you will see the MANIFEST.MF was automatically opened in the Manifest Editor. An OSGi bundle is a regular Java .jar file that contains Java code and resources and a custom Manifest and an Activator.

                      "},{"location":"java-application-development/hello-world-application/#add-dependencies-to-manifest","title":"Add Dependencies to Manifest","text":"

                      First, you will use the Manifest Editor in Eclipse to add some dependencies. Click the Dependencies tab at the bottom of the editor screen and then click the Automated Management of Dependencies heading to expand it.

                      Under Automated Management of Dependencies, click Add. In the Select a Plug-in field, enter org.eclipse.osgi.services. Select the plug-in name and click OK.

                      Note that this operation is very much like adding standalone jars to the buildpath by including the \u2018-cp\u2019 argument to javac. However, in this case you are telling Eclipse where to find these dependencies the \u201cOSGi way\u201d, so it is aware of them at compile time. Click Add again and use the same procedure to add the following dependency:

                      • slf4j.api

                      You should now see the list of dependencies. Save changes to the Manifest.

                      "},{"location":"java-application-development/hello-world-application/#create-java-class","title":"Create Java Class","text":"

                      Now you are ready to start writing a simple Java class. Right-click the org.eclipse.kura.example.hello_osgi project. Select New | Class. The New Java Class window appears as shown below. Set the Source folder to org.eclipse.kura.example.hello_osgi/src. Set the Package field to org.eclipse.kura.example.hello_osgi, set the Name field to HelloOsgi, and then click Finish.

                      Write the following code for the new class. You can copy and paste the code provided below into your newly created Java class file.

                      package org.eclipse.kura.example.hello_osgi;\n\npublic class HelloOsgi {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(HelloOsgi.class);\n\n    private static final String APP_ID = \"org.eclipse.kura.example.hello_osgi\";\n\n    protected void activate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n\n        s_logger.debug(APP_ID + \": This is a debug message.\");\n\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n\n    }\n\n}\n

                      The activate() method is the entry point when the bundle in started. The deactivate() method is the entry point when the bundle is stopped.

                      Notice the use of the private LoggerFactory.getLogger() method. If the LoggerFactory method is present (running) in the OSGi framework and your hello_osgi bundle is started, your activate method is called, and you can simply access the service by calling the getLogger() method.

                      One convenient feature of Eclipse, auto-completion, is worth mentioning here. If you type \u2018s_logger.\u2019 (instance name of the \u201cLoggerFactory.getLogger\u201d method) and stop after the period, it will show you a list of methods implemented in that class. The examples above show two different methods used for logging messages. Logger methods include: \u201cerror\u201d, \u201cwarn\u201d, \u201cinfo\u201d, \u201cdebug\u201d, and \u201ctrace\u201d, which represent increasingly lower (more detailed) levels of log information. Logger levels should generally be used to represent the following conditions:

                      • ERROR - A serious problem has occurred that requires attention from the system administrator.

                      • WARNING - An action occurred or a condition was discovered that should be reviewed and may require action before an error occurs. It may also be used for transient issues.

                      • INFO - A report of a normal action or event. This could be a user operation, such as \"login completed\", or an automatic operation, such as a log file rotation.

                      • DEBUG - A debug message used for troubleshooting or performance monitoring. It typically contains detailed event data including things an application developer would need to know.

                      • TRACE - A fairly detailed output of diagnostic logging, such as actual bytes of a particular message being examined.

                      "},{"location":"java-application-development/hello-world-application/#resolve-dependencies","title":"Resolve Dependencies","text":"

                      At this point, there will be errors in your code because of unresolved imports.

                      Select the menu Source | Organize Imports to resolve these errors. Because you added the \u201corg.slf4j\u201d to your dependency list, you will be prompted to choose one of two potential sources for importing the \u201cLogger\u201d class. Select org.slf4j.Logger and click Finish.

                      Now the errors in the class should have been resolved. Save the HelloOsgi class.

                      The complete set of code (with import statements) is shown below.

                      package org.eclipse.kura.example.hello_osgi;\n\nimport org.osgi.service.component.ComponentContext;\n\nimport org.slf4j.Logger;\n\nimport org.slf4j.LoggerFactory;\n\npublic class HelloOsgi {\n\n    private static final Logger s_logger = LoggerFactory.getLogger(HelloOsgi.class);\n\n    private static final String APP_ID = \"org.eclipse.kura.example.hello_osgi\";\n\n    protected void activate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has started!\");\n\n        s_logger.debug(APP_ID + \": This is a debug message.\");\n\n    }\n\n    protected void deactivate(ComponentContext componentContext) {\n\n        s_logger.info(\"Bundle \" + APP_ID + \" has stopped!\");\n\n    }\n\n}\n

                      For more information on using the Simple Logging Facade for Java (slf4j), see the Logger API.

                      Switch back to the Manifest Editor. Under Automated Management of Dependencies, ensure the Import-Package option button is selected. Click the add dependencies link to automatically add packages to the dependencies list based on the \u201cimport\u201d statements in your example code. Save changes to the Manifest again.

                      "},{"location":"java-application-development/hello-world-application/#create-component-class","title":"Create Component Class","text":"

                      Right-click the example project and select New | Other. From the wizard, select Plug-in Development | Component Definition and click Next.

                      Warning

                      This option is available only if the Plug-in Development Environment (PDE) is installed in Eclipse (plugins can be installed into Eclipse IDE by searching the name in the Eclipse Marketplace under the Help menu).

                      In the Class field of the New Component Definition window shown below, click Browse.

                      Enter the name of your newly created class in the Select entries field. In this case, type the word \u201chello\u201d, and you will see a list of matching items. Select the HelloOsgi class and click OK.

                      In the Enter or select the parent folder field of the New Component Definition window, add \"/OSGI-INF\" to the existing entry (e.g., org.eclipse.kura.example.hello_osgi/OSGI-INF). Then click Finish.

                      After the Component class has been created, it will open in the Workspace. In the Overview tab, the Name and Class point to our Java class. Set the Activate field to activate and set the Deactivate field to deactivate. Doing so tells the component where these OSGi activation methods are located. Then save the Component class definition file.

                      "},{"location":"java-application-development/hello-world-application/#deploying-the-plug-in","title":"Deploying the Plug-in","text":"

                      The next few sections describe how to create a stand-alone JAR file as a deployable OSGI plug-in and how to create an installable Deployment Package.

                      An OSGi bundle is a Java archive file containing Java code, resources, and a Manifest.

                      A Deployment Package is a set of resources grouped into a single package file that may be deployed in the OSGi framework through the Deployment Admin service and may contain one or more bundles, configuration objects, etc.

                      "},{"location":"java-application-development/hello-world-application/#export-the-osgi-bundle","title":"Export the OSGi Bundle","text":"

                      Your bundle can be built as a stand-alone OSGi plug-in.

                      To do so, right-click the project and select the Export menu. This is equivalent to running javac on your project. From the wizard, select Plug-in Development | Deployable plug-ins and fragments and click Next.

                      Under Available Plug-ins and Fragments of the Export window, ensure the newly created plug-in is selected. Under Destination, select the Directory option button and use the Browse button to select an appropriate place to save the JAR file on the local file system.

                      NOTE: During the deployment process that is described in the following section, you will need to remember the location where this JAR file is saved.

                      Click Finish.

                      This will create a JAR file in the selected directory (e.g., /home/joe/myPlugins/plugins/org.eclipse.kura.example.hello_osgi_1.0.0.jar).

                      "},{"location":"java-application-development/hello-world-application/#create-a-deployment-package","title":"Create a Deployment Package","text":"

                      Rather than creating a stand-alone plug-in, you can also create a Deployment Package that contains multiple bundles, configuration elements, etc. that can be deployed into an OSGi framework. In this example, you will simply create a Deployment Package containing the \u201chello_osgi\u201d bundle. This step requires mToolkit to be installed. (See Kura Setup for instructions on setting up the Eclipse development environment.)

                      Right-click the project and select New | Folder. Select the org.eclipse.kura.example.hello_osgi project and enter a folder named \u201cresources\u201d.

                      Then repeat this step to create a folder named \u201cdp\u201d under the resources folder. The resources/dp folder will be used to store the Deployment Package.

                      Select File | New | Other. Select OSGi | Deployment Package Definition and click Next.

                      Ensure that the Target folder field of the New dpp file window is set to the /[project_name]/resources/dp folder. In the File name field, enter the name for the new Deployment Package file to create, such as \u201chello_osgi\u201d. A version number can also be entered in the Version field. Then click Finish.

                      Under the resources/dp folder in your project, verify that the [filename].dpp file was created. This is a Deployment Package Project that provides information needed to create the Deployment Package, such as its output directory, ant build file, etc.

                      Select the Bundles tab and then click New. In the Bundle Path column, select the browse icon. Browse to the bundle\u2019s JAR file created earlier. Select the file and click Open. Doing so should populate the remaining columns as needed.

                      Save changes to the deployment package file.

                      In the resources/dp folder, right-click the .dpp file. Select Quick Build. A new [filename].dp file will be created in the same directory. This is the final Deployment Package that can be installed on a remote target system.

                      In conclusion, you were able to create a new bundle from scratch, write the Java code and Activator, modify the Manifest, and build the plug-in and/or Deployment Package that can be used in a Kura environment.

                      The next steps will be to test your code in an Emulation mode and/or to deploy your code to a target system running Kura. See Testing and Deploying Bundles to continue with those steps.

                      "},{"location":"java-application-development/how-to-manage-network-settings/","title":"How to manage Network Settings","text":"

                      This section provides an example of how to create a Kura bundle that can be used to configure the network interfaces of your device. In this example, you will learn how to perform the following functions:

                      • Create a plugin that configures the network interfaces

                      • Connect to a wireless access point

                      • Create a wireless access point

                      As written, the example code configures the device with a static Wi-Fi configuration. Typically, the device settings would be defined through the Kura Gateway Administration Console instead of through Java code.

                      A more practical application of this example is for IP network interfaces that need to be dynamically modified based on some external trigger or condition, such as geo-fencing. The Kura framework allows the device to be programmatically changed via its APIs based on application-specific logic.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#prerequisites","title":"Prerequisites","text":"

                      Setting up the Eclipse Kura Development Environment

                      "},{"location":"java-application-development/how-to-manage-network-settings/#network-configuration-with-kura","title":"Network Configuration with Kura","text":""},{"location":"java-application-development/how-to-manage-network-settings/#hardware-setup","title":"Hardware Setup","text":"

                      This example requires an embedded device running Kura with at least one Ethernet port and Wi-Fi support.

                      Additionally, the Connect to an Access Point section requires a wireless access point and the following information about the access point:

                      • SSID (Network Name)

                      • Security Type (WEP, WPA, or WPA2), if any

                      • Password/Passphrase, if any

                      Lastly, the Create an Access Point section requires:

                      • A wireless device, such as a laptop, to test the access point.

                      • Optionally, you may connect the Kura device\u2019s Ethernet port to another network and use Kura as a gateway to that network.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#determine-your-network-interfaces","title":"Determine Your Network Interfaces","text":"

                      In order to determine your network interfaces, run one of the following commands at a terminal on the embedded gateway:

                      ifconfig -a or ip link show

                      Typical network interfaces will appear as follows:

                      • lo - loopback interface

                      • eth0 - first Ethernet network interface

                      • wlan0 - first wireless network interface

                      • ppp0 - first point-to-point protocol network interface, which could be a dial-up modem, PPTP VPN connection, cellular modem, etc.

                      Make note of your wireless interface. For this tutorial, we will assume the wireless interface name is \u2018wlan0\u2019.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#kura-networking-api","title":"Kura Networking API","text":"

                      The networking API consists of two basic services: org.eclipse.kura.net.NetworkService and org.eclipse.kura.net.NetworkAdminService.

                      The NetworkService is used to get the current state of the network. For example, the getNetworkInterfaces() method will return a List of NetInterface objects (such as EthernetInterface or WifiInterface) for each interface. This provides a detailed representation of the current state of that interface, such as its type, whether it is currently up, and its current address, which is returned by the getNetInterfaceAddresses() method as a List of NetInterfaceAddress objects. The NetworkService can also be used to get a list of all the network interface names available on the system, or a list of all the Wi-Fi access points that are currently detected by the system.

                      The NetworkAdminService is used to get and set the configuration for each interface. Similar to the NetworkService, it has a getNetworkInterfaceConfigs() that returns a List of NetInterfaceConfig objects (such as EthernetInterfaceConfig and WifiInterfaceConfig) for each interface. These have the same methods as a NetInterface object but represent the current configuration for that interface. For a NetInterfaceConfig object, the getNetInterfaceAddress() method will return a List of NetInterfaceAddressConfig objects. These NetInterfaceAddressConfig instances, in turn, contain a List of NetConfig objects that define the configuration for that interface.

                      There are many types of NetConfig objects, including:

                      • NetConfigIP4 - contains the IPv4 address configuration

                      • WifiConfig - contains the Wi-Fi configuration. Note that a WifiInterfaceAddressConfig may contain multiple WifiConfigs, since a configuration might exist for one or more Wi-Fi modes. The currently active WifiConfig is the one with a WifiMode that matches the WifiInterfaceAddressConfig WifiMode.

                      • DhcpServerConfigIP4 - contains the IPv4-based DHCP server configuration

                      • DnsServerConfigIP4 - contains the IPv4-based DNS server configuration

                      • FirewallNatConfig - contains the firewall NAT configuration

                      These NetConfigs can also be used to configure an interface by providing them as a list to the updateEthernetInterfaceConfig(), updateWifiInterfaceConfig(), or updateModemInterfaceConfig() methods in the NetworkAdminService.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#connect-to-an-access-point","title":"Connect to an Access Point","text":"

                      In this section, you will develop a Kura network configuration bundle that sets up the Wi-Fi interface as a client to a wireless access point.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#implement-the-bundle","title":"Implement the Bundle","text":"

                      To implement the network configuration bundle, perform the following steps:

                      Note

                      For more detailed information about bundle development (i.e., the plug-in project, classes, and MANIFEST file configuration), please refer to the Hello World Application

                      • Create a Plug-in Project named org.eclipse.kura.example.network; set the an OSGi framework option to standard; uncheck the Generate an activator option; and set the Execution Environment variable to match the JVM on your target device.

                      • Include the following bundles in the MANIFEST.MF:

                      • org.eclipse.kura
                      • org.eclipse.kura.net
                      • org.eclipse.kura.net.dhcp
                      • org.eclipse.kura.net.firewall
                      • org.eclipse.kura.net.wifi
                      • org.osgi.service.component
                      • org.slf4j

                      • Create a class named NetworkConfigExample in the org.eclipse.kura.example.network project.

                      • Create an OSGI-INF folder in the org.eclipse.kura.example.network project. Add a Component Class with the parent folder org.eclipse.kura.example.network/OSGI-INF, Component Name org.eclipse.kura.example.network, and Class org.eclipse.kura.example.network.NetworkConfigExample.

                      • Select the Services tab in the component.xml file. Under Referenced Services, add org.eclipse.kura.net.NetworkAdminService. Edit the properties of this service, and configure the Bind property to setNetworkAdminService and Unbind to unsetNetworkAdminService as shown in the following screen capture. These settings are required because of the dependency on NetworkAdminService.

                      The following source code will also need to be implemented:

                      • META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and its dependencies.

                      • OSGI-INF/component.xml - declarative services definition describing what services are exposed by and consumed by this bundle.

                      • org.eclipse.kura.example.network.NetworkConfigExample.java - main implementation class.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#meta-infmanifestmf-file","title":"META-INF/MANIFEST.MF File","text":"

                      The META-INF/MANIFEST.MF file should look as follows when complete:

                      Warning

                      Whitespace is significant in this file; make sure yours matches this file exactly.

                      Manifest-Version: 1.0\nBundle-ManifestVersion: 2\nBundle-Name: Network\nBundle-SymbolicName: org.eclipse.kura.example.network\nBundle-Version: 1.0.0.qualifier\nBundle-Vendor: ECLIPSE\nBundle-RequiredExecutionEnvironment: JavaSE-1.7\nImport-Package: org.eclipse.kura,\n org.eclipse.kura.net,\n org.eclipse.kura.net.dhcp,\n org.eclipse.kura.net.firewall,\n org.eclipse.kura.net.wifi,\n org.osgi.service.component;version=\"1.2.0\",\n org.slf4j;version=\"1.6.4\"\nService-Component: OSGI-INF/component.xml\n
                      "},{"location":"java-application-development/how-to-manage-network-settings/#osgi-infcomponentxml-file","title":"OSGI-INF/component.xml File","text":"
                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n    name=\"org.eclipse.kura.example.network.NetworkConfigExample\"\n    activate=\"activate\"\n    deactivate=\"deactivate\"\n    enabled=\"true\"\n    immediate=\"true\"\n    configuration-policy=\"require\"\n    modified=\"updated\">\n   <implementation class=\"org.eclipse.kura.example.network.NetworkConfigExample\"/>\n   <service>\n        <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n   </service>\n   <reference name=\"NetworkAdminService\"\n        interface=\"org.eclipse.kura.net.NetworkAdminService\"\n        policy=\"static\"\n        cardinality=\"1..1\"\n        bind=\"setNetworkAdminService\"\n        unbind=\"unsetNetworkAdminService\"/>\n   <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.network.NetworkConfigExample\"/>\n</scr:component>\n
                      "},{"location":"java-application-development/how-to-manage-network-settings/#orgeclipsekuraexamplenetworknetworkconfigexamplejava","title":"org.eclipse.kura.example.network.NetworkConfigExample.java","text":"
                      package org.eclipse.kura.example.network;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.osgi.service.component.ComponentContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.eclipse.kura.KuraException;\nimport org.eclipse.kura.net.IP4Address;\nimport org.eclipse.kura.net.IPAddress;\nimport org.eclipse.kura.net.NetConfig;\nimport org.eclipse.kura.net.NetConfigIP4;\nimport org.eclipse.kura.net.NetInterfaceStatus;\nimport org.eclipse.kura.net.NetworkAdminService;\nimport org.eclipse.kura.net.dhcp.DhcpServerConfigIP4;\nimport org.eclipse.kura.net.firewall.FirewallNatConfig;\nimport org.eclipse.kura.net.wifi.WifiCiphers;\nimport org.eclipse.kura.net.wifi.WifiConfig;\nimport org.eclipse.kura.net.wifi.WifiMode;\nimport org.eclipse.kura.net.wifi.WifiRadioMode;\nimport org.eclipse.kura.net.wifi.WifiSecurity;\n\npublic class NetworkConfigExample {\n\n  private static final Logger s_logger = LoggerFactory.getLogger(NetworkConfigExample.class);\n\n  private NetworkAdminService m_netAdminService;\n\n  // ----------------------------------------------------------------\n  //\n  //   Dependencies\n  //\n  // ----------------------------------------------------------------\n\n  public void setNetworkAdminService(NetworkAdminService netAdminService) {\n    this.m_netAdminService = netAdminService;\n  }\n\n  public void unsetNetworkAdminService(NetworkAdminService netAdminService) {\n    this.m_netAdminService = null;\n  }\n\n\n  // ----------------------------------------------------------------\n  //\n  //   Activation APIs\n  //\n  // ----------------------------------------------------------------\n\n  protected void activate(ComponentContext componentContext) {\n    s_logger.info(\"Activating NetworkConfigExample...\");\n\n    connectToWirelessAccessPoint();\n//      createWirelessAccessPoint();\n\n    s_logger.info(\"Activating NetworkConfigExample... Done.\");\n  }\n\n  protected void deactivate(ComponentContext componentContext) {\n    s_logger.info(\"Deactivating NetworkConfigExample...\");\n\n    s_logger.info(\"Deactivating NetworkConfigExample... Done.\");\n  }\n\n\n  // ----------------------------------------------------------------\n  //\n  //   Private Methods\n  //\n  // ----------------------------------------------------------------\n\n  /**\n   * Connect to a wireless access point using the hard-coded parameters below\n   */\n  private void connectToWirelessAccessPoint() {\n    String interfaceName = \"wlan0\";\n\n    // Create a NetConfigIP4 - configure as a WAN (gateway) interface, and a DHCP client\n    NetInterfaceStatus netInterfaceStatus = NetInterfaceStatus.netIPv4StatusEnabledWAN;\n    boolean dhcpClient = true;\n    boolean autoConnect = true;\n    NetConfigIP4 netConfigIP4 = new NetConfigIP4(netInterfaceStatus, autoConnect, dhcpClient);\n\n    // Create a WifiConfig managed mode client\n    String driver = \"nl80211\";\n    String ssid = \"access_point_ssid\";\n    String password = \"password\";\n    WifiSecurity security = WifiSecurity.SECURITY_WPA2;\n    WifiCiphers ciphers = WifiCiphers.CCMP_TKIP;\n    int[] channels = {1,2,3,4,5,6,7,8,9,10,11};\n\n    WifiConfig wifiConfig = new WifiConfig();\n    wifiConfig.setMode(WifiMode.INFRA);\n    wifiConfig.setDriver(driver);\n    wifiConfig.setSSID(ssid);\n    wifiConfig.setPasskey(password);\n    wifiConfig.setSecurity(security);\n    wifiConfig.setChannels(channels);\n    wifiConfig.setGroupCiphers(ciphers);\n    wifiConfig.setPairwiseCiphers(ciphers);\n\n    // Create a NetConfig List\n    List<NetConfig> netConfigs = new ArrayList<NetConfig>();\n    netConfigs.add(netConfigIP4);\n    netConfigs.add(wifiConfig);\n\n    // Configure the interface\n    try{\n      s_logger.info(\"Reconfiguring \" + interfaceName + \" to connect to \" + ssid);\n      m_netAdminService.disableInterface(interfaceName);\n      m_netAdminService.updateWifiInterfaceConfig(interfaceName, autoConnect, null, netConfigs);\n\n      s_logger.info(\"Enable \" + interfaceName);\n      m_netAdminService.enableInterface(interfaceName, dhcpClient);\n    } catch(KuraException e) {\n      s_logger.error(\"Error connecting to wireless access point\", e);\n    }\n  }\n\n  /**\n   * Create a wireless access point\n   */\n  private void createWirelessAccessPoint() {\n    try{\n      String interfaceName = \"wlan0\";\n\n      // Create a NetConfigIP4 - configure as a LAN interface with a manual IP address\n      NetInterfaceStatus netInterfaceStatus = NetInterfaceStatus.netIPv4StatusEnabledLAN;\n      boolean dhcpClient = false;\n      boolean autoConnect = true;\n      IP4Address ipAddress = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.1\");\n      IP4Address subnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n\n      NetConfigIP4 netConfigIP4 = new NetConfigIP4(netInterfaceStatus, autoConnect);\n      netConfigIP4.setAddress(ipAddress);\n      netConfigIP4.setSubnetMask(subnetMask);\n\n      // Create a WifiConfig access point\n      String driver = \"nl80211\";\n      String ssid = \"NetworkConfigExample\";\n      String password = \"password\";\n      WifiSecurity security = WifiSecurity.SECURITY_WPA2;\n      WifiCiphers ciphers = WifiCiphers.CCMP_TKIP;\n      int[] channels = {1};\n      WifiRadioMode radioMode = WifiRadioMode.RADIO_MODE_80211g;\n      String hwMode = \"g\";\n\n      WifiConfig wifiConfig = new WifiConfig();\n      wifiConfig.setMode(WifiMode.MASTER);\n      wifiConfig.setDriver(driver);\n      wifiConfig.setSSID(ssid);\n      wifiConfig.setPasskey(password);\n      wifiConfig.setSecurity(security);\n      wifiConfig.setChannels(channels);\n      wifiConfig.setGroupCiphers(ciphers);\n      wifiConfig.setPairwiseCiphers(ciphers);\n      wifiConfig.setRadioMode(radioMode);\n      wifiConfig.setHardwareMode(hwMode);\n\n\n      // Create a DhcpServerConfig to enable DHCP server functionality\n      int defaultLeaseTime = 7200;\n      int maximumLeaseTime = 7200;\n      IP4Address routerAddress = ipAddress;\n      IP4Address rangeStart = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.100\");\n      IP4Address rangeEnd = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.200\");\n      IP4Address dhcpSubnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n      IP4Address subnet = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.0\");\n      short prefix = 24;\n      boolean passDns = true;\n\n      List<IP4Address> dnsServers = new ArrayList<IP4Address>();\n      dnsServers.add(ipAddress);                // Use our IP as the DNS server\n\n      DhcpServerConfigIP4 dhcpServerConfigIP4 = new DhcpServerConfigIP4(\n          interfaceName, true, subnet, routerAddress, dhcpSubnetMask, defaultLeaseTime,\n          maximumLeaseTime, prefix, rangeStart, rangeEnd, passDns, dnsServers);\n\n      // Create a FirewallNatConfig to enable NAT (network address translation)\n      // note that the destination interface is determined dynamically\n      FirewallNatConfig natConfig = new FirewallNatConfig(interfaceName, \"tbd\", true);\n\n\n      // Create a NetConfig List\n      List<NetConfig> netConfigs = new ArrayList<NetConfig>();\n      netConfigs.add(netConfigIP4);\n      netConfigs.add(wifiConfig);\n      netConfigs.add(dhcpServerConfigIP4);\n      netConfigs.add(natConfig);\n\n      // Configure the interface\n      s_logger.info(\"Reconfiguring \" + interfaceName + \" as an access point with SSID: \" + ssid);\n      m_netAdminService.disableInterface(interfaceName);\n      m_netAdminService.updateWifiInterfaceConfig(interfaceName, autoConnect, null, netConfigs);\n\n      s_logger.info(\"Enable \" + interfaceName);\n      m_netAdminService.enableInterface(interfaceName, dhcpClient);\n    } catch(Exception e) {\n      s_logger.error(\"Error configuring as an access point\", e);\n    }\n  }\n}\n

                      Modify the parameters in the connectToWirelessAccessPoint() method with the specific values for the access point you want to connect to, including the variables for SSID, password, and security settings:

                      • String ssid = \"access_point_ssid\";

                      • String password = \"password\";

                      • WifiSecurity security = WifiSecurity.SECURITY_WPA2;

                      At this point, the bundle implementation is complete. Make sure to save all files before proceeding.

                      Export the OSGi bundle as a stand-alone plug-in, following the instructions in Hello World Using the Kura Logger.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#deploy-the-bundle","title":"Deploy the Bundle","text":"

                      In order to proceed, you need to know the IP address of your embedded gateway that is running Kura. Follow the mToolkit instructions for installing a single bundle to the remote target device located here. Once the bundle has finished deploying, it will set the device\u2019s network configuration and attempt to connect to a Wi-Fi access point using the configured parameters in the connectToWirelessAccessPoint() method.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#test-the-connection-to-the-access-point","title":"Test the Connection to the Access Point","text":"

                      To verify that the interface (wlan0) has acquired an IP address, run the ifconfig command at a terminal on the embedded gateway.

                      To show the current connection status to the access point, run the following commands:

                      wpa_cli -i wlan0 status\niw dev wlan0 link\niw dev wlan0 station dump\n
                      "},{"location":"java-application-development/how-to-manage-network-settings/#create-an-access-point","title":"Create an Access Point","text":"

                      This example code can be modified slightly to make the gateway function as an access point instead of connecting to an access point.

                      To do this, modify the activate() method in the NetworkConfigExample.java file to comment out connectToWirelessAccessPoint() and uncomment createWirelessAccessPoint().

                      protected void activate(ComponentContext componentContext) {\n  s_logger.info(\"Activating NetworkConfigExample...\");\n\n//  connectToWirelessAccessPoint();\n  createWirelessAccessPoint();\n\n  s_logger.info(\"Activating NetworkConfigExample... Done.\");\n}\n

                      Modify the access point configuration variables under createWirelessAccessPoint() for your needs, if necessary, such as the variables:

                      IP4Address ipAddress = (IP4Address) IPAddress.parseHostAddress(\"172.16.10.1\");\nIP4Address subnetMask = (IP4Address) IPAddress.parseHostAddress(\"255.255.255.0\");\n\n// Create a WifiConfig access point\nString driver = \"nl80211\";\nString ssid = \"NetworkConfigExample\";\nString password = \"password\";\n

                      Export the bundle again as a stand-alone OSGi plug-in and redeploy it to the target device. It should now reconfigure itself to create an access point with an active DHCP server, DNS proxy forwarding, and NAT enabled.

                      "},{"location":"java-application-development/how-to-manage-network-settings/#test-the-access-point","title":"Test the Access Point","text":"

                      To verify that the interface (wlan0) has a fixed IP address, run the ifconfig command at a terminal on the embedded gateway.

                      To view information on the Wi-Fi access point, including interface name, wireless channel, and MAC address, enter:

                      iw dev wlan0 info

                      To monitor connect and disconnect events from the access point, enter:

                      iw event \u2013f

                      Use another wireless client, such as a laptop, to verify that you can connect to the access point, that it receives an IP address, and that it can ping the network. When the client connects to the access point, the console should show a new station connection event.

                      To view station statistic information, including signal strength and bitrate, enter:

                      iw dev wlan0 station dump

                      Optionally, if the gateway has another interface configured for WAN with connection to the Internet, then the wireless client should be able to reach the Internet using this access point as its gateway. The setup for the other interface (not covered in this example) would need to be configured in the device using the Kura Gateway Administration Console.

                      "},{"location":"java-application-development/how-to-serial-ports/","title":"How to Use Serial Ports","text":""},{"location":"java-application-development/how-to-serial-ports/#overview","title":"Overview","text":"

                      This section provides an example of how to create a Kura bundle that will communicate with a serial device. In this example, you will communicate with a simple terminal emulator to demonstrate both transmitting and receiving data. You will learn how to perform the following functions:

                      • Create a plugin that communicates to serial devices

                      • Export the bundle

                      • Install the bundle on the remote device

                      • Test the communication with minicom where, minicom is acting as an attached serial device such as an NFC reader, GPS device, or some other ASCII-based communication device

                      "},{"location":"java-application-development/how-to-serial-ports/#prerequisites","title":"Prerequisites","text":"
                      • Setting up Kura Development Environment

                      • Hello World Using the Kura Logger

                      • Hardware

                        • Use an embedded device running Kura with two available serial ports. (If the device does not have a serial port, USB to serial adapters can be used.)

                        • Ensure minicom is installed on the embedded device.

                      "},{"location":"java-application-development/how-to-serial-ports/#serial-communication-with-kura","title":"Serial Communication with Kura","text":"

                      This section of the tutorial covers setting up the hardware, determining serial port device nodes, implementing the basic serial communication bundle, deploying the bundle, and validating its functionality. After completing this section, you should be able to communicate with any ASCII-based serial device attached to a Kura-enabled embedded gateway. In this example, we are using ASCII for clarity, but these same techniques can be used to communicate with serial devices that communicate using binary protocols.

                      "},{"location":"java-application-development/how-to-serial-ports/#hardware-setup","title":"Hardware Setup","text":"

                      Your setup requirements will depend on your hardware platform. At a minimum, you will need two serial ports with a null modem serial, crossover cable connecting them.

                      • If your platform has integrated serial ports, you only need to connect them using a null modem serial cable.

                      • If you do not have integrated serial ports on your platform, you will need to purchase USB-to-Serial adapters. It is recommended to use a USB-to-Serial adapter with either the PL2303 or FTDI chipset, but others may work depending on your hardware platform and underlying Linux support. Once you have attached these adapters to your device, you can attach the null modem serial cable between the two ports.

                      "},{"location":"java-application-development/how-to-serial-ports/#determine-serial-device-nodes","title":"Determine Serial Device Nodes","text":"

                      This step is hardware specific. If your hardware device has integrated serial ports, contact your hardware device manufacturer or review the documentation to find out how the ports are named in the operating system. The device identifiers should be similar to the following:

                      /dev/ttyS*xx*\n/dev/ttyUSB*xx*\n/dev/ttyACM*xx*\n

                      If you are using USB-to-Serial adapters, Linux usually allocates the associated device nodes dynamically at the time of insertion. In order to determine what they are, run the following command at a terminal on the embedded gateway:

                      tail -f /var/log/syslog\n

                      Warning

                      Depending on your specific Linux implementation, other possible log files may be: /var/log/kern.log, /var/log/kernel, or /var/log/dmesg.

                      With the above command running, insert your USB-to-Serial adapter. You should see output similar to the following:

                      root@localhost:/root> tail -f /var/log/syslog\nAug 15 18:43:47 localhost kernel: usb 3-2: new full speed USB device using uhci_hcd and address 3\nAug 15 18:43:47 localhost kernel: pl2303 3-2:1.0: pl2303 converter detected\nAug 15 18:43:47 localhost kernel: usb 3-2: pl2303 converter now attached to ttyUSB10\n

                      In this example, our device is a PL2303-compatible device and is allocated a device node of \u201c/dev/ttyUSB10\u201d. While your results may differ, the key is to identify the \u201ctty\u201d device that was allocated. For the rest of this tutorial, this device will be referred to as [device_node_1], which in this example is /dev/ttyUSB10. During development, it is also important to keep in mind that these values are dynamic; therefore, from one boot to the next and one insertion to the next, these values may change. To stop \u2018tail\u2019 from running in your console, escape with \u2018 c\u2019.

                      If you are using two USB-to-Serial adapters, repeat the above procedure for the second serial port. The resulting device node will be referred to as [device_node_2].

                      "},{"location":"java-application-development/how-to-serial-ports/#implement-the-bundle","title":"Implement the Bundle","text":"

                      Now that you have two serial ports connected to each other, you are ready to implement the code. You will use the same general method that is described in section Hello World Application with the following exceptions:

                      1. process to export the OSGi bundle will have an additional step,
                      2. the actual code in this example will have the following differences:
                        • The new Plug-in Project is named \u201corg.eclipse.kura.example.serial\u201d
                        • A class named \u201cSerialExample\u201d is created in the org.eclipse.kura.example.serial project
                        • The following bundles are included in the Automated Management of Dependencies section in the MANIFEST.MF:
                          • javax.comm
                          • javax.microedition.io
                          • org.eclipse.kura.cloud
                          • org.eclipse.kura.comm
                          • org.eclipse.kura.configuration
                          • org.osgi.service.component
                          • org.osgi.service.io
                          • org.slf4j

                      The following files need to be implemented:

                      • META-INF/MANIFEST.MF \u2013 OSGI manifest that describes the bundle and its dependencies

                      • OSGI-INF/component.xml \u2013 declarative services definition that describe what services are exposed and consumed by this bundle

                      • OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml \u2013 configuration description of the bundle and its parameters, types, and defaults

                      • org.eclipse.kura.example.serial.SerialExample.java \u2013 main implementation class

                      "},{"location":"java-application-development/how-to-serial-ports/#meta-infmanifestmf-file","title":"META-INF/MANIFEST.MF File","text":"

                      The META-INF/MANIFEST.MF file should appear as shown below when complete:

                      NOTE: Whitespace is significant in this file. Make sure yours matches this file exactly with the exception that RequiredExecutionEnvironment may be JavaSE-1.6 or JavaSE-1.7, depending on the Java installation of your device.

                      Manifest-Version: 1.0\nBundle-ManifestVersion: 2\nBundle-Name: Serial\nBundle-SymbolicName: org.eclipse.kura.example.serial\nBundle-Version: 1.0.0.qualifier\nBundle-RequiredExecutionEnvironment: JavaSE-1.7\nService-Component: OSGI-INF/component.xml\nBundle-ActivationPolicy: lazy\nImport-Package: javax.comm;version=\"1.2.0\",\n  javax.microedition.io;resolution:=optional,\n  org.eclipse.kura.cloud;version=\"0.2.0\",\n  org.eclipse.kura.comm;version=\"0.2.0\",\n  org.eclipse.kura.configuration;version=\"0.2.0\",\n  org.osgi.service.component;version=\"1.2.0\",\n  org.osgi.service.io;version=\"1.0.0\",\n  org.slf4j;version=\"1.6.4\"\nBundle-ClassPath: .\n

                      In addition, the build.properties file should have org.eclipse.equinox.io listed as an additional bundle similar to below:

                      additional.bundles = org.eclipse.equinox.io\n
                      "},{"location":"java-application-development/how-to-serial-ports/#osgi-infcomponentxml-file","title":"OSGI-INF/component.xml File","text":"

                      Warning

                      Starting from Kura 3.0, the configuration service will only track \"relevant services\" that, in their component description files, will provide the ConfigurableComponent or SelfConfigurableComponent interface. The old behavior can be restored by setting the \"org.eclipse.kura.core.configuration.legacyServiceTracking\" property to true.

                      If Kura 2.1.0 or older versions are used or the org.eclipse.kura.core.configuration.legacyServiceTracking system property is set to true, the OSGI-INF/component.xml should appear as shown below when complete:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n  name=\"org.eclipse.kura.example.serial.SerialExample\" activate=\"activate\"\n  deactivate=\"deactivate\" modified=\"updated\" enabled=\"true\" immediate=\"true\"\n  configuration-policy=\"require\">\n\n  <implementation class=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.serial.SerialExample\"/>\n\n  <service>\n    <provide interface=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  </service>\n  <reference bind=\"setConnectionFactory\" cardinality=\"1..1\"\n    interface=\"org.osgi.service.io.ConnectionFactory\" name=\"ConnectionFactory\"\n    policy=\"static\" unbind=\"unsetConnectionFactory\" />\n</scr:component>\n

                      If Kura 3.0 or newer versions are used and the org.eclipse.kura.core.configuration.legacyServiceTracking system property is set to false or not set, the OSGI-INF/component.xml should appear as shown below when complete:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scr:component xmlns:scr=\"http://www.osgi.org/xmlns/scr/v1.1.0\"\n  name=\"org.eclipse.kura.example.serial.SerialExample\" activate=\"activate\"\n  deactivate=\"deactivate\" modified=\"updated\" enabled=\"true\" immediate=\"true\"\n  configuration-policy=\"require\">\n\n  <implementation class=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  <property name=\"service.pid\" type=\"String\" value=\"org.eclipse.kura.example.serial.SerialExample\"/>\n\n  <service>\n    <provide interface=\"org.eclipse.kura.configuration.ConfigurableComponent\"/>\n  </service>\n  <reference bind=\"setConnectionFactory\" cardinality=\"1..1\"\n    interface=\"org.osgi.service.io.ConnectionFactory\" name=\"ConnectionFactory\"\n    policy=\"static\" unbind=\"unsetConnectionFactory\" />\n</scr:component>\n
                      "},{"location":"java-application-development/how-to-serial-ports/#osgi-infmetatypeorgeclipsekuraexampleserialserialexamplexml-file","title":"OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml File","text":"

                      The OSGI-INF/metatype/org.eclipse.kura.example.serial.SerialExample.xml file should appear as shown below when complete:

                      <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<MetaData xmlns=\"http://www.osgi.org/xmlns/metatype/v1.2.0\" localization=\"en_us\">\n  <OCD id=\"org.eclipse.kura.example.serial.SerialExample\"\n    name=\"SerialExample\"\n    description=\"Example of a Configuring KURA Application echoing data read from the serial port.\">\n\n    <Icon resource=\"http://sphotos-a.xx.fbcdn.net/hphotos-ash4/p480x480/408247_10151040905591065_1989684710_n.jpg\" size=\"32\"/>\n\n    <AD id=\"serial.device\"\n        name=\"serial.device\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"false\"\n        description=\"Name of the serial device (e.g. /dev/ttyS0, /dev/ttyACM0, /dev/ttyUSB0).\"/>\n\n    <AD id=\"serial.baudrate\"\n        name=\"serial.baudrate\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"9600\"\n        description=\"Baudrate.\">\n        <Option label=\"9600\" value=\"9600\"/>\n        <Option label=\"19200\" value=\"19200\"/>\n        <Option label=\"38400\" value=\"38400\"/>\n        <Option label=\"57600\" value=\"57600\"/>\n        <Option label=\"115200\" value=\"115200\"/>\n    </AD>\n\n    <AD id=\"serial.data-bits\"\n        name=\"serial.data-bits\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"8\"\n        description=\"Data bits.\">\n        <Option label=\"7\" value=\"7\"/>\n        <Option label=\"8\" value=\"8\"/>\n    </AD>\n\n    <AD id=\"serial.parity\"\n        name=\"serial.parity\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"none\"\n        description=\"Parity.\">\n        <Option label=\"none\" value=\"none\"/>\n        <Option label=\"even\" value=\"even\"/>\n        <Option label=\"odd\" value=\"odd\"/>\n    </AD>\n\n    <AD id=\"serial.stop-bits\"\n        name=\"serial.stop-bits\"\n        type=\"String\"\n        cardinality=\"0\"\n        required=\"true\"\n        default=\"1\"\n        description=\"Stop bits.\">\n        <Option label=\"1\" value=\"1\"/>\n        <Option label=\"2\" value=\"2\"/>\n    </AD>\n\n  </OCD>\n  <Designate pid=\"org.eclipse.kura.example.serial.SerialExample\">\n    <Object ocdref=\"org.eclipse.kura.example.serial.SerialExample\"/>\n  </Designate>\n</MetaData>\n
                      "},{"location":"java-application-development/how-to-serial-ports/#orgeclipsekuraexampleserialserialexamplejava-file","title":"org.eclipse.kura.example.serial.SerialExample.java File","text":"

                      The org.eclipse.kura.example.serial.SerialExample.java file should appear as shown below when complete:

                      package org.eclipse.kura.example.serial;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport org.osgi.service.component.ComponentContext;\nimport org.osgi.service.io.ConnectionFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class SerialExample implements ConfigurableComponent {\n\n  private static final Logger s_logger = LoggerFactory.getLogger(SerialExample.class);\n\n  private static final String SERIAL_DEVICE_PROP_NAME= \"serial.device\";\n  private static final String SERIAL_BAUDRATE_PROP_NAME= \"serial.baudrate\";\n  private static final String SERIAL_DATA_BITS_PROP_NAME= \"serial.data-bits\";\n  private static final String SERIAL_PARITY_PROP_NAME= \"serial.parity\";\n  private static final String SERIAL_STOP_BITS_PROP_NAME= \"serial.stop-bits\";\n\n  private ConnectionFactory m_connectionFactory;\n  private CommConnection m_commConnection;\n  private InputStream m_commIs;\n  private OutputStream m_commOs;\n  private ScheduledThreadPoolExecutor m_worker;\n  private Future<?> m_handle;\n  private Map<String, Object> m_properties;\n\n  // ----------------------------------------------------------------\n  //\n  // Dependencies\n  //\n  // ----------------------------------------------------------------\n  public void setConnectionFactory(ConnectionFactory connectionFactory) {\n    this.m_connectionFactory = connectionFactory;\n  }\n\n  public void unsetConnectionFactory(ConnectionFactory connectionFactory) {\n    this.m_connectionFactory = null;\n  }\n\n  // ----------------------------------------------------------------\n  //\n  // Activation APIs\n  //\n  // ----------------------------------------------------------------\n\n  protected void activate(ComponentContext componentContext, Map<String,Object> properties) {\n    s_logger.info(\"Activating SerialExample...\");\n\n    m_worker = new ScheduledThreadPoolExecutor(1);\n    m_properties = new HashMap<String, Object>();\n    doUpdate(properties);\n    s_logger.info(\"Activating SerialExample... Done.\");\n  }\n\n  protected void deactivate(ComponentContext componentContext) {\n    s_logger.info(\"Deactivating SerialExample...\");\n\n    // shutting down the worker and cleaning up the properties\n    m_handle.cancel(true);\n    m_worker.shutdownNow();\n    //close the serial port\n    closePort();\n    s_logger.info(\"Deactivating SerialExample... Done.\");\n  }\n\n  public void updated(Map<String,Object> properties) {\n    s_logger.info(\"Updated SerialExample...\");\n\n    doUpdate(properties);\n    s_logger.info(\"Updated SerialExample... Done.\");\n  }\n\n  // ----------------------------------------------------------------\n  //\n  // Private Methods\n  //\n  // ----------------------------------------------------------------\n\n  /**\n   * Called after a new set of properties has been configured on the service\n   */\n  private void doUpdate(Map<String, Object> properties) {\n    try {\n      for (String s : properties.keySet()) {\n        s_logger.info(\"Update - \"+s+\": \"+properties.get(s));\n      }\n\n      // cancel a current worker handle if one if active\n      if (m_handle != null) {\n        m_handle.cancel(true);\n      }\n\n      //close the serial port so it can be reconfigured\n      closePort();\n\n      //store the properties\n      m_properties.clear();\n      m_properties.putAll(properties);\n\n      //reopen the port with the new configuration\n      openPort();\n\n      //start the worker thread\n      m_handle = m_worker.submit(new Runnable() {\n        @Override\n        public void run() {\n          doSerial();\n        }\n      });\n\n    } catch (Throwable t) {\n        s_logger.error(\"Unexpected Throwable\", t);\n      }\n  }\n\n  private void openPort() {\n    String port = (String) m_properties.get(SERIAL_DEVICE_PROP_NAME);\n\n    if (port == null) {\n      s_logger.info(\"Port name not configured\");\n      return;\n    }\n\n    int baudRate = Integer.valueOf((String) m_properties.get(SERIAL_BAUDRATE_PROP_NAME));\n    int dataBits = Integer.valueOf((String) m_properties.get(SERIAL_DATA_BITS_PROP_NAME));\n    int stopBits = Integer.valueOf((String) m_properties.get(SERIAL_STOP_BITS_PROP_NAME));\n    String sParity = (String) m_properties.get(SERIAL_PARITY_PROP_NAME);\n    int parity = CommURI.PARITY_NONE;\n\n    if (sParity.equals(\"none\")) {\n      parity = CommURI.PARITY_NONE;\n    } else if (sParity.equals(\"odd\")) {\n        parity = CommURI.PARITY_ODD;\n    } else if (sParity.equals(\"even\")) {\n        parity = CommURI.PARITY_EVEN;\n    }\n\n    String uri = new CommURI.Builder(port)\n    .withBaudRate(baudRate)\n    .withDataBits(dataBits)\n    .withStopBits(stopBits)\n    .withParity(parity)\n    .withTimeout(1000)\n    .build().toString();\n\n    try {\n      m_commConnection = (CommConnection) m_connectionFactory.createConnection(uri, 1, false);\n      m_commIs = m_commConnection.openInputStream();\n      m_commOs = m_commConnection.openOutputStream();\n      s_logger.info(port+\" open\");\n    } catch (IOException e) {\n      s_logger.error(\"Failed to open port \" + port, e);\n      cleanupPort();\n    }\n  }\n\n  private void cleanupPort() {\n\n    if (m_commIs != null) {\n      try {\n        s_logger.info(\"Closing port input stream...\");\n        m_commIs.close();\n        s_logger.info(\"Closed port input stream\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port input stream\", e);\n      }\n      m_commIs = null;\n    }\n\n    if (m_commOs != null) {\n      try {\n        s_logger.info(\"Closing port output stream...\");\n        m_commOs.close();\n        s_logger.info(\"Closed port output stream\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port output stream\", e);\n      }\n      m_commOs = null;\n    }\n\n    if (m_commConnection != null) {\n      try {\n        s_logger.info(\"Closing port...\");\n        m_commConnection.close();\n        s_logger.info(\"Closed port\");\n      } catch (IOException e) {\n          s_logger.error(\"Cannot close port\", e);\n      }\n      m_commConnection = null;\n    }\n  }\n\n  private void closePort() {\n    cleanupPort();\n  }\n\n  private void doSerial() {\n    if (m_commIs != null) {\n      try {\n        int c = -1;\n        StringBuilder sb = new StringBuilder();\n        while (m_commIs != null) {\n          if (m_commIs.available() != 0) {\n            c = m_commIs.read();\n          } else {\n            try {\n              Thread.sleep(100);\n              continue;\n            } catch (InterruptedException e) {\n                return;\n            }\n          }\n\n        // on reception of CR, publish the received sentence\n        if (c==13) {\n          s_logger.debug(\"Received serial input, echoing to output: \" + sb.toString());\n          sb.append(\"\\r\\n\");\n          String dataRead = sb.toString();\n          //echo the data to the output stream\n          m_commOs.write(dataRead.getBytes());\n          //reset the buffer\n          sb = new StringBuilder();\n        } else if (c!=10) {\n          sb.append((char) c);\n        }\n      }\n\n      } catch (IOException e) {\n          s_logger.error(\"Cannot read port\", e);\n      } finally {\n          try {\n            m_commIs.close();\n          } catch (IOException e) {\n            s_logger.error(\"Cannot close buffered reader\", e);\n          }\n      }\n    }\n  }\n}\n
                      At this point, the bundle implementation is complete. Make sure to save all files before proceeding.

                      "},{"location":"java-application-development/how-to-serial-ports/#export-the-bundle","title":"Export the Bundle","text":"

                      To build the Serial Example bundle as a stand-alone OSGi plugin, right-click the project and select Export.

                      From the wizard, select Plug-in Development | Deployable plug-ins and fragments and click Next.

                      The Export window appears. Under Available Plug-ins and Fragments, verify that the newly created plug-in is selected.

                      Under Destination, select the Directory option button and use the Browse button to select an appropriate place to save the JAR file on the local file system.

                      Info

                      You will need to know the location where this JAR file is saved for the deployment process.

                      Under Options, select the checkbox Use class files compiled in the workspace in addition to the checkboxes already enabled, and click Finish.

                      Doing so will create a JAR file in the selected directory (e.g., /home/joe/myPlugins/plugins/org.eclipse.kura.example.serial_1.0.0.201410311510.jar).

                      "},{"location":"java-application-development/how-to-serial-ports/#deploy-the-bundle","title":"Deploy the Bundle","text":"

                      In order to proceed, you need to know the IP address of your embedded gateway that is running Kura. Once you have this IP address, follow the mToolkit instructions for installing a single bundle to a remote target device (refer to section 2.03 Testing and Deploying Bundles).

                      Once the installation successfully completes, you should see a message from the /var/log/kura.log file indicating that the bundle was successfully installed and configured. You can also run this example with the emulator in a Linux or OS X environment as shown sample output below. Make sure that your user account has owner permission for the serial device in /dev.

                      "},{"location":"java-application-development/how-to-serial-ports/#validate-the-bundle","title":"Validate the Bundle","text":"

                      Next, you need to test that your bundle does indeed echo characters back by opening minicom and configuring it to use [device_node_2] that was previously determined.

                      Open minicom using the following command at a Linux terminal on the remote gateway device:

                      minicom -s\n

                      This command opens a view similar to the following screen capture:

                      Scroll down to Serial port setup and press . A new dialog window opens as shown below:

                      Use the minicom menu options on the left (i.e., A, B, C, etc.) to change desired fields. Set the fields to the same values as shown in the previous screen capture except the Serial Device should match the [device_node_2] on your target device. Once this is set, press \\<ENTER> to exit from this menu.

                      In the main configuration menu, select Exit (do not select the option Exit from Minicom). At this point, you have successfully started minicom on the second serial port attached to your null modem cable allowing minicom to act as a serial device that can send and receive commands to your Kura bundle. You can verify this operation by typing characters and pressing . The function (specifically a \u2018\\n\u2019 character) signals to the Kura application to echo the buffered characters back to the serial device (minicom in this case).

                      Upon startup, minicom sends an initialization string to the serial device. These characters are sent to the minicom terminal because they were echoed back by Kura listening on the port at the other end of the null modem cable.

                      When you are done, exit minicom by pressing \u2018 a\u2019, then \u2018q\u2019, and finally \u2018\u2019. Doing so brings you back to the Linux command prompt.

                      This tutorial instructed you how to write and deploy a Kura bundle on your target device that listens for serial data (coming from the minicom terminal and being received on [device_node_1]). This tutorial also demonstrated that the application echoes data back to the same serial port that is received in minicom, which acts a serial device that sends and receives data. If supported by the device, Kura may send and receive binary data instead of ASCII.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/","title":"How to Use Beacon APIs","text":""},{"location":"java-application-development/how-to-use-beacon-apis/#overview","title":"Overview","text":"

                      Eclipse Kura implements a set of APIs for managing Bluetooth Low Energy and Beacon devices.

                      The purpose of the BLE Beacon APIs is to simplify the development of applications that interact with Bluetooth LE Beacon devices, offering clear and easy-to-use methods for advertising and scanning. Eclipse Kura offers out-of-the-box the implementation of the Beacon APIs for iBeacon\u2122 and Eddystone\u2122 technologies.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/#how-to-use-kura-ibeacontm-apis","title":"How to use Kura iBeacon\u2122 APIs","text":"

                      This section briefly presents how to use the iBeacon\u2122 implementation of the Kura Beacon APIs, providing several code snippets to explain how to perform common operations on iBeacons. For a complete example on iBeacon advertising and scanning, please refer to the iBeacon\u2122 advertiser and iBeacon\u2122 scanner examples. For more information about iBeacon\u2122 please refer to official page.

                      An application that wants to use the iBeacon\u2122 implementation of Kura Beacon APIs should bind the BluetoothLeService and BluetoothLeIBeaconService OSGI services, as shown in the following Java snippet:

                      public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n\npublic void setBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {\n    this.bluetoothLeIBeaconService = bluetoothLeIBeaconService;\n}\n\npublic void unsetBluetoothLeIBeaconService(BluetoothLeIBeaconService bluetoothLeIBeaconService) {\n    this.bluetoothLeIBeaconService = null;\n}\n

                      and in the component definition:

                      <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n\n<reference bind=\"setBluetoothLeIBeaconService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.ble.ibeacon.BluetoothLeIBeaconService\" \n            name=\"BluetoothLeIBeaconService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeIBeaconService\"/>\n

                      The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeIBeaconScanner and BluetoothLeIBeaconAdvertiser. As explained here, the adapter can be retrieved and powered on as follows:

                      this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

                      where adapterName is the name of the adapter, e.g. hci0.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-ibeacontm-advertiser","title":"Create an iBeacon\u2122 advertiser","text":"

                      In order to properly configure an iBeacon\u2122 advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconAdvertiser instance bound to a specific Bluetooth adapter:

                      try {\n    BluetoothLeBeaconAdvertiser<BluetoothLeIBeacon> advertiser = this.bluetoothLeIBeaconService.newBeaconAdvertiser(this.bluetoothLeAdapter);\n} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {\n    logger.error(\"Beacon Advertiser not available on {}\", this.bluetoothLeAdapter.getInterfaceName(),e);\n}\n

                      Then a BluetoothLeIBeacon object should be created, containing all the information to be broadcasted. In the following snippet, the BluetoothLeIBeacon object is instantiated and added to the advertiser. Then the broadcast time interval is set and the beacon advertising is started.

                      try {\n    BluetoothLeIBeacon iBeacon = new BluetoothLeIBeacon(uuid, major, minor, txPower);\n    advertiser.updateBeaconAdvertisingData(iBeacon);\n    advertiser.updateBeaconAdvertisingInterval(minInterval, maxInterval;\n\n    advertiser.startBeaconAdvertising();\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"IBeacon configuration failed\", e);\n}\n

                      The BluetoothLeIBeacon represents the beacon packet that will be broadcasted by the advertiser and it should be configured will the following parameters:

                      • uuid a unique number that identifies the beacon.
                      • major a number that identifies a subset of beacons within a large group.
                      • minor a number that identifies a specific beacon.
                      • txPower the transmitter power level indicating the signal strength one meter from the device.

                      Warning

                      Only one advertising packet can be broadcasted at a time on a specific Bluetooth adapter.

                      Finally, in the following snippet the advertiser is stopped and removed from the BluetoothLeIBeaconService:

                      try {\n    advertiser.stopBeaconAdvertising();\n    this.bluetoothLeIBeaconService.deleteBeaconAdvertiser(advertiser);\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Stop iBeacon advertising failed\", e);\n}\n
                      "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-ibeacontm-scanner","title":"Create an iBeacon\u2122 scanner","text":"

                      As done for the advertiser, a BluetoothLeIBeaconService is needed to create a new BluetoothLeIBeaconScanner instance bound to a specific Bluetooth adapter:

                      bluetoothLeBeaconScanner<BluetoothLeIBeacon> scanner = this.bluetoothLeIBeaconService.newBeaconScanner(this.bluetoothLeAdapter);\n

                      A BluetoothLeIBeaconScanner needs a listener to collect the iBeacon packets that the Bluetooth adapter detects. In the following snippet, a simple listener that prints the iBeacon packet configuration is added to the scanner object:

                      private class iBeaconListener implements BluetoothLeBeaconListener<BluetoothLeIBeacon> {\n\n    @Override\n    public void onBeaconsReceived(BluetoothLeIBeacon beacon) {\n        logger.info(\"iBeacon received from {}\", beacon.getAddress());\n        logger.info(\"UUID : {}\", beacon.getUuid());\n        logger.info(\"Major : {}\", beacon.getMajor());\n        logger.info(\"Minor : {}\", beacon.getMinor());\n        logger.info(\"TxPower : {}\", beacon.getTxPower());\n        logger.info(\"RSSI : {}\", beacon.getRssi());   \n    }\n\n}\n
                      scanner.addBeaconListener(listener);\n

                      The scanner is started for a specific time interval (in this case 10 seconds):

                      scanner.startBeaconScan(10);\n

                      Finally the scanner should be stopped, if needed, and the resources are released:

                      if (scanner.isScanning()) {\n    scanner.stopBeaconScan();\n}\nscanner.removeBeaconListener(listener);\nthis.bluetoothLeIBeaconService.deleteBeaconScanner(scanner);\n

                      Note

                      The kura.legacy.bluetooth.beacon.scan property in the kura.properties file defines how the scan for beacons is performed. If set to true, the deprecated hcitool command is used. This guarantees that all the advertisement packets are reported. If set to false, the library will communicate to the OS using dbus. In the latter case, the rate of reports is limited and not all the advertisement packets are reported.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/#how-to-use-kura-eddystonetm-apis","title":"How to use Kura Eddystone\u2122 APIs","text":"

                      Eddystone\u2122 is a protocol specification that defines a BLE message format for proximity beacon messages. It describes several different frame types that may be used individually or in combinations to create beacons that can be used for a variety of applications. For more information please see here and here.

                      In this section the Eddystone\u2122 implementation of the Kura Beacon APIs is presented, providing several code snippets to explain how to perform common operations on them. For a complete example on Eddystone\u2122 advertising and scanning, please refer to the Eddystone\u2122 advertiser and Eddystone\u2122 scanner examples.

                      Warning

                      Only Eddystone UID and URL frame types are currently supported.

                      As done with the iBeacon\u2122 implementation, an application has to bind the BluetoothLeService and BluetoothLeEddystoneService OSGI services, as shown in the following Java snippet:

                      public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n\npublic void setBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {\n    this.bluetoothLeEddystoneService = bluetoothLeEddystoneService;\n}\n\npublic void unsetBluetoothLeEddystoneService(BluetoothLeEddystoneService bluetoothLeEddystoneService) {\n    this.bluetoothLeEddystoneService = null;\n}\n

                      and in the component definition:

                      <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n\n<reference bind=\"setBluetoothLeEddystoneService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.ble.eddystone.BluetoothLeEddystoneService\" \n            name=\"BluetoothLeEddystoneService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeEddystoneService\"/>\n

                      The BluetoothLeService is used to get the BluetoothLeAdapter to be used with the BluetoothLeEddystoneScanner and BluetoothLeEddystoneAdvertiser. As explained here, the adapter can be retrieved and powered on as follows:

                      this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

                      where adapterName is the name of the adapter, e.g. hci0.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-eddystonetm-advertiser","title":"Create an Eddystone\u2122 advertiser","text":"

                      In order to properly configure an Eddystone\u2122 advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneAdvertiser instance bound to a specific Bluetooth adapter:

                      try {\n    BluetoothLeBeaconAdvertiser<BluetoothLeEddystone> advertiser = this.advertising = this.bluetoothLeEddystoneService.newBeaconAdvertiser(this.bluetoothLeAdapter);\n} catch (KuraBluetoothBeaconAdvertiserNotAvailable e) {\n    logger.error(\"Beacon Advertiser not available on {}\", this.bluetoothLeAdapter.getInterfaceName(),e);\n}\n

                      The advertiser has to be configured with a BluetoothLeEddystone object that contains all the information to be broadcasted. Currently, UID and URL frame types are supported. A UID frame can be created as follows:

                      BluetoothLeEddystone eddystone = new BluetoothLeEddystone();\neddystone.configureEddystoneUIDFrame(namespace, instance, txPower);\n

                      where namespace and instance are respectively 10-byte and 6-byte long sequences that compose a unique 16-byte Beacon ID. The txPower is the calibrated transmission power at 0 m.

                      A URL frame is created as follows:

                      BluetoothLeEddystone eddystone = new BluetoothLeEddystone();\neddystone.configureEddystoneURLFrame(url, txPower);\n

                      where url is the URL to be broadcasted and the txPower is the calibrated transmission power at 0 m.

                      After the BluetoothLeEddystone creation, the packet is added to the advertiser and the broadcast time interval is set. Then the advertiser is started:

                      try {\n    advertiser.updateBeaconAdvertisingData(eddystone);\n    advertiser.updateBeaconAdvertisingInterval(this.options.getMinInterval(), this.options.getMaxInterval());\n    advertiserstartBeaconAdvertising();\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Eddystone configuration failed\", e);\n}\n

                      Finally, in the following snippet the advertiser is stopped and removed from the BluetoothLeEddystoneService:

                      try {\n    advertiser.stopBeaconAdvertising();\n    this.bluetoothLeEddystoneService.deleteBeaconAdvertiser(advertiser);\n} catch (KuraBluetoothCommandException e) {\n    logger.error(\"Stop Advertiser advertising failed\", e);\n}\n
                      "},{"location":"java-application-development/how-to-use-beacon-apis/#create-an-eddystonetm-scanner","title":"Create an Eddystone\u2122 scanner","text":"

                      As done for the advertiser, a BluetoothLeEddystoneService is needed to create a new BluetoothLeEddystoneScanner instance bound to a specific Bluetooth adapter:

                      bluetoothLeBeaconScanner<BluetoothLeEddystone> scanner = this.bluetoothLeEddystoneService.newBeaconScanner(this.bluetoothLeAdapter);\n

                      A BluetoothLeEddystoneScanner needs a listener to collect the Eddystone packets that the Bluetooth adapter detects. In the following snippet, a simple listener that detects the frame type and prints the packet content is added to the scanner object:

                      private class EddystoneListener implements BluetoothLeBeaconListener<BluetoothLeEddystone> {\n\n    @Override\n    public void onBeaconsReceived(BluetoothLeEddystone beacon) {\n        logger.info(\"Eddystone {} received from {}\", eddystone.getFrameType(), eddystone.getAddress());\n        if (\"UID\".equals(eddystone.getFrameType())) {\n            logger.info(\"Namespace : {}\", bytesArrayToHexString(eddystone.getNamespace()));\n            logger.info(\"Instance : {}\", bytesArrayToHexString(eddystone.getInstance()));\n        } else if (\"URL\".equals(eddystone.getFrameType())) {\n            logger.info(\"URL : {}\", eddystone.getUrlScheme() + eddystone.getUrl());\n        }\n        logger.info(\"TxPower : {}\", eddystone.getTxPower());\n        logger.info(\"RSSI : {}\", eddystone.getRssi());  \n    }\n\n}\n
                      scanner.addBeaconListener(listener);\n

                      The scanner is started for a specific time interval (in this case 10 seconds):

                      scanner.startBeaconScan(10);\n

                      Finally the scanner should be stopped, if needed, and the resources are released:

                      if (scanner.isScanning()) {\n    scanner.stopBeaconScan();\n}\nscanner.removeBeaconListener(listener);\nthis.bluetoothLeEddystoneService.deleteBeaconScanner(scanner);\n

                      Note

                      The kura.legacy.bluetooth.beacon.scan property in the kura.properties file defines how the scan for beacons is performed. If set to true, the deprecated hcitool command is used. This guarantees that all the advertisement packets are reported. If set to false, the library will communicate to the OS using dbus. In the latter case, the rate of reports is limited and not all the advertisement packets are reported.

                      "},{"location":"java-application-development/how-to-use-beacon-apis/#add-new-beacon-apis-implementation","title":"Add new Beacon APIs implementation","text":"

                      Eclipse Kura offers the implementation for iBeacon\u2122 and Eddystone\u2122 protocols, but it is possible to add implementations of different kinds of beacon protocols.

                      The org.eclipse.kura.bluetooth.le.beacon package contains the interfaces used by the beacon implementations:

                      • BluetoothLeBeaconService is the entry point for applications that want to use the Beacon APIs.
                      • BluetoothLeBeaconManager is used by the BluetoothLeBeaconService and provides methods to create and delete Beacon advertisers and scanners.
                      • BluetoothLeBeaconAdvertiser allows configuring advertisement packets and managing advertising.
                      • BluetoothLeBeaconScanner is used to search for specific Beacon packets.
                      • BluetoothLeBeaconEncoder implements methods for encoding a Beacon object to a stream of bytes.
                      • BluetoothLeBeaconDecoder implements methods for decoding a stream of bytes in to a Beacon object.
                      • BluetoothLeBeacon represents a generic Beacon packet.

                      The BluetoothLeBeaconManager, BluetoothLeBeaconScanner and BluetoothLeBeaconAdvertiser interfaces handles generic BluetoothLeBeacon objects and their implementations are provided by the org.eclipse.kura.ble.provider. The others interfaces, instead, are Beacon specific and their implementations depend on the specific protocol that is used. As a consequence, who wants to support a new Beacon protocol, should provide the implementation of the BluetoothLeBeaconService, BluetoothLeBeaconEncoder and BluetoothLeBeaconDecoder interfaces and extend the BluetoothLeBeacon class.

                      As an example, the org.eclipse.kura.ble.ibeacon.provider provides the implementation of the above APIs for the iBeacon\u2122 protocol. In this case, the org.eclipse.kura.ble.ibeacon package contains the following:

                      • BluetoothLeIBeacon implements the BluetoothLeBeacon interface for the iBeacon\u2122 packet.
                      • BluetoothLeIBeaconEncoder is a marker interface that extends BluetoothLeBeaconEncoder.
                      • BluetoothLeIBeaconDecoder is a marker interface that extends BluetoothLeBeaconDecoder.
                      • BluetoothLeIBeaconService is a marker interface that extends BluetoothLeBeaconService and is the entry point for applications that wants to use an iBeacon\u2122.

                      The org.eclipse.kura.internal.ble.ibeacon provides the implementations for the above interfaces:

                      • BluetoothLeIBeaconEncoderImpl implements BluetoothLeIBeaconEncoder offering a method to encode the BluetoothLeIBeacon into a byte stream.
                      • BluetoothLeIBeaconDecoderImpl implements BluetoothLeIBeaconDecoder offering a method to decode a stream of bytes into a BluetoothLeIBeacon object.
                      • BluetoothLeIBeaconServiceImpl is the implementation of BluetoothLeIBeaconService and uses the generic BluetoothLeBeaconManager service to create scanners and advertisers.

                      The following image shows the UML diagram.

                      "},{"location":"java-application-development/how-to-use-bt-le-apis/","title":"How to Use Bluetooth LE APIs","text":""},{"location":"java-application-development/how-to-use-bt-le-apis/#overview","title":"Overview","text":"

                      Eclipse Kura implements a set of APIs for managing Bluetooth Low Energy and Beacon devices.

                      The purpose of the BLE APIs is to simplify the development of applications that interact with Bluetooth LE devices, offering clear and easy-to-use methods, and add new features to correctly manage the connection with remote devices. Moreover, the APIs organize the methods in a logical way to access all levels of a GATT client, from GATT services to GATT characteristics and descriptors, using UUIDs to identify the correct resource.

                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#bluez-dbus-ble-gatt-api","title":"Bluez-Dbus - BLE GATT API","text":"

                      The implementation of the Kura BLE APIs is based on the Bluez-Dbus library that provides an easy to use Bluetooth LE API based on BlueZ over DBus. The library eases the access to GATT services and the management of BLE connections and discovery, without using any wrapper library as it is based on a newer version of dbus-java which uses jnr-unixsocket.

                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#apis-description","title":"APIs description","text":"

                      The BLE APIs are exported in the org.eclipse.kura.bluetooth.le package. The interfaces are briefly described in the following.

                      • BluetoothLeService is the entry point of the OSGI service. It allows to get all the Bluetooth interfaces installed on the gateway or a specific one using the name of the adapter.
                      • BluetoothLeAdapter represents the physical Bluetooth adapter on the gateway. It allows to start/stop a discovery, search a specific BLE device based on the BD address, power up/down the adapter and get information about the adapter.
                      • BluetoothLeDevice represents a Bluetooth LE device. The interface provides methods for connections and disconnections, list the GATT services or search a specific one based on the UUID and get generic information about the device.
                      • BluetoothLeGattService represents a GATT service and allows listing the GATT characteristics provided by the device.
                      • BluetoothLeGattCharacteristic represents a GATT characteristic. It provides methods to read from and write to the characteristic, enable or disable notifications and get the properties.
                      • BluetoothLeGattDescriptor represents a GATT descriptor associated with the characteristic.

                      More information about the APIs can be found in API Reference.

                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#how-to-use-the-kura-ble-api","title":"How to use the Kura BLE API","text":"

                      This section briefly presents how to use the Kura BLE APIs, providing several code snippets to explain how to perform common bluetooth operations. For a complete example, please refer to the SensorTag application.

                      An application that wants to use the Kura BLE APIs should bind the BluetoothLeService OSGI service, as shown in the following Java snippet:

                      public void setBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = bluetoothLeService;\n}\n\npublic void unsetBluetoothLeService(BluetoothLeService bluetoothLeService) {\n    this.bluetoothLeService = null;\n}\n

                      and in the component definition:

                      <reference bind=\"setBluetoothLeService\" \n            cardinality=\"1..1\" \n            interface=\"org.eclipse.kura.bluetooth.le.BluetoothLeService\" \n            name=\"BluetoothLeService\" \n            policy=\"static\" \n            unbind=\"unsetBluetoothLeService\"/>\n
                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#get-the-bluetooth-adapter","title":"Get the Bluetooth adapter","text":"

                      Once bound to the BluetoothLeService, an application can get the Bluetooth adapter and power on it, if needed:

                      this.bluetoothLeAdapter = this.bluetoothLeService.getAdapter(adapterName);\nif (this.bluetoothLeAdapter != null) {\n    if (!this.bluetoothLeAdapter.isPowered()) {\n        this.bluetoothLeAdapter.setPowered(true);\n    }\n} \n

                      where adapterName is the name of the adapter, i.e. hci0.

                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#search-for-ble-devices","title":"Search for BLE devices","text":"

                      The BluetoothLeAdapter provides several methods to search for a device, a.k.a. perform a BLE discovery:

                      • Future<BluetoothLeDevice> findDeviceByAddress(long timeout, String address) search for a BLE device with the specified address. The method will perform a BLE discovery for at most timeout seconds or until the device is found. It will return a Future instance and the discovered device can be retrieved using the get() method.
                      • Future<BluetoothLeDevice> findDeviceByName(long timeout, String name) search for a BLE device with the specified system name and return a Future.
                      • void findDeviceByAddress(long timeout, String address, Consumer<BluetoothLeDevice> consumer) search for a BLE device with the specified address. The method will perform a BLE discovery for at most timeout seconds or until the device is found. When the device is found or the timeout is reached the consumer is used to get the device.
                      • void findDeviceByAddress(long timeout, String address, Consumer<BluetoothLeDevice> consumer) search for a BLE device with the specified name and use the provided consumer to return the device.
                      • Future<List<BluetoothLeDevice>> findDevices(long timeout) and void findDevices(long timeout, Consumer<List<BluetoothLeDevice>> consumer) are similar to the methods above, but they get a list of Bluetooth devices.

                      The following snippet shows how to perform a discovery of 10 seconds using findDevices method:

                      if (this.bluetoothLeAdapter.isDiscovering()) {\n    try {\n        this.bluetoothLeAdapter.stopDiscovery();\n    } catch (KuraException e) {\n        logger.error(\"Failed to stop discovery\", e);\n    }\n}\nFuture<List<BluetoothLeDevice>> future = this.bluetoothLeAdapter.findDevices(10);\ntry {\n    List<BluetoothLeDevice>; devices = future.get();\n} catch (InterruptedException | ExecutionException e) {\n    logger.error(\"Scan for devices failed\", e);\n}\n
                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#get-the-gatt-services-and-characteristics","title":"Get the GATT services and characteristics","text":"

                      To get the GATT services using the BluetoothLeDevice, use the following snippet:

                      try {\n    List<BluetoothLeGattService>; services = device.findServices();\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT services\", e);\n}\n

                      A specific GATT service can be retrieved using its UUID:

                      try {\n    BluetoothLeGattService service = device.findService(uuid);\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT service\", e);\n}\n

                      Using the GATT service, it is possible to get a specific GATT characteristic (or the complete list) and the GATT descriptor from it:

                      try {\n    BluetoothLeGattCharacteristic characteristic = service.findCharacteristic(characteristicUuid);\n    BluetoothLeGattDescriptor descriptor = characteristic.findDescriptor(descriptorUuid);\n} catch (KuraBluetoothResourceNotFoundException e) {\n    logger.error(\"Unable to find GATT resources\", e);\n}\n
                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#io-operations-on-gatt-characteristics-and-descriptors","title":"IO operations on GATT characteristics and descriptors","text":"

                      The Kura BLE APIs provides methods to manage the IO operations on GATT characteristics and descriptors. The following snippet provides an example on how to read and write data to a characteristic.

                      try {\n    byte[] valueRead = characteristic.readValue();\n    byte[] valueWrite = { 0x01};\n    characteristic.writeValue(valueWrite);\n} catch (KuraBluetoothIOException e) {\n    logger.error(\"IO operation failed\", e);\n}\n

                      In the following example, instead, a notification listener is configured to periodically receive the data from a GATT characteristic and print the first value of the given array. The period is internally set by the BLE device.

                      try {\n    Consumer<byte[]>; callback = valueBytes -> System.out.println((int) valueBytes[0]);\n    characteristic.enableValueNotifications(callback);\n} catch (KuraBluetoothNotificationException e) {\n    logger.error();\n}\n
                      "},{"location":"java-application-development/how-to-use-bt-le-apis/#configure-bluez-on-the-raspberry-pi","title":"Configure Bluez on the Raspberry Pi","text":"

                      The minimum version of Bluez supported by Kura Bluetooth LE APIs is 5.42. The Raspbian Stretch OS comes with Bluez 5.43, but older OS couldn't have an updated Bluez version. In this case, it is possible to compile and install Bluez from sources using a Raspberry Pi. The Bluez sources can be found here Proceed as follows:

                      • Install the packages needed for compile Bluez:

                      sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev\n
                      * Download bluez-5.43.tar.xz (or newer version) from here. * Decompress the compressed archive:

                      tar -xf bluez-5.43.tar.x\n
                      • Compile the sources:
                      cd bluez-5.43\n./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-library -disable-systemd --enable-experimental --enable-maintainer-mod\nmake\nmake install\n
                      "},{"location":"java-application-development/how-to-use-can-bus/","title":"How to Use CAN bus","text":"

                      The Kura CAN bus protocol implementation is based on the SocketCAN interface, which provides a socket interface to userspace applications. The sockets are designed as can0 and can1. The SocketCAN package is an implementation of Controller Area Network (CAN) protocols. For more information, refer to the following link: https://www.kernel.org/doc/Documentation/networking/can.txt.

                      "},{"location":"java-application-development/how-to-use-can-bus/#configure-the-can-bus-driver","title":"Configure the CAN bus Driver","text":"

                      The CAN network must be initialized prior to communications. Verify that the CAN driver module has been enabled in the kernel by issuing the following command:

                      ifconfig -a\n
                      The connections \u201ccan0\u201d and \u201ccan1\u201d should be displayed.

                      Next, the sockets must be enabled and configured using the following commands (the bitrate value must be set according to the bitrate of the device that will be connected):

                      ip link set can0 type can bitrate 50000 triple-sampling on\nip link set can0 up\nip link set can1 type can bitrate 50000 triple-sampling on\nip link set can1 up\n

                      "},{"location":"java-application-development/how-to-use-can-bus/#use-the-can-bus-driver-in-kura","title":"Use the CAN bus Driver in Kura","text":"

                      To use the Can bus Driver in Kura, the bundle org.eclipse.kura.protocol.can must be installed. Refer to the section Application Management for more information.

                      Once this bundle is installed and verified, the CanConnectionService provides access to basic functionalities of the CAN network, including:

                      • sendCanMessage \u2013 sends an array of bytes in RAW mode.

                      • receiveCanMessage \u2013 reads frames in RAW mode waiting on socket CAN.

                      Refer to the following Kura javadocs for more information: http://download.eclipse.org/kura/docs/api/5.2.0/apidocs/.

                      Also, for information about the wrapper that this service utilizes, refer to the following link: https://github.com/entropia/libsocket-can-java.

                      "},{"location":"java-application-development/how-to-use-dummy-signature-service/","title":"How to use the Dummy Container Signature Validation Service","text":"

                      The Dummy Container Signature Validation Service is an example implementation of the Container Signature Validation Service interface which mainly serves as a reference for future implementations and testing.

                      The purpose of this component is to have a service whose configuration dictates the signature verification outcome. In the main text area is possible to set the container image reference that the service will report as correctly signed.

                      The format accepted by the service is: <imageName>:<imageTag>@<imageDigest> where

                      • <imageName>: is the container image name. The value will need to be expressed in the form registryURL/imagename in case of a custom registry. Example: nginx or nvcr.io/nvidia/deepstream.
                      • <imageTag>: is the container image tag. Example: latest or 6.4-gc-triton-devel.
                      • <imageDigest>: is the image digest in the OCI specification format.

                      Each image should be separated by a newline. The service will report a successful signature validation for each image inside its configuration, returning the provided image digest as a result.

                      "},{"location":"java-application-development/how-to-use-gpio/","title":"How to use GPIO","text":"

                      GPIO resources can be accessed either using the GPIO Service provided by Kura, or directly using the OpenJDK Device I/O embedded library.

                      "},{"location":"java-application-development/how-to-use-gpio/#gpio-service","title":"GPIO Service","text":"

                      Access to GPIO resources is granted by the GPIOService. Once retrieved, the service can be used to acquire a GPIO Pin and use it as a digital output or a digital input.

                      The GPIO Service exposes methods to retrieve a GPIO Pin via its name or index as shown below.

                      KuraGpioPin thePin = gpioServiceInstance.getPinByTerminal(18);\nKuraGpioPin thePin = gpioServiceInstance.getPinByName(\"IgnitionPin\");\n

                      The KuraGpioPin object is used to manipulate GPIO Pins and exposes methods to read the status of an input, or set the status of digital output as shown below.

                      //sets digital output value to high\nthePin.setValue(true);\n\n//get value of a digital input pin\nboolean active = thePin.getValue();\n\n//listen for status change on a digital input pin\ntry {  \n      thePin.addPinStatusListener(new PinStatusListener() {\n          @Override    \n          public void pinStatusChange(boolean value) {      \n          // Perform tasks when pin status changes    \n          }  \n      });\n} catch (KuraClosedDeviceException e) {\n  // Here if GPIO cannot be acquired\n  } catch (IOException e) {\n    // Here on I/O error\n  }\n

                      "},{"location":"java-application-development/how-to-use-gpio/#pin-configuration","title":"Pin Configuration","text":"

                      Pin names, indexes, and configuration are defined in the jdk.dio.properties file.

                      Although GPIO pins can be accessed with their default configuration, the settings of each pin can be changed when acquiring it with the GPIO Service as shown below.

                      KuraGpioPin customInputPin = gpioServiceInstance.getPinByTerminal(\n  14, \n  KuraGPIODirection.INPUT, \n  KuraGPIOMode.INPUT_PULL_UP, \n  KuraGPIOTrigger.BOTH_LEVELS);\n

                      "},{"location":"java-application-development/how-to-use-gpio/#default-configuration","title":"Default Configuration","text":"

                      Default hardware configuration for the hardware platform is defined in the jdk.dio.properties file. Standard configuration for complex devices can be added on a per-device basis as shown below.

                      #Default PIN configuration. To be overwritten in the following lines\ngpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\n\n#Standard PIN configuration\n64 = deviceType: gpio.GPIOPin, pinNumber:64, name:RELAY1\n

                      Starting from Kura 5.5.0, the default pin configuration can be described in the form of symbolic links. The syntax is as follows:

                      /dev/digital_in1 = deviceType: gpio.GPIOPin, direction:0, mode=1, name:digital_in1\n

                      where /dev/digital_in1 points to /sys/class/gpio/gpioXYZ. The pin number is set to XYZ and it can be retrieved by name.

                      "},{"location":"java-application-development/how-to-use-gpio/#openjdk-device-io","title":"OpenJDK Device I/O","text":"

                      Linux-level access in Kura is granted through OpenJDK Device I/O, a third-party library that leverages standard Java ME Device I/O APIs to Java SE. Kura is distributed with the relevant native libraries, together with the default hardware configuration, for each platform on which it runs.

                      I2C, SPI, and GPIO resources can be directly accessed through the jdk.dio library present in the target platform.

                      "},{"location":"java-application-development/how-to-use-gpio/#apis","title":"APIs","text":"

                      Kura supports the full set of APIs for the listed device types. Refer to References for further API information.

                      "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-with-openjdk-device-io","title":"Accessing a GPIO Pin with OpenJDK Device I/O","text":"

                      A GPIO Pin can be accessed by referencing its index in the properties file, or by creating a Pin configuration object and feeding it to the DeviceManager as shown in the code examples below.

                      "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-by-its-index","title":"Accessing a GPIO Pin by its Index","text":"
                      #Accessing the GPIO Pin number 17. The default behaviour is defined in the\n#jdk.dio.properties file\n#\n#i.e.:\n# gpio.GPIOPin = initValue:0, deviceNumber:0, direction:3, mode:-1, trigger:3\n# 17 = deviceType: gpio.GPIOPin, pinNumber:17, name:GPIO_USER_1\n\nGPIOPin led = (GPIOPin)DeviceManager.open(17);\n\nled.setValue(true) //Turns the LED on\nled.setValue(false) //Turns the LED off\nboolean status = led.getValue() //true if the LED is on\n
                      "},{"location":"java-application-development/how-to-use-gpio/#accessing-a-gpio-pin-using-a-device-configuration-object","title":"Accessing a GPIO Pin Using a Device Configuration Object","text":"
                      #Accessing the Pin number 17 with custom configuration\n\nGPIOPinConfig pinConfig = new GPIOPinConfig(\n    DeviceConfig.DEFAULT,                       //GPIO Controller number or name\n    17,                                                 //GPIO Pin number\n    GPIOPinConfig.DIR_INPUT_ONLY,               //Pin direction\n    GPIOPinConfig.MODE_INPUT_PULL_DOWN,     //Pin resistor\n    GPIOPinConfig.TRIGGER_BOTH_EDGES,       //Triggers\n    false                                           //initial value (for outputs)\n);\n\nGPIOPin button = (GPIOPin) DeviceManager.open(GPIOPin.class, pinConfig);\n\nbutton.setInputListener(new PinListener(){\n        @Override\n        public void valueChanged(PinEvent event) {\n            System.out.println(\"PIN Status Changed!\");\n            System.out.println(event.getLastTimeStamp() + \" - \" + event.getValue());\n        }\n});\n
                      "},{"location":"java-application-development/how-to-use-modbus/","title":"How to Use Modbus","text":"

                      ModbusProtocolDevice is a service that provides a connection to a device or a network of devices over Serial Line (RS-232/RS-485) or Ethernet using the Modbus protocol. This service implements a subset of the Modbus Application Protocol as defined by Modbus Organization (for more information, refer to http://www.modbus.org/specs.php).

                      The ModbusProtocolDevice service needs to receive a valid Modbus configuration including the following parameters:

                      • Modbus protocol mode - defines the protocol mode as RTU or ASCII (only RTU mode for Ethernet connections).

                      • Timeout - sets the timeout in order to detect a disconnected device.

                      The ModbusProtocolDevice service also requires a valid Serial Line or Ethernet connection configuration including the following parameters:

                      • Serial Line
                      • port name
                      • baudrate
                      • bits
                      • stops
                      • parity

                      • Ethernet

                      • ip address
                      • port number

                      When a valid configuration is received, the ModbusProtocolDevice service tries to open the communication port. Serial Line communication uses the CommConnection class; Ethernet communication is based on java.net.Socket. When the communication is established, the client makes direct calls to the Modbus functions. The first parameter of each method is the Modbus address of the queried unit. This address must be in the range of 1 - 247.

                      "},{"location":"java-application-development/how-to-use-modbus/#function-codes","title":"Function Codes","text":"

                      The following function codes are implemented within the ModbusProtocolDevice service:

                      • 01 (0x01) readCoils(int unitAddr,\u00a0 int dataAddress, int count) - read 1 to 2000 maximum contiguous status of coils from the attached field device with address \"unitAddr\". An array of booleans representing the requested data points is returned.

                      • 02 (0x02) readDiscreteInputs(int unitAddr,\u00a0 int dataAddress, int count) - read 1 to 2000 maximum contiguous status of discrete inputs from the attached field device with address \"unitAddr\". An array of booleans representing the requested data points is returned.

                      • 03 (0x03) readHoldingRegisters(int unitAddr,\u00a0 int dataAddress, int count) - read contents of 1 to 125 maximum contiguous block of holding registers from the attached field device with address \"unitAddr\". An array of int representing the requested data points (data registers on 2 bytes) is returned.

                      • 04 (0x04) readInputRegisters(int unitAddr,\u00a0 int dataAddress, int count) - read contents of 1 to 125 maximum contiguous block of input registers from the attached field device with address \"unitAddr\". An array of int representing the requested data points (data registers on 2 bytes) is returned.

                      • 05 (0x05) writeSingleCoil(int unitAddr,\u00a0 int dataAddress, boolean data) - write a single output to either ON or OFF in the attached field device with address \"unitAddr\".

                      • 06 (0x06) writeSingleRegister(int unitAddr,\u00a0 int dataAddress, int data) - write a single holding register in the attached field device with address \"unitAddr\".

                      • 15 (0x0F) writeMultipleCoils(int unitAddr,\u00a0 int dataAddress, boolean[ ] data) - write multiple coils in a sequence of coils to either ON or OFF in the attached field device with address \"unitAddr\".

                      • 16 (0x10) writeMultipleRegister(int unitAddr,\u00a0 int dataAddress, int[ ] data) - write a block of contiguous registers (1 to 123) in the attached field device with address \"unitAddr\".

                      All functions throw a ModbusProtocolException. Valid exceptions include:

                      • INVALID_CONFIGURATION

                      • NOT_AVAILABLE

                      • NOT_CONNECTED

                      • TRANSACTION_FAILURE

                      "},{"location":"java-application-development/how-to-use-modbus/#code-examples","title":"Code Examples","text":"

                      The ModbusProtocolDeviceService is an OSGi declarative service referenced in the client XML definition file:

                      <reference bind=\"setModbusProtocolDeviceService\"\n       cardinality=\"1..1\"\n       interface=\"org.eclipse.kura.protocol.modbus.ModbusProtocolDeviceService\"\n       name=\"ModbusProtocolDeviceService\"\n       policy=\"static\"\n       unbind=\"unsetModbusProtocolDeviceService\"/>\n
                      public void setModbusProtocolDeviceService(ModbusProtocolDeviceService modbusService) {\n  this.m_protocolDevice = modbusService;\n}\n\npublic void unsetModbusProtocolDeviceService(ModbusProtocolDeviceService modbusService) {\n  this.m_protocolDevice = null;\n}\n
                      if(m_protocolDevice!=null){\n  m_protocolDevice.disconnect();\n  m_protocolDevice.configureConnection(modbusSerialProperties);\n}\n
                      If no exception occurs, the ModbusProtocolDevice can then be used to exchange data:
                      boolean[] digitalInputs = m_protocolDevice.readDiscreteInputs(1, 2048, 8);\nint[] analogInputs = m_protocolDevice.readInputRegisters(1, 512, 8);\nboolean[] digitalOutputs = m_protocolDevice.readCoils(1, 2048, 6); // LEDS\n\n// to set LEDS\nm_protocolDevice.writeSingleCoil(1, 2047 + LED, On?TurnON:TurnOFF);\n

                      "},{"location":"java-application-development/how-to-use-watchdog/","title":"How to use watchdog","text":""},{"location":"java-application-development/how-to-use-watchdog/#overview","title":"Overview","text":"

                      When enabled, the watchdog is a peripheral monitor that will reboot the system if it is not refreshed during a certain time interval. In Kura, the WatchdogService can be used by critical applications. If the specified application is alive, the service notifies the watchdog; if the application is down, the service stops notifying the watchdog and a hardware reset occurs.

                      The WatchdogService notifies the kernel watchdog driver using the /dev/watchdog device file. You can verify that the watchdog driver is installed using the following command:

                      ls \u2013l /dev/watchdog\n
                      "},{"location":"java-application-development/how-to-use-watchdog/#configuration","title":"Configuration","text":"

                      To configure the WatchdogService, select the WatchdogService option located in the Services area as shown in the screen capture below.

                      The WatchdogService provides the following configuration parameters:

                      • enabled - sets whether or not this service is enabled or disabled. If enabled, you must set a pingInterval periodicity compatible with the watchdog driver.

                      • pingInterval - specifies the time between two watchdog notifications. This time is hardware dependent. Generally, the maximum time between two notifications should be between 30 seconds and 1 minute. 10000 milliseconds for the pingInterval is typically a good choice.

                      "},{"location":"java-application-development/how-to-use-watchdog/#code-example","title":"Code Example","text":"

                      The WatchdogService references a list of Critical Components that correspond to the applications implementing the CriticalComponent interface.

                      CriticalComponent is an interface that can be used to denote a component that is crucial to system operations. If a component implements CriticalComponent, then it must state its name as well as its criticalComponentTimeout. The name is a unique identifier in the system. The timeout is the length of time in milliseconds that the CriticalComponent must \"check in\" with the WatchdogService. If the CriticalComponent extends beyond the period of time specified in this timeout, a system reboot will be performed based on the WatchdogService configuration.

                      If at least one of the registered CriticalComponents has not \"checked in\" during the pingInterval time, the WatchdogService stops notifying the watchdog driver. The system reboots when the time interval reaches the hardware time that is programmed for the watchdog. When the WatchdogService is enabled and no application is using it, the service runs silently in the background.

                      An example of the WatchdogService can be found here.

                      The following code snippets demonstrate how to implement the CriticalComponent interface:

                      public class ModbusManager implements ConfigurableComponent, CriticalComponent, CloudClientListener\n

                      Registration of the class in WatchdogService::

                      if(m_watchdogService!=null){\n  m_watchdogService.registerCriticalComponent(this);\n}\n

                      Periodic call to checkin method of WatchdogService in the main loop (keeps watchdog notification alive):

                      if(m_watchdogService!=null){\n  m_watchdogService.checkin(this);\n}\n
                      "},{"location":"java-application-development/kura-workspace-setup/","title":"Kura Workspace Setup","text":"

                      This document describes how to set up the Eclipse Kura workspace development environment, which consists of the following components:

                      • JVM (Java JDK SE 8)
                      • Eclipse IDE
                      • Eclipse Kura Workspace

                      This setup will allow you to develop applications or bundles running on Eclipse Kura. It will install only the APIs and the examples. If you want to contribute to the Eclipse Kura project follow this guide instead.

                      The Eclipse Kura development environment may be installed on Windows, Linux, or Mac OS. The setup instructions will be the same across each OS though each system may have unique characteristics.

                      Info

                      The local emulation of Eclipse Kura code is only supported in Linux and Mac, not in Windows.

                      "},{"location":"java-application-development/kura-workspace-setup/#jvm-installation","title":"JVM Installation","text":"

                      Download and install JDK SE 8 from the following links as appropriate for your OS.

                      For Windows and Linux users, the JDK can be downloaded from the following link: Java SE 8 Downloads. Use the latest version of the Java SE Development Kit and download the version appropriate for your system.

                      For additional information regarding the installation of Java 8 on all supported operating systems, see JDK 8 and JRE 8 Installation Guide.

                      "},{"location":"java-application-development/kura-workspace-setup/#installing-eclipse-ide","title":"Installing Eclipse IDE","text":"

                      Before installing Eclipse, you should choose directory locations for the Eclipse install and its workspaces.

                      Info

                      The following points should be kept in mind regarding Eclipse installs and workspaces:

                      • The directory location of the Eclipse workspaces should be chosen carefully. Once Eclipse is installed and workspaces are created, they should never be moved to another location in the file system.
                      • There may be multiple installs of Eclipse (of different or similar versions), and single instances of each install can be run simultaneously; but there should never be more that one instance of a specific install running at the same time (to avoid corruption to the Eclipse environment).
                      • Each workspace should be used with only one Eclipse install. You should avoid opening the workspace from more than one installation of Eclipse.

                      Download the current distribution of Eclipse for your OS from Eclipse official website. Choose the Eclipse IDE for Eclipse Committers.

                      The zipped Eclipse file will be downloaded to the local file system and can be saved to a temporary location that can be deleted after Eclipse has been installed. After the file has been downloaded, it should be extracted to the Eclipse installs directory. The following screen capture shows the installation in Linux using an eclipse\\installs directory. The Eclipse executable will then be found in the eclipse\\installs\\eclipse directory. This installation will be different depending on the operating system.

                      Because there may potentially be future Eclipse installs extracted into this location, before doing anything else, rename the directory, such as eclipse\\installs\\juno1\\.

                      Warning

                      Once you begin using this Eclipse install, it should NOT be moved or renamed.

                      "},{"location":"java-application-development/kura-workspace-setup/#workspaces","title":"Workspaces","text":""},{"location":"java-application-development/kura-workspace-setup/#creating-an-eclipse-workspace","title":"Creating an Eclipse Workspace","text":"

                      Run Eclipse by clicking its executable in the install directory.

                      When Eclipse is run for the first time, a workspace needs to be created. A single workspace will contain all the Java code/projects/bundles, Eclipse configuration parameters, and other relevant files for a specific business-level product. If the Use this as the default option is selected, the designated workspace becomes the default each time you run Eclipse.

                      If a workspace has not already been defined, or if you are creating a different workspace for another development project, enter a new workspace name. The workspace should be named appropriate to the project/product being developed.

                      Warning

                      Once you begin using a particular workspace, it should NOT be moved or renamed at any time.

                      Otherwise, select an existing workspace and click OK. After Eclipse is running, you can select the Eclipse menu File | Switch Workspace | Other to create or open a different workspace.

                      After the new workspace opens, click the Workbench icon to display the development environment.

                      Info

                      Additional workspace configuration:

                      • In the Eclipse workspace modify the lifecycle mapping by adding these XML lines to the lifecycle-mapping-metadata.xml in Eclipse Kura workspace. You can find the file in the Windows -> Preferences -> Maven -> Lifecycle Mappings -> Open workspace lifecycle mappings metadata. After editing the file, reload it by pressing the \"Reload workspace lifecycle mappings metadata\" button.
                        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lifecycleMappingMetadata>\n    <lifecycleMappingFilters>\n        <lifecycleMappingFilter>\n            <symbolicName>org.eclipse.m2e.pde.connector</symbolicName>\n            <versionRange>[2.1.2,)</versionRange>\n            <packagingTypes>\n                <packagingType>eclipse-test-plugin</packagingType>\n                <packagingType>eclipse-plugin</packagingType>\n                <packagingType>eclipse-feature</packagingType>\n            </packagingTypes>\n        </lifecycleMappingFilter>\n    </lifecycleMappingFilters>\n</lifecycleMappingMetadata>\n
                      • Install the eclipse-tycho plugin following this steps:
                        1. Menu Help -> Install new software... -> Paste the m2eclipse-tycho repository URL in the Work with: text field -> expand the category and select the Tycho Project Configurators Feature and proceed with the installation.
                        2. Then restart Eclipse.
                      "},{"location":"java-application-development/kura-workspace-setup/#importing-the-eclipse-kura-user-workspace","title":"Importing the Eclipse Kura User Workspace","text":"

                      To set up your Eclipse Kura project workspace, you will need to download the Eclipse Kura User Workspace archive from Eclipse Kura Download Page.

                      From the Eclipse File menu, select the Import option. In the Import dialog box, expand the General heading, select Existing Projects into Workspace, and then click Next.

                      Now click the Select archive file option button and browse to the archive file, such as user_workspace_archive_.zip.

                      Finally, click Finish to import the projects. At this point, you should have four projects in your workspace. The four projects are as follows:

                      • org.eclipse.kura.api \u2013 the core Eclipse Kura API.

                      • org.eclipse.kura.demo.heater \u2013 an example project that you can use as a starting point for creating your own bundle.

                      • org.eclipse.kura.emulator \u2013 the emulator project for running Eclipse Kura within Eclipse (Linux/Mac only).

                      • target-definition \u2013 a set of required bundles that are dependencies of the APIs and Eclipse Kura.

                      Eclipse will also report some errors at this point. See the next section to resolve those errors.

                      "},{"location":"java-application-development/kura-workspace-setup/#workspace-setup","title":"Workspace Setup","text":"

                      This section will guide the users to configure the development workspace environment.

                      "},{"location":"java-application-development/kura-workspace-setup/#jre-configuration","title":"JRE Configuration","text":"

                      The latest Eclipse IDEs require and configure, by default, a Java 11 environment. In order to be able to leverage and develop using the new workspace for Eclipse Kura, the user will be required to perform a one-time operation to specify to the IDE a Java 8 JDK. Opening the Eclipse preferences and selecting the Installed JREs in the Java section, the user has to select an installed Java 8 instance.

                      After applying the configuration change, the user will be prompted to align also the compiler options. To do so, selecting the Compiler entry in the Java section, the user has to select 1.8 from the list of available Java versions.

                      After applying the changes, the user will be prompted to recompile the environment.

                      "},{"location":"java-application-development/kura-workspace-setup/#target-definition-setup","title":"Target Definition Setup","text":"

                      Click the arrow next to the target-definition project in the workspace and double-click kura-equinox_.target to open it.

                      In the Target Definition window, click the link Set as Target Platform. Doing so will reset the target platform, rebuild the Eclipse Kura projects, and clear the errors that were reported. At this point, you are ready to begin developing Eclipse Kura-based applications for your target platform.

                      "},{"location":"java-application-development/kura-workspace-setup/#run-the-eclipse-kura-emulator","title":"Run the Eclipse Kura Emulator","text":"

                      To start the Eclipse Kura emulator, select the \"Eclipse Kura Emulator.launch\" profile from \"Other Projects\" -> \"setups\" -> \"launchers\" and open it with \"Run as\" -> \"Run Configurations...\". Then click on the \"Arguments\" tab and update the \"VM arguments\" as follows to adapt the paths to the folder structure created by the Oomph installer:

                      -Dkura.have.net.admin=false -Dorg.osgi.framework.storage=/tmp/osgi/framework_storage -Dosgi.clean=true -Dosgi.noShutdown=true -Declipse.ignoreApp=true -Dorg.eclipse.kura.mode=emulator -Dkura.configuration=file:${workspace_loc}/../git/kura/kura/emulator/org.eclipse.kura.emulator/src/main/resources/kura.properties -Ddpa.configuration=/tmp/kura/dpa.properties -Dlog4j.configurationFile=file:${workspace_loc}/../git/kura/kura/emulator/org.eclipse.kura.emulator/src/main/resources/log4j.xml -Dkura.data=${workspace_loc}/kura/data -Dkura.snapshots=${workspace_loc}/kura/user/snapshots -Dorg.eclipse.equinox.http.jetty.customizer.class=org.eclipse.kura.jetty.customizer.KuraJettyCustomizer\n

                      The Eclipse Kura Web UI will be available at the following URL: http://127.0.0.1:8080 with username and password admin.

                      "},{"location":"java-application-development/ram-usage-considerations/","title":"RAM Usage Considerations","text":"

                      During application development and before moving to production, it is advisable to understand if the amount of free RAM available on the device is enough for correct device operation.

                      Since RAM usage is application dependent, it is important to perform some stress tests to bring the device in the worst case conditions and verify system behavior.

                      Some of the aspects that should be taken into account are the following:

                      "},{"location":"java-application-development/ram-usage-considerations/#java-heap-memory-usage","title":"Java Heap memory usage","text":"

                      Java heap is used to store the Java objects and classes at runtime.

                      The heap should be:

                      1. Large enough to satisfy the requirements of applications running inside Kura.
                      2. Small enough so that the requirements of the system and applications running outside Kura are satisfied.

                      The size of the heap is controlled by the -Xms and -Xmx Java command line arguments. These parameters are defined in the /opt/eclipse/kura/bin/start_kura_debug.sh (for development mode) and /opt/eclipse/kura/bin/start_kura_background.sh (for production mode).

                      The -Xms parameter defines the initial size of Java heap and -Xmx defines the maximum size. The JVM will start using Xms as the size of the heap, and then it will grow the heap at runtime up to Xmx if needed, depending on application memory demand.

                      Resizing the heap has a cost in terms of performance, for this reason Xms and Xmx are set to the same size by default on most platforms.

                      In order to understand if the heap is large enough, it is advisable to perform a stress test simulating the conditions of maximum memory demand by the applications running inside Kura. For example, if a in-memory database instance is used by a DataService instance, during the test the database can be filled up to the maximum capacity to verify if this causes any issue.

                      Regarding point 2., it should be noted that heap memory is not necessarily backed by physical memory immediately after JVM startup. Even if the JVM performs an allocation of size Xmx immediately, physical memory will be assigned to the Java process by the kernel only when the memory pages are actually accessed by the JVM.

                      For this reason the amount of physical memory used by the JVM might appear small right after system boot and grow with time, up to the maximum size. This can happen even if the applications running inside Kura do not have high memory requirements, and can lead to potential issues that show up only after some time.

                      In order to recreate such issues, the -XX:+AlwaysPreTouch JVM command line option can be used during development to force the JVM to access all heap memory after start, causing the JVM process to use the maximum amount of physical memory immediately.

                      "},{"location":"java-application-development/ram-usage-considerations/#logging","title":"Logging","text":"

                      Another aspect that can lead to RAM related issues is logging. As a general rule, it is recommended to reduce the amount of log messages produced by Kura during normal operation.

                      Kura default logging configuration (/opt/eclipse/kura/log4j/log4j.xml) depends on the platform.

                      The size of the files in the /var/log directory will be checked periodically and the files will be rotated to the persisted /var/old_logs directory if needed.

                      "},{"location":"java-application-development/ram-usage-considerations/#external-application-ram-usage","title":"External application RAM usage","text":"

                      If external applications are installed on the system (e.g. Docker containers), their RAM usage should be analyzed as well.

                      Stress tests related to Java heap size, log size and external applications can be run simultaneously to simulate a worst case scenario.

                      "},{"location":"java-application-development/remote-debugging-on-target-platform/","title":"Remote debugging on target platform","text":"

                      Eclipse Kura can be started with Java Debug Wire Protocol (JDWP) support, allowing the remote debugging of the developed application using Eclipse IDE. The procedure for remote debugging is presented in the following.

                      • Connect to the target platform (i.e. RaspberryPi) and stop the Kura application typing sudo systemctl stop kura or sudo /etc/init.d/kura stop.

                      • Start Kura with Java Debug Wire Protocol (JDWP) typing sudo /opt/eclipse/kura/bin/start_kura_debug.sh. This will start Kura and open an OSGi console. It will also start listening for socket connections on port 8000.

                      Warning

                      Starting from Java 9, the JDWP socket connector accepts only local connections by default (see here for further details). To enable remote debugging on Java 9, the following line in /opt/eclipse/kura/bin/start_kura_debug.sh: -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n \\has to be replaced with the following one: -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=*:8000,suspend=n \\

                      • Open the tcp port 8000 in the firewall. This can be done through the firewall tab in Kura web interface or using iptables.

                      • Install your application bundle on the target platform.

                      • From Eclipse IDE, set a breakpoint in the application code at a point that will be reached (i.e. activation method, common logging statement, etc.). Then:

                      • Go to \"Run -> Debug Configurations\u2026\"
                      • Select \u201cRemote Java Application\u201d and click the \u201cNew launch configuration\u201d button
                      • For \u201cProject:\u201d, select the bundle project to be debugged
                      • For \u201cConnection Type:\u201d, select the default \u201cStandard (Socket Attach)\u201d
                      • For \u201cConnection Properties:\u201d, enter the IP address of the target platform and the tcp port 8000
                      • Click Debug

                      • Eclipse will connect to the target platform VM and switch to the Debug Perspective when the breakpoint will have been hit.

                      • To stop the remote debugging, select the \u201cDisconnect\u201d button from the Debug Perspective.

                      "},{"location":"kura-wires/assets-as-wire-components/","title":"Assets as Wire Components","text":"

                      An Asset can be used inside a Wire Graph, in this case it is represented as node with two ports an input port and an output port. An Asset used in this way is called WireAsset.

                      "},{"location":"kura-wires/assets-as-wire-components/#read-mode","title":"Read mode","text":"

                      Every time a WireAsset receives an envelope on its input port, it will read the values of all of its channels with READ or READ_WRITE type. The result is emitted as a WireEnvelope with a single WireRecord. The WireRecord contains the following properties:

                      • a property with key assetName, value type STRING and the emitting asset asset name as value
                      • a property with key assetError, value type STRING if emit.errors and emit.connection.errors is enabled and a general connection failure is returned by the Driver. See the description of the emit.connection.errors parameter for more details.
                      • a property with key assetTimestamp, value type LONG, reporting a timestamp in milliseconds since Unix epoch (midnight, January 1, 1970 UTC). This property may be present or not depending on the value of the timestamp.mode configuration parameter or in case of general connection exception reported by the Driver, see the configuration parameter description for more details.

                      For each channel in asset configuration with READ or READ_WRITE type named name:

                      • a property with key = name, value type = value.type in channel configuration and value = value obtained from read operation. This property will be present only if the read operation is successful.
                      • a property with key = <name>_timestamp value type = LONG reporting a timestamp in milliseconds since UNIX epoch. This property will be present only if the timestamp.mode Asset configuration property is set to PER_CHANNEL
                      • a property with key = <name>_error, value type = STRING reporting an error message. This property will be present only if read operation fails and the emit.errors Asset configuration property is set to true.

                      For example, if an Asset that has the channel configuration shown in the picture below receives a WireEnvelope on its input port, it can emit an envelope with the following content, assuming that the read operation for each channel succeed:

                      • WireEnvelope
                        • WireRecord[0]
                          • assetName: modbusAsset (type = STRING)
                          • LED1: true (type = BOOLEAN)
                          • LED1_timestamp: 1597925188 (type = LONG)
                          • LED2: false (type = BOOLEAN)
                          • LED2_timestamp: 1597925188 (type = LONG)
                          • LED3: true (type = BOOLEAN)
                          • LED3_timestamp: 1597925188 (type = LONG)
                          • LED4-RED: false (type = BOOLEAN)
                          • LED4-RED_timestamp: 1597925188 (type = LONG)
                          • LED4-GREEN: false (type = BOOLEAN)
                          • LED4-GREEN_timestamp: 1597925188 (type = LONG)
                          • LED4-BLUE: true (type = BOOLEAN)
                          • LED4-BLUE_timestamp: 1597925188 (type = LONG)
                          • Toggle-4: true (type = BOOLEAN)
                          • Toggle-4_timestamp: 1597925188 (type = LONG)
                          • Toggle-5: false (type = BOOLEAN)
                          • Toggle-5_timestamp: 1597925188 (type = LONG)
                          • Toggle-6: true (type = BOOLEAN)
                          • Toggle-6_timestamp: 1597925188 (type = LONG)
                          • Counter-3: 123 (type = INTEGER)
                          • Counter-3_timestamp: 1597925188 (type = LONG)
                          • Quad-Counter: 11 (type = INTEGER)
                          • Quad-Counter_timestamp: 1597925188 (type = LONG)
                          • Reset-Counter3: false (type = BOOLEAN)
                          • Reset-Counter3_timestamp: 1597925188 (type = LONG)
                          • Reset-Quad-Counter: false (type = BOOLEAN)
                          • Reset-Quad-Counter_timestamp: 1597925188 (type = LONG)

                      The emitted WireEnvelope contains a single record containing the properties described above.

                      The Logger WireComponent can be used to inspect the messages emitted on a specific output port of a WireComponent by creating a connection between the output port of the component to the input port of the Logger. In this case the content of the received envelopes will be printed on device log (/var/log/kura.log).

                      As mentioned above, the read operation is performed only if an envelope is received on the input port of the WireAsset. In order to achieve this, another component must be connected to the input port of the WireAsset.

                      An example of such component can be the Timer, this component can be configured to periodically emit an envelope containing a single wire record with a single property named TIMER reporting the current UNIX timestamp. Connecting this component to a WireAsset allows to implement a simple read polling cycle. The configuration of the timer defines the polling interval.

                      "},{"location":"kura-wires/assets-as-wire-components/#listen-mode","title":"Listen mode","text":"

                      Enabling the listen flag allows to enable unsolicited notifications from the driver.

                      When this happens, the Asset will emit an WireEnvelope containing the updated value for the channel involved in the event. The content of this envelope is the same as the one generated in case of a read operation on the channel.

                      For example if listen is ticked for LED1 and the driver above detects a value change in that channel, the Asset will emit the following envelope:

                      • WireEnvelope
                        • WireRecord[0]
                          • assetName: modbusAsset (type = STRING)
                          • LED1: false (type = BOOLEAN)
                          • LED1_timestamp: 1597925200 (type = LONG)

                      This mode does not require to connect any component to the input port of the driver. The conditions that trigger the events for the channels and their meaning is reported in the Driver specific documentation.

                      Note: The example above is not completely realistic since the Modbus driver does not support listen mode. In this case ticking the listen flag will have no effect. The support for listen mode is mentioned in driver documentation.

                      Listen mode and Read mode are not mutually exclusive. If a channel is defined as READ or READ_WRITE and the listen flag is ticked, the driver will emit the channel value when a WireEnvelope is received on its input port or when a driver event is generated.

                      "},{"location":"kura-wires/assets-as-wire-components/#write-mode","title":"Write mode","text":"

                      Additionally, the Wire Graph can also be used to update asset values through write operations, according to the following rule.

                      Every time a WireAsset receives an envelope on its input port, for each property contained in the received WireRecords with key <key>, value type <value type> and value <value>, the driver will perform this operation: If a channel with name <key> is defined in asset configuration whose value.type is equal to <value type> and type is WRITE or READ_WRITE, then the Asset will write <value> to the channel.

                      For example if the Asset above receives the following envelope:

                      • WireEnvelope
                        • WireRecord[0]
                          • LED1: false (type = BOOLEAN)
                          • LED2: 78 (type = LONG)
                          • Toggle-4: true (type = BOOLEAN)
                          • foo: bar (type = STRING)

                      The following operations will happen:

                      • Since the Asset configuration contains a channel named LED1, with value.type = BOOLEAN, and type = READ_WRITE, the driver will write false to that channel for the rule mentioned above.
                      • The LED2: 78 property will have no effect, since the Asset configuration contains a channel named LED2 with type = READ_WRITE, but value.type = BOOLEAN != LONG.
                      • The Toggle-4: true property will have no effect, since the asset contains a channel named Toggle-4, with value.type = BOOLEAN but type = READ != WRITE | READ_WRITE
                      • The foo: bar property will have no effect, since none of the defined channels has foo as name.
                      • The Asset will read and emit all of the channel values with type = READ or READ_WRITE, since a WireEnvelope has been received.
                      "},{"location":"kura-wires/assets-as-wire-components/#wire-asset-global-configuration-parameters","title":"Wire Asset global configuration parameters","text":"

                      The WireAsset component provides the following global (non per-channel) configuration parameters that can be used to customize component behavior:

                      • emit.all.channels: Specifies whether the values of all READ or READ_WRITE channels should be emitted when a channel event is received in listen mode. If set to true, the values for all channels will be read and emitted, if set to false, only the value for the channel related to the event will be emitted.

                      • timestamp.mode: Allows to configure how timestamps are emitted, the following modes are supported:

                        • NO_TIMESTAMPS: no timestamp-related properties will be emitted.

                        • PER_CHANNEL: the component will emit a driver-generated timestamp property per channel, as the value of the <channel name>_timestamp property.

                        • SINGLE_ASSET_GENERATED: the component will emit a single timestamp per request, generated by the Asset itself before emitting the envelope as the value of the assetTimestamp property.

                        • SINGLE_DRIVER_GENERATED_MAX and SINGLE_DRIVER_GENERATED_MIN: the component will emit a single driver generated timestamp being respectively the max (most recent) or min (oldest) among the timestamps of the channels as the value of the assetTimestamp property.

                      • emit.errors: Specifies whether channel specific errors should be included or not in emitted envelopes. If enabled, the component will add an additional property per channel, named <channel_name>_error. If the channel operation fails, the property value will be an error message reported by the Driver, if the operation succeeds the property value will be the empty string.

                      • emit.connection.errors: Specifies whether the component should emit an envelope in case of a general connection exception reported by the Driver (for example due to the fact that the connection with a remote device cannot be established). The error message associated with the exception will be emitted in a property named assetError. In case of connection exception, channel values are not available and no channel related properties will be emitted. If the timestamp.mode property is set to a value other than NO_TIMESTAMPS, the component will also emit a assetTimestamp property reporting current system time. This property will be ignored if emit.errors is disabled. Example of emitted envelope contents:

                      • WireEnvelope

                        • WireRecord[0]
                          • assetName: myAsset (type = STRING)
                          • assetError: Connection refused (type = STRING)
                          • assetTimestamp: 1597925200 (type = LONG)
                      • emit.on.change: If set to true, this component will include a channel value in the output emitted in Kura Wires only if it differs from the last emitted value for that channel. Channel errors will always be emitted if emit.errors is set to true. If as a result of a read operation no changes are detected in any referenced channel, the Asset will emit an envelope containing no channel values, unless emit.empty.envelopes is set to false.

                      • emit.empty.envelopes: If set to false, this component will not emit empty envelopes.

                      "},{"location":"kura-wires/introduction/","title":"Introduction","text":"

                      The Wires feature aims to simplify the development of IoT Edge Computing Applications leveraging reusable configurable components that can be wired together and which, eventually, allows configurable cooperation between these components.

                      In the dataflow programming model, the application logic is expressed as a directed graph (flow) where each node can have inputs, outputs, and independent processing units. There are nodes that only produce outputs and ones that only consume inputs, which usually represent the start and the end of the flow. The inner-graph nodes process the inputs and produce outputs for downstream nodes. The processing unit of a node executes independently and does not affect the execution of other nodes. Thus, the nodes are highly reusable and portable.

                      In this way, the developer can easily prototype its solution without sacrificing flexibility and working at a high level of abstraction: the graph can be extended adding new nodes or drawing new connections. Furthermore, the developer can take advantage of the Eclipse Marketplace integration, being able to use open source or commercial building blocks into the final solution, by simply dragging and dropping a link to the Eclipse Marketplace in the Administrative Web UI.

                      "},{"location":"kura-wires/introduction/#data-model","title":"Data Model","text":"

                      The communication over a single graph edge (Wire) is message oriented, messages are called WireEnvelopes. Each WireEnvelope contains a list of WireRecords. Each WireRecord contains a set of key-value pairs, called properties, the property key is always a string and it is unique within the same record, the property value can have one of the following types:

                      • BOOLEAN
                      • BYTE_ARRAY
                      • DOUBLE
                      • INTEGER
                      • LONG
                      • FLOAT
                      • STRING
                      "},{"location":"kura-wires/introduction/#wire-composer","title":"Wire Composer","text":"

                      The Wire Composer is the main source of interaction with the Wires framework. It is accessible by clicking on the Wires button under System.

                      The Wires page is composed by a central composer, where the graph can be actually designed, a lower part that is populated when a wire component is clicked and that allows to update the component configuration and a section in the right with the available Wire Components.

                      "},{"location":"kura-wires/introduction/#wire-components","title":"Wire Components","text":"

                      The following components are distributed with Kura:

                      • Timer ticks every x seconds and starts the graph;
                      • Publisher publishes every message received from a Wire (Wire Message). It is configurable in order to use a specific Cloud Service;
                      • Subscriber subscribes to a configurable topic via a specific Cloud Service. It receives a message from a Cloud Platform, wraps it as a Wire Message and sends it through the connected wires to the other components that are part of the Wire Graph;
                      • Wire Record Store allows the storage of Wire Messages into a specific stored collection. It has rules for message cleanup and retention;
                      • Wire Record Query, allows the filtering of messages residing in a store via a specific query. The corresponding messages are sent as Wire Messages to the connected Wire Components;
                      • Logger logs the received messages;
                      • Asset \u200ballows the definition of Wire Channels that will be used to communicate with a field device through the associated Driver instance.
                      "},{"location":"kura-wires/introduction/#graph-download","title":"Graph Download","text":"

                      In the top left part of the Wires page the Download button allows to download the configuration of the graph and of all the components that are part of the graph.

                      This snapshot can be used to replicate the same configuration across all the fleet of devices.

                      To upload the stored graph, the user has to access the Settings page and in the Snapshots section click the Upload and Apply button.

                      Warning

                      The graph configuration will be actually merged with the one existing. Be careful to Delete the existing graph and apply, if you don't want to merge with the existing Wires configuration.

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/","title":"WireGraphService Configuration Format","text":"

                      This document describes the configuration format for the WireGraphService component.

                      The WireGraphService configuration contains all the information related to the Wire Graph topology and rendering properties. The pid of the WireGraphService configuration is org.eclipse.kura.wire.graph.WireGraphService.

                      The WireGraphService configuration represents the current graph layout as a single string typed property named WireGraph that represents a serialized JSON representation of a WireGraph object.

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#json-definitions","title":"JSON definitions","text":""},{"location":"kura-wires/wire-graph-service-configuration-format/#position","title":"Position","text":"

                      An object representing a Wire Component position.

                      Properties:

                      • x: number
                      • optional If not specified, 0.0 will be used as default value The x coordinate of the Wire Component inside the graph canvas
                      • y: number
                      • optional If not specified, 0.0 will be used as default value The y coordinate of the Wire Component inside the graph canvas

                      {\n  \"x\": 40,\n  \"y\": 0\n}\n
                      {\n  \"x\": 1.5\n}\n
                      {}\n

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#portnamelist","title":"PortNameList","text":"

                      An object that specifies custom names for Wire Component input and output ports. The properties name for this object must be represented as an integer starting from 0, matching the index of the port whose name needs to be assigned. If the property name is not specified, the default port name will be used.

                      Properties:

                      • _portIndex: string The name for the port of index _portIndex

                      {\n  \"0\": \"foo\",\n  \"1\": \"bar\"\n}\n
                      {}\n

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#renderingproperties","title":"RenderingProperties","text":"

                      An object describing some Wire Component rendering parameters like position and custom port names.

                      Properties:

                      • position: object
                      • optional If not specified the component coordinates will be set to 0.0.
                        • Position
                      • inputPortNames: object
                      • optional If not specified, the default input port names will be used.
                        • PortNameList
                      • outputPortNames: object
                      • optional If not specified, the default output port names will be used.
                        • PortNameList

                      {\n  \"inputPortNames\": {},\n  \"outputPortNames\": {\n    \"0\": \"foo\",\n    \"1\": \"bar\"\n  },\n  \"position\": {\n    \"x\": 40,\n    \"y\": 0\n  }\n}\n
                      {\n  \"inputPortNames\": {},\n  \"outputPortNames\": {\n    \"0\": \"foo\",\n    \"1\": \"bar\"\n  }\n}\n
                      {\n  \"position\": {\n    \"x\": 40,\n    \"y\": 0\n  }\n}\n
                      {}\n

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#wirecomponent","title":"WireComponent","text":"

                      An object that describes a Wire Component that is part of a Wire Graph

                      Properties:

                      • pid: string The Wire Component pid
                      • inputPortCount: number An integer reporting the number of input ports of the Wire Component.
                      • outputPortCount: number An integer reporting the number of output ports of the Wire Component.
                      • renderingProperties: object
                      • optional If not specified, the default rendering properties will be used
                        • RenderingProperties

                      {\n  \"inputPortCount\": 1,\n  \"outputPortCount\": 2,\n  \"pid\": \"cond\",\n  \"renderingProperties\": {\n    \"inputPortNames\": {},\n    \"outputPortNames\": {\n      \"0\": \"foo\",\n      \"1\": \"bar\"\n    },\n    \"position\": {\n      \"x\": 40,\n      \"y\": 0\n    }\n  }\n}\n
                      {\n  \"inputPortCount\": 0,\n  \"outputPortCount\": 1,\n  \"pid\": \"timer\",\n  \"renderingProperties\": {\n    \"inputPortNames\": {},\n    \"outputPortNames\": {},\n    \"position\": {\n      \"x\": -220,\n      \"y\": -20\n    }\n  }\n}\n

                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#wire","title":"Wire","text":"

                      An object that describes a Wire connecting two Wire Components.

                      Properties:

                      • emitter: string The pid of the emitter component.
                      • emitterPort: number The index of the output port of the emitter component that is connected to this Wire.
                      • receiver: string The pid of the receiver component.
                      • receiverPort: number The index of the input port of the receiver component that is connected to this Wire.
                      {\n  \"emitter\": \"timer\",\n  \"emitterPort\": 0,\n  \"receiver\": \"cond\",\n  \"receiverPort\": 0\n}\n
                      "},{"location":"kura-wires/wire-graph-service-configuration-format/#wiregraph","title":"WireGraph","text":"

                      An object that describes the topology and rendering properties of a Wire Graph

                      Properties:

                      • components: array The list of the wire components contained in the Wire Graph
                        • array elements: object
                        • WireComponent
                      • wires: array The list of Wires contained in the Wire Graph
                        • array elements: object
                        • Wire
                      {\n  \"components\": [\n    {\n      \"inputPortCount\": 0,\n      \"outputPortCount\": 1,\n      \"pid\": \"timer\",\n      \"renderingProperties\": {\n        \"inputPortNames\": {},\n        \"outputPortNames\": {},\n        \"position\": {\n          \"x\": -220,\n          \"y\": -20\n        }\n      }\n    },\n    {\n      \"inputPortCount\": 1,\n      \"outputPortCount\": 2,\n      \"pid\": \"cond\",\n      \"renderingProperties\": {\n        \"inputPortNames\": {},\n        \"outputPortNames\": {\n          \"0\": \"foo\",\n          \"1\": \"bar\"\n        },\n        \"position\": {\n          \"x\": 40,\n          \"y\": 0\n        }\n      }\n    }\n  ],\n  \"wires\": [\n    {\n      \"emitter\": \"timer\",\n      \"emitterPort\": 0,\n      \"receiver\": \"cond\",\n      \"receiverPort\": 0\n    }\n  ]\n}\n
                      "},{"location":"kura-wires/wire-service-references/","title":"References","text":"

                      Additional information about Wires is available at the following resources:

                      "},{"location":"kura-wires/wire-service-references/#dzone","title":"DZONE","text":"
                      • Kura Wires Can Help Overcome Challenges of Industrial IoT.
                      • Kura Wires: A Sneak Peek.
                      • Kura Wires: A Different Perspective to Develop IIoT Applications.
                      • Different Dataflow Programming Approaches and Comparison With Kura Wires.
                      "},{"location":"kura-wires/wire-service-references/#master-thesis","title":"Master Thesis","text":"
                      • Kura Wires: Design and Development of a Component for managing Devices and Drivers in Eclipse Kura 2.0 by Amit Kumar Mondal.
                      "},{"location":"kura-wires/wire-service-references/#conferences-and-slides","title":"Conferences and slides","text":"
                      • Building IoT Mashups for Industry 4.0 with Eclipse Kura and Kura Wires.
                      • Industry 4.0 with Eclipse Kura.
                      "},{"location":"kura-wires/wire-service-references/#youtube","title":"Youtube","text":"
                      • Kura Wires - A Mashup in Eclipse Kura for Industry 4.0.
                      • Kura Wires: Industry 4.0 with Eclipse Kura - EclipseCon Europe 2016 IoT Day.
                      "},{"location":"kura-wires/wire-service-rest-v1/","title":"Wire Service V1 REST APIs and MQTT Request Handler","text":"

                      The WIRE-V1 cloud request handler and the corresponding REST APIs allow to update, delete and get the current Wire Graph status. The request handler also supports creating, updating and deleting Asset and Driver instances and retrieving the metadata required for supporting Wire Graph editing applications.

                      The GET/graph/shapshot and PUT/graph/snapshot requests use the same format as the Wire Graph snapshot functionality of the Kura Web UI.

                      A Wire Graph snapshot can be obtained by navigating to the Wires section of Kura Web UI, clicking the Download button and selecting the JSON format.

                      Accessing the REST APIs requires to use an identity with the rest.wires.admin permission assigned.

                      • Wire Service V1 REST APIs and MQTT Request Handler
                      • Request definitions
                        • GET/graph/shapshot
                        • PUT/graph/snapshot
                        • DEL/graph
                        • GET/drivers/pids
                        • GET/assets/pids
                        • GET/graph/topology
                        • POST/configs/byPid
                        • DEL/configs/byPid
                        • PUT/configs
                        • GET/metadata
                        • GET/metadata/wireComponents/factoryPids
                        • GET/metadata/wireComponents/definitions
                        • POST/metadata/wireComponents/definitions/byFactoryPid
                        • GET/metadata/drivers/factoryPids
                        • GET/metadata/driver/ocds
                        • POST/metadata/drivers/ocds/byFactoryPid
                        • GET/metadata/drivers/channelDescriptors
                        • POST/metadata/drivers/channelDescriptors/byPid
                        • GET/metadata/assets/channelDescriptor
                      • JSON definitions
                        • WireComponentDefinition
                        • DriverChannelDescriptor
                        • WireGraphMetadata
                      • Wire Graph snapshot example
                      "},{"location":"kura-wires/wire-service-rest-v1/#request-definitions","title":"Request definitions","text":""},{"location":"kura-wires/wire-service-rest-v1/#getgraphshapshot","title":"GET/graph/shapshot","text":"
                      • REST API path : /services/wire/v1/graph/snapshot
                      • description : Returns the current Wire Graph Configuration. The received configuration includes the WireGraphService configuration containing the graph layout, the configuration of the components currently referenced by the Wire Graph, and the configuration of the existing Driver instances.
                      • responses :
                        • 200
                        • description : The current wire graph configuration.
                        • response body :
                          • ComponentConfigurationList
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#putgraphsnapshot","title":"PUT/graph/snapshot","text":"
                      • REST API path : /services/wire/v1/graph/snapshot
                      • description : Updates the current Wire Graph.
                      • request body :
                        • ComponentConfigurationList
                      • responses :
                        • 200
                        • description : The current Wire Graph has been updated.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: updateGraph for the graph update operation, update:$pid or delete:$pid for update or delete operations performed on configurations not referenced by the Wire Graph, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport

                      This request will replace the current graph topology with the received one. The received configuration must satisfy the following requirements. If any of the requirements is not met, the operation will fail and no changes will be applied to the system: * The configuration of the org.eclipse.kura.wire.graph.WireGraphService component must be specified. * The configuration of the org.eclipse.kura.wire.graph.WireGraphService component must contain a property named WireGraph of STRING type containing the graph layout as described in the WireGraphService document. * The inputPortCount and outputPortCount properties must be specified for all components in WireGraphService configuration. * The configuration of all components referenced by WireGraphService configuration that do not exist on target device must be specified. * The configuration of all components referenced by WireGraphService configuration that do not exist on target device must specify the service.factoryPid configuration property reporting the component factory pid.

                      If a component already exists on the system and its configuration is supplied as part of the request, the component configuration will be updated. In this case the usual configuration merge semantics will be applied, the set of received properties will be merged with the existing one. The properties in the request body will overwrite the existing ones with the same name.

                      WireAsset configurations are treated sligtly differently, an update to a WireAsset configuration is performed by deleting the existing component and creating a new instance with the received configuration. This behavior is necessary in order to allow channel removal.

                      It is also allowed to specify Driver or Asset configurations that are not referenced by the Wire Graph included in the request body.

                      "},{"location":"kura-wires/wire-service-rest-v1/#delgraph","title":"DEL/graph","text":"
                      • REST API path : /services/wire/v1/graph
                      • description : Deletes the current Wire Graph.
                      • responses :
                        • 200
                        • description : The current wire graph has been deleted.
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getdriverspids","title":"GET/drivers/pids","text":"
                      • REST API path : /services/wire/v1/drivers/pids
                      • description : Returns the list of existing Driver pids.
                      • responses :
                        • 200
                        • description : The list of driver pids
                        • response body :
                          • PidAndFactoryPidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getassetspids","title":"GET/assets/pids","text":"
                      • REST API path : /services/wire/v1/assets/pids
                      • description : Returns the list of existing Asset pids. The returned pids may or may not be referenced by the current Wire Graph.
                      • responses :
                        • 200
                        • description : The list of driver pids
                        • response body :
                          • PidAndFactoryPidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getgraphtopology","title":"GET/graph/topology","text":"
                      • REST API path : /services/wire/v1/graph/topology
                      • description : Returns the current Wire Graph topology as a WireGraph object. The returned object is the current value of the WireGraph property in Wire Graph Service configuration. This request allows to inspect the Wire Graph topology without downloading the entire Wire Graph snapshot with GET/graph/shapshot.
                      • responses :
                        • 200
                        • description : The current wire graph topology.
                        • response body :
                          • WireGraph
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#postconfigsbypid","title":"POST/configs/byPid","text":"
                      • REST API path : /services/wire/v1/configs/byPid
                      • description : Returns the list of configurations referenced by the provided pids. This request can only be used to retrieve Wire Component, Driver or Asset configurations.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The returned configurations. If a configuration cannot be found, it will not be included in the response. The returned configuration list can be empty.
                        • response body :
                          • ComponentConfigurationList
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters.
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#delconfigsbypid","title":"DEL/configs/byPid","text":"
                      • REST API path : /services/wire/v1/configs/byPid
                      • description : Deletes the configurations referenced by the provided pids. This request can only be used to delete Wire Component, Driver or Asset configurations. This request does not allow to delete configurations that are referenced by the current Wire Graph.
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters. This status will be returned also if the request references pids that are currenly part of the Wire Graph, or components that are not Wire Components, Driver or Asset instances. If this status is retured, no changes will be applied to the system.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for delete operations, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#putconfigs","title":"PUT/configs","text":"
                      • REST API path : /services/wire/v1/configs
                      • description : Updates or creates the provided configurations. This request can only be used to process Wire Component, Driver or Asset configurations. This request does not allow to create Wire Component configurations that are not referenced by the current Wire Graph, except from WireAsset instances. The component creation/update semantics are the same as PUT/graph/snapshot, this request can be used to perform configuration updates whithout knowing or specifying the Wire Graph topology.
                      • request body :
                        • ComponentConfigurationList
                      • responses :
                        • 200
                        • description : The request succeeded
                        • 400
                        • description : The request body is not valid JSON or it contains invalid parameters. This status will be returned also if the request involves the creation of components that are not Wire Components, Driver or Asset instances, or the creation of Wire Components that are not referenced by the current Wire Graph. If this status is retured, no changes will be applied to the system.
                        • response body :
                          • GenericFailureReport
                        • 500
                        • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid or delete:$pid for update or delete operations, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                        • response body :

                          Variants:

                          • object
                            • GenericFailureReport
                          • object
                            • BatchFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadata","title":"GET/metadata","text":"
                      • REST API path : /services/wire/v1/metadata
                      • description : Returns all available Wire Component, Asset and Driver metadata in a single request.
                      • responses :
                        • 200
                        • description : The request succeeded. Single fields in the response can be missing if the corresponding list is empty (e.g. driverDescriptors can be missing if no Driver instances exist on the system)
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatawirecomponentsfactorypids","title":"GET/metadata/wireComponents/factoryPids","text":"
                      • REST API path : /services/wire/v1/metadata/wireComponents/factoryPids
                      • description : Return the list of available Wire Component factory pids
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • response body :
                          • PidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatawirecomponentsdefinitions","title":"GET/metadata/wireComponents/definitions","text":"
                      • REST API path : /services/wire/v1/metadata/wireComponents/definitions
                      • description : Returns all available Wire Component definitions
                      • responses :
                        • 200
                        • description : The available Wire Component definitions. All fields except at most wireComponentDefinitions will be missing.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatawirecomponentsdefinitionsbyfactorypid","title":"POST/metadata/wireComponents/definitions/byFactoryPid","text":"
                      • REST API path : /services/wire/v1/metadata/wireComponents/definitions/byFactoryPid
                      • description : Returns the Wire Component definitions for the given set of factory pids
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The available Wire Component definitions. All fields except at most wireComponentDefinitions will be missing. If the metadata for a given factoryPid is not found, the request will succeed and it will not be included in the list.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriversfactorypids","title":"GET/metadata/drivers/factoryPids","text":"
                      • REST API path : /services/wire/v1/metadata/drivers/factoryPids
                      • description : Return the list of available Driver factory pids
                      • responses :
                        • 200
                        • description : The request succeeded.
                        • response body :
                          • PidSet
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriverocds","title":"GET/metadata/driver/ocds","text":"
                      • REST API path : /services/wire/v1/metadata/drivers/ocds
                      • description : Returns all available Driver OCDs
                      • responses :
                        • 200
                        • description : The available Driver OCDs. All fields except at most driverOCDs will be missing.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatadriversocdsbyfactorypid","title":"POST/metadata/drivers/ocds/byFactoryPid","text":"
                      • REST API path : /services/wire/v1/metadata/drivers/ocds/byFactoryPid
                      • description : Returns the Driver OCDSs for the given set of factory pids
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The requested Driver OCDs. All fields except at most driverOCDs will be missing. If the metadata for a given factoryPid is not found, the request will succeed and it will not be included in the list.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadatadriverschanneldescriptors","title":"GET/metadata/drivers/channelDescriptors","text":"
                      • REST API path : /wire/v1/metadata/drivers/channelDescriptors
                      • description : Returns the list of all available Driver channel descriptors
                      • responses :
                        • 200
                        • description : The list of Driver channel descriptors. All fields except at most driverChannelDescriptors will be missing.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#postmetadatadriverschanneldescriptorsbypid","title":"POST/metadata/drivers/channelDescriptors/byPid","text":"
                      • REST API path : /services/wire/v1/metadata/drivers/channelDescriptors/byPid
                      • description : Returns the Driver channel descriptors for the given set of pids
                      • request body :
                        • PidSet
                      • responses :
                        • 200
                        • description : The requested Driver channel descriptors. All fields except at most driverChannelDescriptors will be missing. If the metadata for a given pid is not found, the request will succeed and it will not be included in the list.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#getmetadataassetschanneldescriptor","title":"GET/metadata/assets/channelDescriptor","text":"
                      • REST API path : /wire/v1/metadata/assets/channelDescriptor
                      • description : Returns the Asset channel descriptor
                      • responses :
                        • 200
                        • description : The Asset channel descriptors. All fields except assetChannelDescriptor will be missing.
                        • response body :
                          • WireGraphMetadata
                        • 500
                        • description : An unexpected internal error occurred.
                        • response body :
                          • GenericFailureReport
                      "},{"location":"kura-wires/wire-service-rest-v1/#json-definitions","title":"JSON definitions","text":""},{"location":"kura-wires/wire-service-rest-v1/#wirecomponentdefinition","title":"WireComponentDefinition","text":"

                      A Wire Component definition object

                      Properties:

                      • factoryPid: string The component factory pid
                      • minInputPorts: number The minimum input port count
                      • maxInputPorts: number The maximum number of input ports
                      • defaultInputPorts: number The default number of input ports
                      • minOutputPorts: number The minimum number of output ports
                      • maxOutputPorts: number The maximum number of output ports
                      • defaultOutputPorts: number The default number of output ports
                      • inputPortNames: object
                      • optional If no custom input port names are defined
                        • PortNameList
                      • outputPortNames: object
                      • optional If no custom output port names are defined
                        • PortNameList
                      • componentOcd: array
                      • optional If the component OCD is empty The component OCD
                        • array elements: object
                        • AttributeDefinition
                      {\n  \"componentOCD\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"50\",\n      \"description\": \"The maximum number of envelopes that can be stored in the queue of this FIFO component\",\n      \"id\": \"queue.capacity\",\n      \"isRequired\": true,\n      \"name\": \"queue.capacity\",\n      \"type\": \"INTEGER\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"false\",\n      \"description\": \"Defines the behavior in case of full queue: if set to true new envelopes will be dropped,              otherwise, if an emitter delivers an envelope to this component it will block until the envelope can be successfully enqueued.\",\n      \"id\": \"discard.envelopes\",\n      \"isRequired\": true,\n      \"name\": \"discard.envelopes\",\n      \"type\": \"BOOLEAN\"\n    }\n  ],\n  \"defaultInputPorts\": 1,\n  \"defaultOutputPorts\": 1,\n  \"factoryPid\": \"org.eclipse.kura.wire.Fifo\",\n  \"maxInputPorts\": 1,\n  \"maxOutputPorts\": 1,\n  \"minInputPorts\": 1,\n  \"minOutputPorts\": 1\n}\n
                      "},{"location":"kura-wires/wire-service-rest-v1/#driverchanneldescriptor","title":"DriverChannelDescriptor","text":"

                      An object that describes Driver specific channel configuration properties

                      Properties:

                      • pid: string The Driver pid
                      • factoryPid: string The Driver factory pid
                      • channelDescriptor: array
                      • optional If the driver does not define any channel property The list of Driver specific channel configuration properties
                        • array elements: object
                        • AttributeDefinition
                      {\n  \"channelDescriptor\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"AA:BB:CC:DD:EE:FF\",\n      \"description\": \"sensortag.address\",\n      \"id\": \"sensortag.address\",\n      \"isRequired\": true,\n      \"name\": \"sensortag.address\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"TEMP_AMBIENT\",\n      \"description\": \"sensor.name\",\n      \"id\": \"sensor.name\",\n      \"isRequired\": true,\n      \"name\": \"sensor.name\",\n      \"option\": [\n        {\n          \"label\": \"TEMP_AMBIENT\",\n          \"value\": \"TEMP_AMBIENT\"\n        },\n        {\n          \"label\": \"TEMP_TARGET\",\n          \"value\": \"TEMP_TARGET\"\n        },\n        {\n          \"label\": \"HUMIDITY\",\n          \"value\": \"HUMIDITY\"\n        },\n        {\n          \"label\": \"ACCELERATION_X\",\n          \"value\": \"ACCELERATION_X\"\n        },\n        {\n          \"label\": \"ACCELERATION_Y\",\n          \"value\": \"ACCELERATION_Y\"\n        },\n        {\n          \"label\": \"ACCELERATION_Z\",\n          \"value\": \"ACCELERATION_Z\"\n        },\n        {\n          \"label\": \"MAGNETIC_X\",\n          \"value\": \"MAGNETIC_X\"\n        },\n        {\n          \"label\": \"MAGNETIC_Y\",\n          \"value\": \"MAGNETIC_Y\"\n        },\n        {\n          \"label\": \"MAGNETIC_Z\",\n          \"value\": \"MAGNETIC_Z\"\n        },\n        {\n          \"label\": \"GYROSCOPE_X\",\n          \"value\": \"GYROSCOPE_X\"\n        },\n        {\n          \"label\": \"GYROSCOPE_Y\",\n          \"value\": \"GYROSCOPE_Y\"\n        },\n        {\n          \"label\": \"GYROSCOPE_Z\",\n          \"value\": \"GYROSCOPE_Z\"\n        },\n        {\n          \"label\": \"LIGHT\",\n          \"value\": \"LIGHT\"\n        },\n        {\n          \"label\": \"PRESSURE\",\n          \"value\": \"PRESSURE\"\n        },\n        {\n          \"label\": \"GREEN_LED\",\n          \"value\": \"GREEN_LED\"\n        },\n        {\n          \"label\": \"RED_LED\",\n          \"value\": \"RED_LED\"\n        },\n        {\n          \"label\": \"BUZZER\",\n          \"value\": \"BUZZER\"\n        },\n        {\n          \"label\": \"KEYS\",\n          \"value\": \"KEYS\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"1000\",\n      \"description\": \"notification.period\",\n      \"id\": \"notification.period\",\n      \"isRequired\": true,\n      \"name\": \"notification.period\",\n      \"type\": \"INTEGER\"\n    }\n  ],\n  \"factoryPid\": \"org.eclipse.kura.driver.ble.sensortag\",\n  \"pid\": \"sensortag\"\n}\n
                      "},{"location":"kura-wires/wire-service-rest-v1/#wiregraphmetadata","title":"WireGraphMetadata","text":"

                      An object contiaining metatada describing Wire Components, Drivers and Assets

                      Properties:

                      • wireComponentDefinitions: array
                      • optional See request specific documentation The list of Wire Component definitions
                        • array elements: object
                        • WireComponentDefinition
                      • driverOCDs: array
                      • optional See request specific documentation The list of Driver factory component OCDs
                        • array elements: object
                        • ComponentConfiguration
                      • driverChannelDescriptors: array
                      • optional See request specific documentation The list of Driver channel descriptors
                        • array elements: object
                        • DriverChannelDescriptor
                      • assetChannelDescriptor: array
                      • optional See request specific documentation The list of Asset specific channel configuration properties
                        • array elements: object
                        • AttributeDefinition
                      {\n  \"assetChannelDescriptor\": [\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"true\",\n      \"description\": \"Determines if the channel is enabled or not\",\n      \"id\": \"+enabled\",\n      \"isRequired\": true,\n      \"name\": \"enabled\",\n      \"type\": \"BOOLEAN\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"Channel-1\",\n      \"description\": \"Name of the Channel\",\n      \"id\": \"+name\",\n      \"isRequired\": true,\n      \"name\": \"name\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"READ\",\n      \"description\": \"Type of the channel\",\n      \"id\": \"+type\",\n      \"isRequired\": true,\n      \"name\": \"type\",\n      \"option\": [\n        {\n          \"label\": \"READ\",\n          \"value\": \"READ\"\n        },\n        {\n          \"label\": \"READ_WRITE\",\n          \"value\": \"READ_WRITE\"\n        },\n        {\n          \"label\": \"WRITE\",\n          \"value\": \"WRITE\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"INTEGER\",\n      \"description\": \"Value type of the channel\",\n      \"id\": \"+value.type\",\n      \"isRequired\": true,\n      \"name\": \"value.type\",\n      \"option\": [\n        {\n          \"label\": \"BOOLEAN\",\n          \"value\": \"BOOLEAN\"\n        },\n        {\n          \"label\": \"BYTE_ARRAY\",\n          \"value\": \"BYTE_ARRAY\"\n        },\n        {\n          \"label\": \"DOUBLE\",\n          \"value\": \"DOUBLE\"\n        },\n        {\n          \"label\": \"INTEGER\",\n          \"value\": \"INTEGER\"\n        },\n        {\n          \"label\": \"LONG\",\n          \"value\": \"LONG\"\n        },\n        {\n          \"label\": \"FLOAT\",\n          \"value\": \"FLOAT\"\n        },\n        {\n          \"label\": \"STRING\",\n          \"value\": \"STRING\"\n        }\n      ],\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"description\": \"Scale to be applied to the numeric value of the channel\",\n      \"id\": \"+scale\",\n      \"isRequired\": false,\n      \"name\": \"scale\",\n      \"type\": \"DOUBLE\"\n    },\n    {\n      \"cardinality\": 0,\n      \"description\": \"Offset to be applied to the numeric value of the channel\",\n      \"id\": \"+offset\",\n      \"isRequired\": false,\n      \"name\": \"offset\",\n      \"type\": \"DOUBLE\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"\",\n      \"description\": \"Unit associated to the value of the channel\",\n      \"id\": \"+unit\",\n      \"isRequired\": false,\n      \"name\": \"unit\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"cardinality\": 0,\n      \"defaultValue\": \"false\",\n      \"description\": \"Specifies if WireAsset should emit envelopes on Channel events\",\n      \"id\": \"+listen\",\n      \"isRequired\": true,\n      \"name\": \"listen\",\n      \"type\": \"BOOLEAN\"\n    }\n  ],\n  \"driverChannelDescriptors\": [\n    {\n      \"channelDescriptor\": [\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"MyNode\",\n          \"description\": \"node.id\",\n          \"id\": \"node.id\",\n          \"isRequired\": true,\n          \"name\": \"node.id\",\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"2\",\n          \"description\": \"node.namespace.index\",\n          \"id\": \"node.namespace.index\",\n          \"isRequired\": true,\n          \"name\": \"node.namespace.index\",\n          \"type\": \"INTEGER\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"DEFINED_BY_JAVA_TYPE\",\n          \"description\": \"opcua.type\",\n          \"id\": \"opcua.type\",\n          \"isRequired\": true,\n          \"name\": \"opcua.type\",\n          \"option\": [\n            {\n              \"label\": \"DEFINED_BY_JAVA_TYPE\",\n              \"value\": \"DEFINED_BY_JAVA_TYPE\"\n            },\n            {\n              \"label\": \"BOOLEAN\",\n              \"value\": \"BOOLEAN\"\n            },\n            {\n              \"label\": \"SBYTE\",\n              \"value\": \"SBYTE\"\n            },\n            {\n              \"label\": \"INT16\",\n              \"value\": \"INT16\"\n            },\n            {\n              \"label\": \"INT32\",\n              \"value\": \"INT32\"\n            },\n            {\n              \"label\": \"INT64\",\n              \"value\": \"INT64\"\n            },\n            {\n              \"label\": \"BYTE\",\n              \"value\": \"BYTE\"\n            },\n            {\n              \"label\": \"UINT16\",\n              \"value\": \"UINT16\"\n            },\n            {\n              \"label\": \"UINT32\",\n              \"value\": \"UINT32\"\n            },\n            {\n              \"label\": \"UINT64\",\n              \"value\": \"UINT64\"\n            },\n            {\n              \"label\": \"FLOAT\",\n              \"value\": \"FLOAT\"\n            },\n            {\n              \"label\": \"DOUBLE\",\n              \"value\": \"DOUBLE\"\n            },\n            {\n              \"label\": \"STRING\",\n              \"value\": \"STRING\"\n            },\n            {\n              \"label\": \"BYTE_STRING\",\n              \"value\": \"BYTE_STRING\"\n            },\n            {\n              \"label\": \"BYTE_ARRAY\",\n              \"value\": \"BYTE_ARRAY\"\n            },\n            {\n              \"label\": \"SBYTE_ARRAY\",\n              \"value\": \"SBYTE_ARRAY\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"STRING\",\n          \"description\": \"node.id.type\",\n          \"id\": \"node.id.type\",\n          \"isRequired\": true,\n          \"name\": \"node.id.type\",\n          \"option\": [\n            {\n              \"label\": \"NUMERIC\",\n              \"value\": \"NUMERIC\"\n            },\n            {\n              \"label\": \"STRING\",\n              \"value\": \"STRING\"\n            },\n            {\n              \"label\": \"GUID\",\n              \"value\": \"GUID\"\n            },\n            {\n              \"label\": \"OPAQUE\",\n              \"value\": \"OPAQUE\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"Value\",\n          \"description\": \"attribute\",\n          \"id\": \"attribute\",\n          \"isRequired\": true,\n          \"name\": \"attribute\",\n          \"option\": [\n            {\n              \"label\": \"NodeId\",\n              \"value\": \"NodeId\"\n            },\n            {\n              \"label\": \"NodeClass\",\n              \"value\": \"NodeClass\"\n            },\n            {\n              \"label\": \"BrowseName\",\n              \"value\": \"BrowseName\"\n            },\n            {\n              \"label\": \"DisplayName\",\n              \"value\": \"DisplayName\"\n            },\n            {\n              \"label\": \"Description\",\n              \"value\": \"Description\"\n            },\n            {\n              \"label\": \"WriteMask\",\n              \"value\": \"WriteMask\"\n            },\n            {\n              \"label\": \"UserWriteMask\",\n              \"value\": \"UserWriteMask\"\n            },\n            {\n              \"label\": \"IsAbstract\",\n              \"value\": \"IsAbstract\"\n            },\n            {\n              \"label\": \"Symmetric\",\n              \"value\": \"Symmetric\"\n            },\n            {\n              \"label\": \"InverseName\",\n              \"value\": \"InverseName\"\n            },\n            {\n              \"label\": \"ContainsNoLoops\",\n              \"value\": \"ContainsNoLoops\"\n            },\n            {\n              \"label\": \"EventNotifier\",\n              \"value\": \"EventNotifier\"\n            },\n            {\n              \"label\": \"Value\",\n              \"value\": \"Value\"\n            },\n            {\n              \"label\": \"DataType\",\n              \"value\": \"DataType\"\n            },\n            {\n              \"label\": \"ValueRank\",\n              \"value\": \"ValueRank\"\n            },\n            {\n              \"label\": \"ArrayDimensions\",\n              \"value\": \"ArrayDimensions\"\n            },\n            {\n              \"label\": \"AccessLevel\",\n              \"value\": \"AccessLevel\"\n            },\n            {\n              \"label\": \"UserAccessLevel\",\n              \"value\": \"UserAccessLevel\"\n            },\n            {\n              \"label\": \"MinimumSamplingInterval\",\n              \"value\": \"MinimumSamplingInterval\"\n            },\n            {\n              \"label\": \"Historizing\",\n              \"value\": \"Historizing\"\n            },\n            {\n              \"label\": \"Executable\",\n              \"value\": \"Executable\"\n            },\n            {\n              \"label\": \"UserExecutable\",\n              \"value\": \"UserExecutable\"\n            }\n          ],\n          \"type\": \"STRING\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"1000\",\n          \"description\": \"listen.sampling.interval\",\n          \"id\": \"listen.sampling.interval\",\n          \"isRequired\": true,\n          \"name\": \"listen.sampling.interval\",\n          \"type\": \"DOUBLE\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"10\",\n          \"description\": \"listen.queue.size\",\n          \"id\": \"listen.queue.size\",\n          \"isRequired\": true,\n          \"name\": \"listen.queue.size\",\n          \"type\": \"LONG\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"true\",\n          \"description\": \"listen.discard.oldest\",\n          \"id\": \"listen.discard.oldest\",\n          \"isRequired\": true,\n          \"name\": \"listen.discard.oldest\",\n          \"type\": \"BOOLEAN\"\n        },\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"false\",\n          \"description\": \"listen.subscribe.to.children\",\n          \"id\": \"listen.subscribe.to.children\",\n          \"isRequired\": true,\n          \"name\": \"listen.subscribe.to.children\",\n          \"type\": \"BOOLEAN\"\n        }\n      ],\n      \"factoryPid\": \"org.eclipse.kura.driver.opcua\",\n      \"pid\": \"opcuaDriver\"\n    }\n  ],\n  \"driverOCDs\": [\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"default-server\",\n            \"description\": \"OPC-UA Endpoint IP Address\",\n            \"id\": \"endpoint.ip\",\n            \"isRequired\": true,\n            \"name\": \"Endpoint IP\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"53530\",\n            \"description\": \"OPC-UA Endpoint Port\",\n            \"id\": \"endpoint.port\",\n            \"isRequired\": true,\n            \"min\": \"1\",\n            \"name\": \"Endpoint port\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"OPC-UA-Server\",\n            \"description\": \"OPC-UA Server Name\",\n            \"id\": \"server.name\",\n            \"isRequired\": false,\n            \"name\": \"Server Name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"false\",\n            \"description\": \"If set to true the driver will use the hostname, port, and server name parameters specified in the configuration instead of the values contained in endpoint descriptions fetched from the server.\",\n            \"id\": \"force.endpoint.url\",\n            \"isRequired\": false,\n            \"name\": \"Force endpoint URL\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"120\",\n            \"description\": \"Session timeout (in seconds)\",\n            \"id\": \"session.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Session timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"60\",\n            \"description\": \"Request timeout (in seconds)\",\n            \"id\": \"request.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Request timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"40\",\n            \"description\": \"The time to wait for the server response to the 'Hello' message (in seconds)\",\n            \"id\": \"acknowledge.timeout\",\n            \"isRequired\": true,\n            \"name\": \"Acknowledge timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"opc-ua client\",\n            \"description\": \"OPC-UA application name\",\n            \"id\": \"application.name\",\n            \"isRequired\": true,\n            \"name\": \"Application name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"urn:kura:opcua:client\",\n            \"description\": \"OPC-UA application uri\",\n            \"id\": \"application.uri\",\n            \"isRequired\": true,\n            \"name\": \"Application URI\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"1000\",\n            \"description\": \"The publish interval in milliseconds for the subscription created by the driver.\",\n            \"id\": \"subscription.publish.interval\",\n            \"isRequired\": true,\n            \"name\": \"Subscription publish interval\",\n            \"type\": \"LONG\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"PFX or JKS Keystore\",\n            \"description\": \"Absolute path of the PKCS or JKS keystore that contains the OPC-UA client certificate, private key and trusted server certificates\",\n            \"id\": \"certificate.location\",\n            \"isRequired\": true,\n            \"name\": \"Keystore path\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"0\",\n            \"description\": \"Security Policy\",\n            \"id\": \"security.policy\",\n            \"isRequired\": true,\n            \"name\": \"Security policy\",\n            \"option\": [\n              {\n                \"label\": \"None\",\n                \"value\": \"0\"\n              },\n              {\n                \"label\": \"Basic128Rsa15\",\n                \"value\": \"1\"\n              },\n              {\n                \"label\": \"Basic256\",\n                \"value\": \"2\"\n              },\n              {\n                \"label\": \"Basic256Sha256\",\n                \"value\": \"3\"\n              }\n            ],\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"description\": \"OPC-UA server username\",\n            \"id\": \"username\",\n            \"isRequired\": false,\n            \"name\": \"Username\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"description\": \"OPC-UA server password\",\n            \"id\": \"password\",\n            \"isRequired\": false,\n            \"name\": \"Password\",\n            \"type\": \"PASSWORD\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"client-ai\",\n            \"description\": \"Alias for the client certificate in the keystore\",\n            \"id\": \"keystore.client.alias\",\n            \"isRequired\": true,\n            \"name\": \"Client certificate alias\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"false\",\n            \"description\": \"Specifies whether to enable or not server certificate verification\",\n            \"id\": \"authenticate.server\",\n            \"isRequired\": true,\n            \"name\": \"Enable server authentication\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"PKCS12\",\n            \"description\": \"Keystore type\",\n            \"id\": \"keystore.type\",\n            \"isRequired\": true,\n            \"name\": \"Keystore type\",\n            \"option\": [\n              {\n                \"label\": \"PKCS11\",\n                \"value\": \"PKCS11\"\n              },\n              {\n                \"label\": \"PKCS12\",\n                \"value\": \"PKCS12\"\n              },\n              {\n                \"label\": \"JKS\",\n                \"value\": \"JKS\"\n              }\n            ],\n            \"type\": \"STRING\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"password\",\n            \"description\": \"Configurable Property to set keystore password (default set to password)\",\n            \"id\": \"keystore.password\",\n            \"isRequired\": true,\n            \"name\": \"Keystore password\",\n            \"type\": \"PASSWORD\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"200\",\n            \"description\": \"Maximum number of items that will be included in a single request to the server.\",\n            \"id\": \"max.request.items\",\n            \"isRequired\": true,\n            \"name\": \"Max request items\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"cardinality\": 0,\n            \"defaultValue\": \"BROWSE_PATH\",\n            \"description\": \"The format to be used for channel name for subtree subscriptions.     If set to BROWSE_PATH, the channel name will contain the browse path of the source node relative to the subscription root.     If set to NODE_ID, the name will contain the node id of the source node.\",\n            \"id\": \"subtree.subscription.name.format\",\n            \"isRequired\": true,\n            \"name\": \"Subtree subscription events channel name format\",\n            \"option\": [\n              {\n                \"label\": \"BROWSE_PATH\",\n                \"value\": \"BROWSE_PATH\"\n              },\n              {\n                \"label\": \"NODE_ID\",\n                \"value\": \"NODE_ID\"\n              }\n            ],\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"OPC-UA Driver\",\n        \"id\": \"org.eclipse.kura.driver.opcua\",\n        \"name\": \"OpcUaDriver\"\n      },\n      \"pid\": \"org.eclipse.kura.driver.opcua\"\n    }\n  ],\n  \"wireComponentDefinitions\": [\n    {\n      \"componentOCD\": [\n        {\n          \"cardinality\": 0,\n          \"defaultValue\": \"records[0].TIMER !== null && records[0].TIMER.getValue() > 10 && records[0]['TIMER'].getValue() < 30;\\n\",\n          \"description\": \"The boolean expression to be evaluated by this component when a wire envelope is              received.\",\n          \"id\": \"condition\",\n          \"isRequired\": true,\n          \"name\": \"condition\",\n          \"type\": \"STRING\"\n        }\n      ],\n      \"defaultInputPorts\": 1,\n      \"defaultOutputPorts\": 2,\n      \"factoryPid\": \"org.eclipse.kura.wire.Conditional\",\n      \"inputPortNames\": {\n        \"0\": \"if\"\n      },\n      \"maxInputPorts\": 1,\n      \"maxOutputPorts\": 2,\n      \"minInputPorts\": 1,\n      \"minOutputPorts\": 2,\n      \"outputPortNames\": {\n        \"0\": \"then\",\n        \"1\": \"else\"\n      }\n    }\n  ]\n}\n
                      "},{"location":"kura-wires/wire-service-rest-v1/#wire-graph-snapshot-example","title":"Wire Graph snapshot example","text":"
                      {\n  \"configs\": [\n    {\n      \"pid\": \"org.eclipse.kura.wire.graph.WireGraphService\",\n      \"properties\": {\n        \"WireGraph\": {\n          \"type\": \"STRING\",\n          \"value\": \"{\\\"components\\\":[{\\\"pid\\\":\\\"timer\\\",\\\"inputPortCount\\\":0,\\\"outputPortCount\\\":1,\\\"renderingProperties\\\":{\\\"position\\\":{\\\"x\\\":-300,\\\"y\\\":-20},\\\"inputPortNames\\\":{},\\\"outputPortNames\\\":{}}},{\\\"pid\\\":\\\"logger\\\",\\\"inputPortCount\\\":1,\\\"outputPortCount\\\":0,\\\"renderingProperties\\\":{\\\"position\\\":{\\\"x\\\":-100,\\\"y\\\":-20},\\\"inputPortNames\\\":{},\\\"outputPortNames\\\":{}}}],\\\"wires\\\":[{\\\"emitter\\\":\\\"timer\\\",\\\"emitterPort\\\":0,\\\"receiver\\\":\\\"logger\\\",\\\"receiverPort\\\":0}]}\"\n        }\n      }\n    },\n    {\n      \"pid\": \"timer\",\n      \"properties\": {\n        \"componentDescription\": {\n          \"type\": \"STRING\",\n          \"value\": \"A wire component that fires a ticking event on every configured interval\"\n        },\n        \"componentId\": {\n          \"type\": \"STRING\",\n          \"value\": \"timer\"\n        },\n        \"componentName\": {\n          \"type\": \"STRING\",\n          \"value\": \"Timer\"\n        },\n        \"cron.interval\": {\n          \"type\": \"STRING\",\n          \"value\": \"0/10 * * * * ?\"\n        },\n        \"emitter.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 1\n        },\n        \"factoryComponent\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": false\n        },\n        \"factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"timer\"\n        },\n        \"receiver.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"service.factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Timer-1642493602000-13\"\n        },\n        \"simple.custom.first.tick.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"simple.first.tick.policy\": {\n          \"type\": \"STRING\",\n          \"value\": \"DEFAULT\"\n        },\n        \"simple.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 10\n        },\n        \"simple.time.unit\": {\n          \"type\": \"STRING\",\n          \"value\": \"SECONDS\"\n        },\n        \"type\": {\n          \"type\": \"STRING\",\n          \"value\": \"SIMPLE\"\n        }\n      }\n    },\n    {\n      \"pid\": \"logger\",\n      \"properties\": {\n        \"componentDescription\": {\n          \"type\": \"STRING\",\n          \"value\": \"A wire component which logs data as received from upstream connected Wire Components\"\n        },\n        \"componentId\": {\n          \"type\": \"STRING\",\n          \"value\": \"logger\"\n        },\n        \"componentName\": {\n          \"type\": \"STRING\",\n          \"value\": \"Logger\"\n        },\n        \"emitter.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"factoryComponent\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": false\n        },\n        \"factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"logger\"\n        },\n        \"log.verbosity\": {\n          \"type\": \"STRING\",\n          \"value\": \"QUIET\"\n        },\n        \"receiver.port.count\": {\n          \"type\": \"INTEGER\",\n          \"value\": 1\n        },\n        \"service.factoryPid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.wire.Logger-1642493602046-14\"\n        }\n      }\n    }\n  ]\n}\n
                      "},{"location":"kura-wires/wires-mqtt-namespace/","title":"Wires MQTT Namespace","text":"

                      The CloudPublisher is a WireComponent that converts a WireEnvelope in a KuraPayload and publishes it over MQTT. Each WireRecord in a WireEnvelope is trivially converted to a KuraPayload and published on a configurable semantic data or control topic. During this process, the emitter PID of the WireEnvelope is discarded.

                      The CloudPublisher is agnostic with respect to the contents of the WireEnvelope. It does not know for example if a WireEnvelope contains data readings emitted from a WireAsset. On the other hand, WireAssetS are first-class citizens of a Wire graph and the source of the information which is processed by the downstream Wire components. Eventually, the WireEnvelope representing the output of the processing is connected to a CloudPublisher and published to a Cloud platform.

                      In the simplest case, a WireAsset is directly connected to a CloudPublisher and it would be useful to publish the telemetry data under a well-known topic namespace, for example the following full topic:

                      <accountName>/<deviceID>/W1/A1/<assetName>\n

                      In this case ${assetName} matches the emitterPID of the WireEnvelope emitted by the WireAsset and received by a CloudPublisher.

                      Interested applications can then subscribe to (or query a message datastore for):

                      • <accountName>/<deviceID>/W1/# for all Wires (W) topics
                      • <accountName>/<deviceID>/W1/A1/# for all WireAsset (W/A) topics
                      • <accountName>/<deviceID>/W1/A1/<assetName> for all topics for a specific WireAsset

                      In a more complex scenario there might be filters between the WireAsset \u201csource\u201d and the CloudPublisher \u201csink\u201d and the emitterPID of the WireEnvelope received by the publisher no longer matches the emitterPID of the WireEnvelope emitted by the WireAsset. However the published data still represents \u201cAsset\u201d (filtered) data and should be published under the topic above.

                      The Kura Wires model haven\u2019t the notion of source of an WireEnvelope since a given WireEnvelope instance does not move across the graph but only from one WireEmitter to downstream WireReceiverS that are free to emit something semantically different.

                      To overcome this issue:

                      • The CloudPublisher can be configured with a \u201csemantic topic template\u201d like W1/A1/$assetName where tokens prefixed with $ will be expanded to the value of a property with the same name in a WireEnvelope\u2019s WireRecord.
                      • Into the WireAsset, it is possible to add an assetName property to the WireEnvelope\u2019s WireRecord.
                      "},{"location":"kura-wires/multiport-wire-components/join-component/","title":"Join Component","text":"

                      The Join Component is a Multiport-enabled component that merges into a single Wire Envelope the properties contained in the envelopes received in the input ports. It is provided by default in every Kura installation.

                      In the image above a simple usage example of the Join component: two timers simulate separate paths in the graph and the envelopes received by the Conditional component are then merged into a single Wire Envelope that is then received by the logger component.

                      The behaviour of the Join component is specified by the barrier property.

                      "},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/","title":"Mathematical Components Example","text":"

                      Mathematical Wire components can be installed from the Eclipse Marketplace. For these examples, the Wire Math Multiport Components DP will be used, but other more specific operators can be found on the marketplace (like trigonometric functions).

                      The following Multiport-enabled Mathematical examples are provided:

                      • Sum
                      • Difference
                      • Multiplication
                      • Division
                      "},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#sum","title":"Sum","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#difference","title":"Difference","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#multiplication","title":"Multiplication","text":""},{"location":"kura-wires/multiport-wire-components/mathematical-components-example/#division","title":"Division","text":""},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/","title":"Multiport Wire Components","text":"

                      In order to allow a better routing of data through the Wire Graph, Kura introduces a new class of Wire components named Multiport Wire Components.

                      With the addition of this new functionality, a compatible component instance can be defined with an arbitrary number of input and output ports.

                      In the example provided, we have two components in the Wire Composer:

                      • the Conditional component that implements the if-then-else logic
                      • the Join component that joins into a single Wire the data processed by two separate branches of a graph

                      Those components are available in every default installation of Kura.

                      In order to show the potentialities of the new APIs, in the Eclipse Kura Marketplace and in the Kura downloads page, are available few more multiport-enabled components for Mathematical processing.

                      "},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#convert-a-component-to-a-multiport-component","title":"Convert a Component to a Multiport Component","text":""},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#component-configuration-changes","title":"Component Configuration Changes","text":"

                      The following properties need to be specified in the component configuration:

                      • input.cardinality.minimum: an integer that specifies the minimum number of input ports that the component can be configured to manage.
                      • input.cardinality.maximum: an integer that specifies the maximum number of input ports that the component can be configured to manage.
                      • input.cardinality.default: an integer that specifies the default number of input ports that the component will be created with, if not specified in a different way.
                      • output.cardinality.minimum: an integer that specifies the minimum number of output ports that the component can be configured to manage.
                      • output.cardinality.maximum: an integer that specifies the maximum number of output ports that the component can be configured to manage.
                      • output.cardinality.default: an integer that specifies the default number of output ports that the component will be created with, if not specified in a different way.
                      • input.port.names: optional mapping between input ports and friendly names
                      • output.port.names: optional mapping between output ports and friendly names

                      The component should also provide service interface org.eclipse.kura.wire.WireComponent

                      "},{"location":"kura-wires/multiport-wire-components/multiport-wire-components/#code-changes","title":"Code Changes","text":"

                      To leverage all the new Multiport functionalities, a Multiport-enabled component must use the newly introduced MultiportWireSupport APIs that provide support to get the list of Emitter and Receiver Ports, as well as to create a new Wire Envelope from a list of Wire Records.

                      For the conditional component, that has two output ports, the following code allows to get the proper Wire Support from the Wire Helper Service and to get the then and else ports to be used to push the processed envelopes.

                      this.wireSupport = (MultiportWireSupport) this.wireHelperService.newWireSupport(this);\n        final List<EmitterPort> emitterPorts = this.wireSupport.getEmitterPorts();\n        this.thenPort = emitterPorts.get(0);\n        this.elsePort = emitterPorts.get(1);\n

                      To emit the result, the code has to be adapted to use the Wire Support to create the Wire Envelope that has to be sent. Effectively, the envelope is sent to the corresponding wire invoking the emit method of the corresponding Port, as shown below.

                      final WireEnvelope outputEnvelope = this.wireSupport.createWireEnvelope(inputRecords);\n\nif ((Boolean) decision) {\n    this.thenPort.emit(outputEnvelope);\n} else {\n    this.elsePort.emit(outputEnvelope);\n}\n
                      "},{"location":"kura-wires/script-components/graalvm-conditional-component/","title":"GraalVM\u2122 Conditional Component","text":"

                      The Conditional Component is a multiport-enabled component that implements the if-then-else logic in the Wire Composer.

                      In the image above a simple usage example of the conditional component: a timer ticks and the envelope is received by the conditional component. If the timer has an even number of seconds, then it will evaluate to true and forward the received envelope to the then port: the 'verbose' logger will receive the input.

                      The choice between the two ports is performed based on a condition expressed in the component configuration as in the image below.

                      "},{"location":"kura-wires/script-components/graalvm-filter-component/","title":"GraalVM\u2122 Filter Component","text":"

                      The Filter Component provides scripting functionalities in Kura Wires using the GraalVM\u2122 JavaScript engine:

                      • The script execution is triggered when a wire envelope is received by the filter component.
                      • It is possible to access the received envelope and inspect the wire records contained in it from the script.
                      • The script can optionally emit a wire envelope containing one or more wire records for each wire envelope received.
                      • The script context is persisted across multiple executions, allowing to perform stateful computations like running a counter, performing time averages etc.
                      • A slf4j Logger is available to the script for debugging purposes.
                      • The script context is restricted to allow only Wires-related processing. Any attempt to load additional Java classes will fail.
                      • The default configuration contains an example script describing the component usage, it can be executed connecting a Timer and a Logger component.
                      "},{"location":"kura-wires/script-components/graalvm-filter-component/#usage","title":"Usage","text":"

                      The following global variables are available to the script:

                      • input: an object that represents the received wire envelope.
                      • output: an object that allows to emit wire records.
                      • logger: a slf4j logger.

                      The following utility functions are available (see Creating and emitting wire records for usage):

                      • newWireRecord(Map<String, TypedValue<?>) -> WireRecord
                      • newByteArray(int) -> byte[]
                      • newBooleanValue(boolean) -> TypedValue
                      • newByteArrayValue(byte[]) -> TypedValue
                      • newDoubleValue(number) -> TypedValue
                      • newIntegerValue(number) -> TypedValue
                      • newLongValue(number) -> TypedValue
                      • newStringValue(object) -> TypedValue

                      The following global constants expose the org.eclipse.kura.type.DataType enum variants:

                      • BOOLEAN
                      • BYTE_ARRAY
                      • DOUBLE
                      • FLOAT
                      • INTEGER
                      • LONG
                      • STRING
                      "},{"location":"kura-wires/script-components/graalvm-filter-component/#received-envelope","title":"Received envelope","text":"

                      The received envelope is represented by the input global variable and is mapped in Javascript as a WireEnvelope object.

                      GraalVM Javascript Engine allows a 1:1 mapping of Java Objects to JavaScript ones. Hence, it is possible to access the WireRecords of the envelope and the emitter pid using the methods specified in the WireEnvelope Kura API:

                      logger.info('Emitter pid is {}', input.getEmitterPid())\nvar records = input.getRecords()\n

                      The records array can be iterated to extract the WireRecord properties (reference the WireRecord Kura API):

                      for(let i=0; i<records.length; i++) {\n // WireRecord.getProperties() returns a map of String - TypedValue\n for (const [keyString, typedValue] of records[i].getProperties()) {\n logger.info('The {}-th record contains:'\\, String(i))\n logger.info('{}={}\\n', keyString, typedValue.getValue())\n }\n}\n

                      As in the example above, wireRecord.getProperties returns a map of String, TypedValue. Refer to the TypedValue Kura API for the accessible public methods list.

                      "},{"location":"kura-wires/script-components/graalvm-filter-component/#creating-and-emitting-wire-records","title":"Creating and emitting wire records","text":"

                      New mutable WireRecord instances can be created using the newWireRecord(Map<String, TypedValue<?>) function. The properties of a mutable WireRecord can be modified by setting Javascript object properties. The properties of a WireRecord object must be instances of the TypedValue class created using the new<type>Value() family of functions. Setting different kind of objects as properties of a WireRecord will result in an exception.

                      The output global variable is an object that can be used for emitting WireRecords. This object contains a list of WireRecords that will be emitted when the script execution finishes if no exceptions are thrown. The following code is an example of how to emit a list containing a single WireRecord:

                      var output = new Array()\nvar outputMap = new Object()\n\nvar byteArray = newByteArray(4)\nbyteArray[0] = 1\nbyteArray[1] = 2\nbyteArray[2] = 0xaa\nbyteArray[3] = 0xbb\n\noutputMap['example.integer'] = newIntegerValue(10)\noutputMap['example.long'] = newLongValue(100)\noutputMap['example.float'] = newFloatValue(1.014)\noutputMap['example.double'] = newDoubleValue(10.12)\noutputMap['example.boolean'] = newBooleanValue(true)\noutputMap['example.string'] = newStringValue('Hello World!')\noutputMap['example.byte.array'] = newByteArrayValue(byteArray)\n\noutput[0] = newWireRecord(outputMap)\n
                      "},{"location":"kura-wires/script-components/graalvm-filter-component/#script-context","title":"Script context","text":"

                      The script.context.drop option allows to reset the script context. If set to true the script context will be dropped every time the component configuration is updated, resetting the value of any persisted variable.

                      In the example below, with script.context.drop=false the following script will preserve the value of counter across executions. Setting script.context.drop=true will cause counter to be undefined every time the component is triggered.

                      counter = typeof(counter) === 'undefined'\n ? 0 // counter is undefined, initialise it to zero\n : counter; // counter is already defined, keep the previous value\n
                      "},{"location":"kura-wires/script-components/introduction/","title":"Introduction to the Script Components","text":"

                      The Script Components allow for performing more complex operations on the received Wire Envelopes using a JavaScript Engine.

                      Depending if the target device is running on Java 8 or Java 17, there are several components to choose from.

                      For devices running a JRE with Nashorn JS Engine (Java < 15), a Script Filter and a Conditional Component are provided:

                      • Nashorn-based Script Filter
                      • Nashorn-based Conditional Component

                      The above components will run only on Java < 15 since the Nashorn dependency is not included in the DP. The two components are available in the Eclipse Marketplace as two separate entries. These components are deprecated as of Kura version 5.3.

                      The following components instead have the GraalVM\u2122 JavaScript Engine included in the DP and therefore do not require a JRE with Nashorn JS Engine:

                      • GraalVM\u2122 Filter Component
                      • GraalVM\u2122 Conditional Component

                      The input scripts for these components are not compatible with the Nashorn implementations Script Filter and Conditional Component. Both components are shipped as a single DP named org.eclipse.kura.wire.script.tools. Since the JS engine dependency is shipped along with the DP, these components will work on both Java 8 and Java 17 devices but the DP is bigger in size (~18,6 MB).

                      "},{"location":"kura-wires/script-components/nashorn-conditional-component/","title":"Nashorn Conditional Component","text":"

                      Warning

                      This component is deprecated as of Kura version 5.3 since no more available on Java 17.

                      The Conditional component is a multiport-enabled component that implements the if-then-else logic in the Wire Composer. It is provided by default in every Kura installation.

                      In the image above a simple usage example of the Conditional component: a timer ticks and the envelope is received by the Conditional component. The message is then processed and can be sent downstream to the logger component (then port) or to a publisher (else port).

                      The choice between the two ports is performed based on a condition expressed in the Conditional component configuration.

                      "},{"location":"kura-wires/script-components/nashorn-script-filter/","title":"Nashorn Script Filter","text":"

                      Warning

                      This component is deprecated as of Kura version 5.3 since no more available on Java 17.

                      The Script Filter Component provides scripting functionalities in Kura Wires using the Nashorn Javascript engine:

                      • The script execution is triggered when a wire envelope is received by the filter component.
                      • It is possible to access to the received envelope and inspect the wire records contained in it form the script.
                      • The script can optionally emit a wire envelope containing one or more wire records for each wire envelope received.
                      • The script context is persisted across multiple executions, allowing to perform stateful computations like running a counter, performing time averages etc.
                      • A slf4j Logger is available to the script for debug purposes.
                      • The script context is restricted in order to allow only Wires related processing. Any attempt to load additional Java classes will fail.
                      • The default configuration contains an example script describing the component usage, it can be executed connecting a Timer and a Logger component.
                      "},{"location":"kura-wires/script-components/nashorn-script-filter/#usage","title":"Usage","text":"

                      The following global variables are available to the script:

                      • input: an object that represents the received wire envelope.
                      • output: an object that allows to emit wire records.
                      • logger: a slf4j logger

                      The following utility functions are available (see Creating and emitting wire records for usage):

                      • newWireRecord(void) -> WireRecordWrapper
                      • newByteArray(void) -> byte[]
                      • newBooleanValue(boolean) -> TypedValue
                      • newByteArrayValue(byte[]) -> TypedValue
                      • newDoubleValue(number) -> TypedValue
                      • newFloatValue(number) -> TypedValue
                      • newIntegerValue(number) -> TypedValue
                      • newLongValue(number) -> TypedValue
                      • newStringValue(object) -> TypedValue

                      The following global constants expose the org.eclipse.kura.type.DataType enum variants:

                      • BOOLEAN
                      • BYTE_ARRAY
                      • DOUBLE
                      • FLOAT
                      • INTEGER
                      • LONG
                      • STRING
                      "},{"location":"kura-wires/script-components/nashorn-script-filter/#received-envelope","title":"Received envelope","text":"

                      The received envelope is represented by the input global variable and it has the following properties:

                      • emitterPid: the emitter pid of the received envelope as a String.
                      • records: an immutable array that represents the Wire Records contained in the Wire Envelope.

                      Each element of the records array is an immutable object that represents a received wire record. Wire record properties are directly mapped to Javascript object properties, and are instances of the org.eclipse.kura.type.TypedValue class. Each Wire Record property has the following methods available:

                      • getType(void) -> DataType: Returns the type of the value, as a DataType enum variant. Can be matched against the data type constants described above.
                      • getValue(void) -> Object: Returns the actual value.

                      The javascript objects referred as WireRecords in this guide are not instances of the org.eclipse.kura.wire.WireRecord class, but are wrappers that map WireRecord properties into javascript properties. The following code is a simple example script that show how to use the filter:

                      // get the first record from the envelope\nvar record = input.records[0]\n// let's assume it contains the LED boolean property and the TEMPERATURE double property\nrecord.LED1.getType() === BOOLEAN // evaluates to true\nif (record.LED1.getValue()) {\n    // LED1 is on\n}\nrecord.LED1.getType() === DOUBLE // evaluates to true\nif (record.TEMPERATURE.getValue() > 50) {\n    // temperature is high, do something\n}\n
                      "},{"location":"kura-wires/script-components/nashorn-script-filter/#creating-and-emitting-wire-records","title":"Creating and emitting wire records","text":"

                      New mutable WireRecord instances can be created using the newWireRecord(void) -> WireRecordWrapper function. The properties of a mutable WireRecord can be modified by setting Javascript object properties. The properties of a WireRecord object must be instances of the TypedValue class created using the new<type>Value() family of functions. Setting different kind of objects as properties of a WireRecord will result in an exception.

                      The output global variable is an object that can be used for emitting WireRecords. This object contains a list of WireRecords that will be emitted when the script execution finishes, if no exceptions are thrown. New records can be added to the list using the add(WireRecordWrapper) function. It is also possible to emit records contained in the received WireEnvelope.

                      The script filter will emit a wire envelope only if the WireRecord list is not empty when the script execution completes. The following code is an example about how to emit a value:

                      var record = newWireRecord()\n\nvar byteArray = newByteArray()\nbyteArray[0] = 1\nbyteArray[1] = 2\nbyteArray[2] = 0xaa\nbyteArray[3] = 0xbb\n\nrecord.LED1 = newBooleanValue(true)\nrecord.foo = newStringValue('bar')\nrecord['myprop'] = newDoubleValue(123.456)\nrecord['example.long'] = newLongValue(100)\nrecord['example.float'] = newFloatValue(1.014)\nrecord['example.byte.array'] = newByteArrayValue(byteArray)\n\noutput.add(record)\n
                      "},{"location":"kura-wires/script-components/nashorn-script-filter/#script-context","title":"Script context","text":"

                      The script.context.drop option allows to reset the script context. If set to true the script context will be dropped every time the component configuration is updated, resetting the value of any persisted variable.

                      In the example below, with script.context.drop=false the following script will preserve the value of counter across executions. Setting script.context.drop=true will cause counter to be undefined every time the component is triggered.

                      counter = typeof(counter) === 'undefined'\n ? 0 // counter is undefined, initialise it to zero\n : counter; // counter is already defined, keep the previous value\n
                      "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/","title":"AI Wire Component","text":"

                      The component allows interacting with an InferenceEngineService to perform machine learning-related operations. For boards that are not explicitly made for AI, the component can be installed from the Eclipse Marketplace at this link.

                      An InferenceEngineService is a Kura service that implements a simple API to interface with an Inference Engine. The Inference Engine allows to perform inference on trained Artificial Intelligence models commonly described by a file and some configuration for explaining its input and outputs. An example of Inference Engine implementation is the Nvidia\u2122 Triton Server inference engine.

                      In a normal machine learning flow, the input is preprocessed before it is given to the machine learning algorithm, and the result is processed again to be adapted to the rest of the pipeline.

                      Once these models are loaded in the engine, the AI wire component allows to specify the name of the models that are used in the pre-processing, infer, and post-processing steps. Only the infer model name is mandatory so that it is possible to just use the strictly necessary steps in case the pre/post-processing is performed directly by the infer step.

                      "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#models-input-and-output-formats","title":"Models Input and Output formats","text":"

                      The AI wire component takes a WireEnvelope as an input, it processes its records and feeds them to the specified preprocessing or inference model. The outputs of the inference or the post-processing step are then reconverted into a wire record. This section explains the inputs and output formats that the wire component is expecting. Not specifying the models according to this contract will result in a non-functioning inference.

                      The 3 inference steps are applied on each WireRecord contained in the input WireEnvelope.

                      The inputs and outputs will have assigned the corresponding Kura DataType, which can be one of:

                      • BOOLEAN
                      • DOUBLE
                      • FLOAT
                      • INTEGER
                      • LONG
                      • STRING
                      • BYTE_ARRAY

                      Reference to Introduction for the data types that are allowed to flow through the wires.

                      The models that manage the input and the output must expect a list of inputs such that:

                      • each input corresponds to an entry of the WireRecord properties
                      • the entry key will become the input name (e.g. in the case of an asset, the channel name becomes the tensor name)
                      • input shape will be [1]

                      In the following, two example configurations for Triton Inference Engine models are provided. A complete usage example that implements an Anomaly Detector using a RaspberryPi SenseHat is provided in the Kura examples repository.

                      "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#input-specification-example","title":"Input Specification Example","text":"

                      Following, an example of a model configuration for the Nvidia\u2122 Triton Inference Engine. It expects the input from the WireEnvelope that contains a record with properties:

                      • ACCELERATION of type Float
                      • CHANNEL_0 of type Integer
                      • STREAM of type byte[]
                      • GYRO of type Boolean

                      This record can be generated from an asset with channel names as above. The output will be a single tensor of type Float, of shape 1x13, and name OUT_PRE.

                      Note that each input will have shape 1.

                      name: \"preprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"ACCELERATION\"\n    data_type: FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"CHANNEL_0\"\n    data_type: INT32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"STREAM\"\n    data_type: BYTES\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"GYRO\"\n    data_type: BOOL\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"OUT_PRE\"\n    data_type: FP32\n    dims: [ 13 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
                      "},{"location":"kura-wires/single-port-wire-components/ai-wire-component/#output-specification-example","title":"Output Specification Example","text":"

                      Following, an example of a Nvidia\u2122 Triton Inference Engine configuration that takes input IN_POST and produces outputs that will be mapped to a WireRecord with the properties as follows: - RESULT0 of type Boolean - RESULT1 of type Integer - RESULT2 of type byte[] - RESULT3 of type Float

                      Note that each output will have shape 1.

                      name: \"postprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"IN_POST\"\n    data_type: FP32\n    dims: [ 1, 5 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT0\"\n    data_type: BOOL\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT1\"\n    data_type: INT32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT2\"\n    data_type: BYTES\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"RESULT3\"\n    data_type: FP32\n    dims: [ 1 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
                      "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/","title":"Wire Record Store and Wire Record Query","text":"

                      This tutorial will present how to use Wire Record Store and Wire Record Query components and provide an example based on the OPC-UA simulated server already used in OPC-UA Application.

                      The Wire Record Store component allows the wire graphs to interact with a persistend Wire Record store implementation, for example a SQL database. It stores in a user-defined collection all the envelopes received by the component.

                      The Wire Record Query component, instead, can run a custom query on the attached store and emit the result on the wire.

                      Note

                      The Wire Record Store and Wire Record Query components have been introduced in Kura 5.3.0 as a replacement of the Db Store and Db Filter, that have been deprecated.

                      The reason of the deprecation is the fact that Db Store and Db Filter only support databases that provide a JDBC interface. Moreover, the old Db Store interacts with the database using a set of hardcoded SQL queries. This fact makes it difficult to use it with different databases since the syntax of the queries is usually database-specific.

                      The new components use the org.eclipse.kura.wire.store.provider.WireRecordStoreProvider and org.eclipse.kura.wire.store.provider.QueryableWireRecordStoreProvider APIs introduced in 5.3.0 allowing to use them with generic data store implementations.

                      Please note that Wire Record Query component is not portable by nature, since it allows to execute an arbitrary user defined query. The new APIs allows to use it with non-JDBC data stores.

                      The Wire Record Store component can be configured as follows:

                      • Record Collection Name: The name of the record collection that should be used. The implementation of the collection depends on the Wire Record Store implementation, if it is a SQL database, the collection will likely be a table.
                      • Maximum Record Collection Size: The maximum number of records that is possible to store in the collection.
                      • Cleanup Records Keep: The number of records in the collection to keep while performing a cleanup operation (if set to 0 all the records will be deleted). The cleanup operation is performed when a new record is inserted and the current size of the record collection is greater than the configured Maximum Record Collection Size.
                      • WireRecordStoreProvider Target Filter : Specifies, as an OSGi target filter, the pid of the of the Wire Record Store instance to be used.

                      The Wire Record Query component can be configured as follows:

                      • Query: Query to be executed. The query syntax depends on the Queryable Wire Record Store implementation.
                      • Cache Expiration Interval (Seconds): This component is capable of maintaining a cache of the records produced by the last query execution and emitting its contents on the Wire until it expires. This value specifies the cache validity interval in seconds. When cache expires, it will cause a new query execution. The query will be executed for every trigger received if the value is set to 0.
                      • QueryableWireRecordStoreProvider Target Filter : Specifies, as an OSGi target filter, the pid of the of the Queryable Wire Record Store instance to be used.
                      • Emit On Empty Result : Defines the behavior of the component if the result of the performed query is empty. If set to true, an empty envelope will be emitted in this case, if set to false no envelopes will be emitted.

                      The following procedure will create a wire graph that collects data from a simulated OPC-UA Server, stores it in a table in the database, using the Wire Record Store component, and publishes it in the cloud platform. Moreover, the Wire Record Query component is used to read from the database and write data to the OPC-UA Server based on the values read.

                      "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/#configure-opc-ua-server-simulator","title":"Configure OPC-UA server simulator","text":"
                      1. Download the OPC-UA server simulator bundle and install it on your Kura instance. It will create a simulated OPC-UA server that exposes some sensors (light, temperature and water sensor) and some actuators (buzzer, led and fan).
                      2. In the Kura Administrative Web Interface, select \u201cOPCUA Server demo\u201d in \u201cServices\u201d and set server.port to 1234. Click the Apply button. This will start an OPC-UA\u200b server on port 1234.
                      "},{"location":"kura-wires/single-port-wire-components/db-store-and-filter/#configure-wires-opc-ua-application","title":"Configure Wires OPC-UA application","text":"
                      1. Install the OPC-UA Driver from Eclipse Kura Marketplace.
                      2. Use the local Kura Administrative Web Interface to create a new OPC-UA driver instance:
                        • Select Drivers and Assets, click the New Driver button
                        • Select org.eclipse.kura.driver.opcua, type in a name, and click Apply: a new service will show up under Services.
                      3. Configure the new service as follows:

                        • endpoint.ip: localhost
                        • endpoint.port: 1234
                        • server.name: leave blank
                      4. Click on Wires under System

                      5. Add a new Timer component and configure the interval at which the OPC-UA server will be sampled
                      6. Add a new Asset with the previously added OPC-UA Driver
                      7. Configure the new OPC-UA asset, adding new Channels as shown in the following image. Make sure that all the channels are set to READ.

                      8. Add a new Wire Record Store named DBStore component and configure it as follows:

                        • Record Collection Name: WR_data
                        • Maximum Record Collection Size: 10000
                        • Cleanup Records Keep: 0
                        • WireRecordStoreProvider Target Filter : the Wire Record Store Provider pid to be used
                      9. Add a new Publisher component and configure the chosen cloud platform stack in cloud.service.pid option
                      10. Add Logger component
                      11. Add another instance of Timer
                      12. Add a new Wire Record Query component named DBFilter component and configure it as follows. The query will get the values from the light sensor and if they are less than 200, the fan is activated.
                        • Query: SELECT (CASE WHEN \u201clight\u201d < 200 THEN 1 ELSE 0 END) AS \u201cled\u201d FROM \u201cWR_data\u201d ORDER BY TIMESTAMP DESC LIMIT 1;
                        • Cache Expiration Interval (Seconds): 0
                        • QueryableWireRecordStoreProvider Target Filter : the Queryable Wire Record Store Provider pid to be used
                      13. Add another Asset with the OPC-UA Driver, configured as shown in the following image. Be sure that all the channels are set to WRITE.

                        Note

                        Be aware that the Query syntax can vary accordingly to the dialect used by the database. For example, the MySQL dialect doesn't allow to surround the table or columns names with double-quotes. In the H2DB, this is mandatory instead.

                      14. Connect the components as shown in the following image, then click on \u201cApply\u201d and check the logs and the cloud platform that the data is correctly published.

                      "},{"location":"kura-wires/single-port-wire-components/fifo-component/","title":"FIFO Component","text":"

                      This page describes the usage of the FIFO component in Wires.

                      The current Wires threading model allows any component to perform potentially blocking operations when a wire envelope is received.

                      The fact that the wire envelopes are delivered synchronously implies that if a wire component performs blocking operations, other components in the same subgraph might be blocked as well, introducing delays in the processing of the graph.

                      The FIFO component can be used for decoupling blocking or slow wire components from other parts of the graph that cannot tolerate delays.

                      In the graph above, the NODELAY component cannot tolerate potential delays introduced by the DB component, adding a FIFO component allows to decouple the two components.

                      This component implements a FIFO queue that operates as follows:

                      • The received envelopes are added to the queue. Adding an envelope to the queue is usually (see below) a non-blocking operation.
                      • A dedicated thread pops the envelopes from the queue and delivers them to downstream components.

                      In this way, the threads running the upstream components are not affected by blocking operations performed by the downstream components.

                      In the example above there will be two threads that manage the processing of the graph:

                      1. A thread from the TIMER Quartz scheduler pool handles the processing for the NODELAY component and submits the envelopes produced by it to the queue of the FIFO component.

                      2. A second thread introduced by the FIFO component pops the received envelopes from the queue and dispatches them to the DB component, performing the processing required by it.

                      In this way, the NODELAY and DB components are decoupled because they are managed by different threads.

                      "},{"location":"kura-wires/single-port-wire-components/fifo-component/#configuration","title":"Configuration","text":"

                      The FIFO component configuration is composed of the following properties:

                      • queue.capacity: The size of the queue in terms of the number of storable wire envelopes.

                      • discard.envelopes : Configures the behavior of the component in case of a full queue.

                      • If set to true, envelopes received when the queue is full will be dropped. In this mode, submitting an envelope to the queue is always a non-blocking operation. It should be used if occasionally losing wire envelopes is acceptable for the application, but introducing delays for upstream components is not.

                      • If set to false, adding an envelope when the queue is full will block the submitting thread until there is space in the queue. Submitting an envelope to the FIFO component can be a blocking operation if the queue is full. This mode should be used if dropping wire envelopes is unacceptable for upstream components.

                      The probability of dropping envelopes in discard.envelopes=true mode or the probability of blocking upstream components in discard.envelopes=false mode can be controlled by setting a proper queue size.

                      "},{"location":"kura-wires/usage-examples/eddystone-driver-application/","title":"Eddystone\u2122 Driver Application with RuuviTag+","text":"

                      As presented in Eddystone Driver, Kura provides a specific driver that can be used to listen for Eddystone\u2122 beacons.

                      This tutorial will explain how to configure an Eddystone\u2122 driver and put it into a Wire graph that retrieves data from a RuuviTag+. For further information about RuuviTag see here.

                      "},{"location":"kura-wires/usage-examples/eddystone-driver-application/#configure-kura-wires-eddystone-application","title":"Configure Kura Wires Eddystone application","text":"
                      1. Install the Eddystone\u2122 driver from the Eclipse Kura Marketplace.

                      2. On the Kura Web Interface, instantiate an Eddystone\u2122 Driver:

                        • Under System, select Drivers and Assets and click on the New Driver button.
                        • Select org.eclipse.kura.driver.eddystone as Driver Factory, type a name in to Driver Name and click Apply: a new driver will be instantiated and shown up under the Drivers and Assets tab.
                        • Configure the new driver setting the bluetooth interface name (e.g. hci0).
                      3. From the Drivers and Assets tab, add a new asset bound to the Eddystone\u2122 driver:

                        • Click on the New Asset button and fill the form with the Asset Name and selecting the driver created in step 2. as Driver Name. Click Apply and a new asset will be listed under the Eddystone\u2122 driver.

                        • Click on the new asset and configure it, adding the channels. Each channel represents a type of frame the Driver is interested to. Please note that in the above picture two channels are created: one for the UID type and the second for the URL. In this example only the URL will be used.
                        • Check the listen checkbox for both channels.
                        • Click \"Apply\".
                      4. Click on Wires under System.

                      5. Add a new Asset with the previously added Eddystone asset.

                      6. Add a new Javascript Filter component. The filter will be configured to parse the URL frames coming from the RuuviTag+ and extract the environmental data from the on-board sensors. In the script window write the following code:

                        function toHexString(str) {\n    var hex = '';\n    for ( i = 0; i < str.length; i++ ) {\n        var hexTemp = str.charCodeAt(i).toString(16)\n        hex += (hexTemp.length==2?hexTemp:'0'+hexTemp);\n}\nreturn hex;\n};\n\nfunction decodeValues(rawSensors) {\n    var rawSensorsDecoded = Base64.decode(rawSensors)\n    logger.info(toHexString(rawSensorsDecoded))\n    var sensorsValues = new Array();\n    // Data Format Definition (4)\n    var format = parseInt(rawSensorsDecoded[0].charCodeAt(0));\n    if (format == 4) {\n        sensorsValues.push(format)\n        // Humidity\n        sensorsValues.push(rawSensorsDecoded[1].charCodeAt(0) * 0.5)\n        // Temperature\n        sensorsValues.push(rawSensorsDecoded[2].charCodeAt(0) & 0x7f)\n        // Pressure\n        sensorsValues.push(parseInt(rawSensorsDecoded[4].charCodeAt(0) << 8 )+ parseInt(rawSensorsDecoded[5].charCodeAt(0) & 0xff) + 50000)\n        // Random id of tag\n        sensorsValues.push(rawSensorsDecoded[6].charCodeAt(0))\n    }\n    return sensorsValues;\n};\n\nload(\"https://gist.githubusercontent.com/jarus/948005/raw/524bea3b4e0b74c06c9cfd2a8e54429dda1918fe/base64.js\")\nvar record = input.records[0]\nif (record.URLEddystone != null) {\n    var values = record.URLEddystone.getValue().split(\";\")\n\n    if (values.length == 5 && values[1].split(\"#\").length == 2) {\n        var outRecord = newWireRecord()\n        var sensorsValues = decodeValues(values[1].split(\"#\")[1])\n        if (sensorsValues.length == 5) {\n            outRecord.format = newIntegerValue(sensorsValues[0])\n            outRecord.humidity = newDoubleValue(sensorsValues[1])\n            outRecord.temperature = newDoubleValue(sensorsValues[2])\n            outRecord.pressure = newIntegerValue(sensorsValues[3])\n            outRecord.id = newIntegerValue(sensorsValues[4])\n        }\n        outRecord.txPower = newIntegerValue(parseInt(values[2]))\n        outRecord.rssi = newIntegerValue(parseInt(values[3]))\n        outRecord.distance = newDoubleValue(parseFloat(values[4]))\n\n        output.add(outRecord)\n    }\n}\n
                      7. Add Logger component and set the log.verbosity to VERBOSE.

                      8. Connect the Asset to the Filter and this to the Logger.

                      9. Click on Apply and check on the logs that the environmental data are correctly logged.

                        INFO  o.e.k.w.s.f.p.ScriptFilter - 04541a00c35078\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.ScriptFilter-1537884418687-2\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     txPower : -7\nINFO  o.e.k.i.w.l.Logger -     rssi : -55\nINFO  o.e.k.i.w.l.Logger -     distance : 251.18864315095797\nINFO  o.e.k.i.w.l.Logger -     format : 4\nINFO  o.e.k.i.w.l.Logger -     temperature : 26.0\nINFO  o.e.k.i.w.l.Logger -     humidity : 42.0\nINFO  o.e.k.i.w.l.Logger -     pressure : 100000\nINFO  o.e.k.i.w.l.Logger -     id : 120\nINFO  o.e.k.i.w.l.Logger -\nINFO  o.e.k.w.s.f.p.ScriptFilter - 04401a00c35078\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.ScriptFilter-1537884418687-2\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     txPower : -7\nINFO  o.e.k.i.w.l.Logger -     rssi : -39\nINFO  o.e.k.i.w.l.Logger -     distance : 39.810717055349734\nINFO  o.e.k.i.w.l.Logger -     format : 4\nINFO  o.e.k.i.w.l.Logger -     temperature : 26.0\nINFO  o.e.k.i.w.l.Logger -     humidity : 32.0\nINFO  o.e.k.i.w.l.Logger -     pressure : 100000\nINFO  o.e.k.i.w.l.Logger -     id : 120\nINFO  o.e.k.i.w.l.Logger -\n
                      "},{"location":"kura-wires/usage-examples/gpio-driver-application/","title":"GPIO Driver Application","text":"

                      In this section a simple but effective example of the GPIO Driver on Wires will be presented. This example will implement a Wire graph that toggles a digital GPIO. A listener will be attached to an input GPIO externally connected to the first one.

                      Setup a Raspberry Pi as shown in GPIO Driver section. Add a cable from the LED contact near the red cable to pin 37 (gpio 26) on the RaspberryPi.

                      "},{"location":"kura-wires/usage-examples/gpio-driver-application/#configure-kura-wires-gpio-driver-application","title":"Configure Kura Wires GPIO Driver Application","text":"
                      1. Install the GPIO Driver from the Eclipse Kura Marketplace.

                      2. On the Kura web interface, instantiate a GPIO Driver:

                        • Under \"System\", select \"Drivers and Assets\" and click on the \"New Driver\" button.
                        • Select \"org.eclipse.kura.driver.gpio\" as \"Driver Factory\", type a name in to \"Driver Name\" and click \"Apply\": a new driver will be instantiated and shown up under the \"Drivers and Assets\" tab.
                      3. From the \"Drivers and Assets\" tab, add a new asset bound to the GPIO driver:

                        • Click on the \"New Asset\" button and fill the form with the \"Asset Name\" and selecting the driver created in step 2. as \"Driver Name\". Click \"Apply\" and a new asset will be listed under the GPIO driver.
                        • Click on the new asset and configure it, adding only one channel called LED as shown in the following picture:

                        • Click \"Apply\".
                      4. As in point 3., create a new asset as shown below:

                        • Click \"Apply\".
                      5. Click on \"Wires\" under \"System\".

                      6. Add a new \"Timer\" component and configure the interval at which the LED will be toggled.

                      7. Add a new \"Script Filter\" (it can be downloaded from the Eclipse Marketplace and configure it with the following script:

                        // create a persistent counter\ncounter = typeof(counter) === 'undefined' ? 0 : counter\ncounter++\n\n// emit the counter value in a different WireRecord\nvar counterRecord = newWireRecord()\ncounterRecord.LED = newBooleanValue(counter%2==0)\noutput.add(counterRecord)\n
                      8. Add the \"Asset\" created at point 3 and connect the \"Timer\" to the \"Filter\" and the latter to the \"Asset\".

                      9. Add the \"Asset\" created at point 4.

                      10. Add \"Logger\" component and set log.verbosity to \"VERBOSE\".

                      11. Connect the latter \"Asset\" to the \"Logger\". The resulting Wire Graph should be as below:

                      12. Click on \"Apply\". After a while, the led on the breadboard should start to blink at a rate defined by the \"Timer\" as shown below:

                        Moreover, the kura.log file should show a long sequencce of messages reporting that the value from the input gpio is changed:

                        2018-04-09 13:08:42,990 [Thread-3289] INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1523276074484-23\n2018-04-09 13:08:42,991 [Thread-3289] INFO  o.e.k.i.w.l.Logger - Record List content: \n2018-04-09 13:08:42,992 [Thread-3289] INFO  o.e.k.i.w.l.Logger -   Record content: \n2018-04-09 13:08:42,993 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     LED_Feedback : false\n2018-04-09 13:08:42,993 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     assetName : GPIOAssetFeedback\n2018-04-09 13:08:42,994 [Thread-3289] INFO  o.e.k.i.w.l.Logger -     LED_Feedback_timestamp : 1523279322990\n2018-04-09 13:08:42,994 [Thread-3289] INFO  o.e.k.i.w.l.Logger - \n2018-04-09 13:08:44,988 [Thread-3291] INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1523276074484-23\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger - Record List content: \n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -   Record content: \n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     LED_Feedback : true\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     assetName : GPIOAssetFeedback\n2018-04-09 13:08:44,989 [Thread-3291] INFO  o.e.k.i.w.l.Logger -     LED_Feedback_timestamp : 1523279324988\n
                      "},{"location":"kura-wires/usage-examples/ibeacon-driver-application/","title":"iBeacon\u2122 Driver Application","text":"

                      As presented in the iBeacon\u2122 Driver, Kura provides a specific driver that can be used to listen for iBeacons packets.

                      This tutorial will explain how to configure a Wire graph that get iBeacon\u2122 data and show them to a logger.

                      "},{"location":"kura-wires/usage-examples/ibeacon-driver-application/#configure-the-wires-ibeacontm-application","title":"Configure the Wires iBeacon\u2122 Application","text":"
                      1. Install the iBeacon driver from the Eclipse Kura Marketplace.

                      2. On the Web Interface, instantiate the iBeacon Driver:

                        • Under \"System\", select \"Drivers and Assets\" and click on the \"New Driver\" button.
                        • Select \"org.eclipse.kura.driver.ibeacon\" as \"Driver Factory\", type a name in to \"Driver Name\" and click \"Apply\": a new driver will be instantiated and shown up under the \"Drivers and Assets\" tab.
                        • Configure the new driver setting the bluetooth interface name (e.g. hci0).
                      3. From the \"Drivers and Assets\" tab, add a new asset binded to the iBeacon driver:

                        • Click on the \"New Asset\" button and fill the form with the \"Asset Name\" and selecting the driver created in step 2. as \"Driver Name\". Click \"Apply\" and a new asset will be listed under the iBeacon driver.

                        • Click on the new asset and configure it, adding a single channel that represents a listener for iBeacon\u2122 advertising packets. Check the listen checkbox for the channel.

                        • Click \"Apply\".

                      4. Click on \"Wires\" under \"System\".

                      5. Add a new \"Asset\" with the previously added iBeacon asset.

                      6. Add a new \"Javascript Filter\" component. The filter will be configured to parse the iBeacon packets and extract relevant data from it. In the script window write the following code:

                        var record = input.records[0]\nif (record.ibeacon != null) {\n    var values = record.ibeacon.getValue().split(\";\")\n\n    if (values.length == 6) {\n        var outRecord = newWireRecord()\n        outRecord.uuid = newStringValue(values[0])  \n        outRecord.txPower = newIntegerValue(parseInt(values[1]))\n        outRecord.rssi = newIntegerValue(parseInt(values[2]))\n        outRecord.major = newIntegerValue(parseInt(values[3]))\n        outRecord.minor = newIntegerValue(parseInt(values[4]))\n        outRecord.distance = newDoubleValue(parseFloat(values[5]))\n\n        output.add(outRecord)\n    }\n}\n
                      7. Add \"Logger\" component and set the log.verbosity to VERBOSE

                      8. Connect the \"Asset\" to the \"Filter\" and this to the \"Logger\".
                      9. Click on \"Apply\".

                        Using this graph, every iBeacon packet will be detected and reported to the log, as shown below. To simulate an iBeacon device, it is possible to use another gateway with the iBeacon advertiser example.

                        INFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1537886139797-15\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     assetName : iBeaconAsset\nINFO  o.e.k.i.w.l.Logger -     iBeacon : aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee;0;-38;0;0;79.43282347242814\nINFO  o.e.k.i.w.l.Logger -     iBeacon_timestamp : 1537886424085\nINFO  o.e.k.i.w.l.Logger -\nINFO  o.e.k.i.w.l.Logger - Received WireEnvelope from org.eclipse.kura.wire.WireAsset-1537886139797-15\nINFO  o.e.k.i.w.l.Logger - Record List content:\nINFO  o.e.k.i.w.l.Logger -   Record content:\nINFO  o.e.k.i.w.l.Logger -     assetName : iBeaconAsset\nINFO  o.e.k.i.w.l.Logger -     iBeacon : aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee;0;-42;0;0;125.89254117941674\nINFO  o.e.k.i.w.l.Logger -     iBeacon_timestamp : 1537886425086\nINFO  o.e.k.i.w.l.Logger -\n
                      "},{"location":"kura-wires/usage-examples/opcua-application/","title":"OPC-UA Application","text":"

                      This tutorial will describe how to collect data from an OPC-UA device and publish them on a cloud platform using Wires. The OPC-UA server device will be emulated using a bundle running on Kura.

                      "},{"location":"kura-wires/usage-examples/opcua-application/#configure-opc-ua-server-simulator","title":"Configure OPC-UA server simulator","text":"
                      1. Download the OPC-UA server simulator bundle and install it on Kura. It will create a simulated OPC-UA server that exposes some sensors (light, temperature and water sensor) and some actuators (buzzer, led and fan).
                      2. On the Kura web interface, select OPCUA Server demo in Services and set server.port to 1234. Click the Apply button. This will start an OPC-UA server on port 1234.
                      "},{"location":"kura-wires/usage-examples/opcua-application/#configure-wires-opc-ua-application","title":"Configure Wires OPC-UA application","text":"
                      1. Install the OPC-UA driver from Eclipse Kura Marketplace.
                      2. Use the local Kura Administrative Web Interface to create a new OPC-UA driver instance:
                        • Select Drivers and Assets, click the New Driver button
                        • Select org.eclipse.kura.driver.opcua, type in a name, and click Apply: a new service will show up under Services.
                      3. Configure the new service as follows:
                        • endpoint.ip: localhost
                        • endpoint.port: 1234
                        • server.name: leave blank
                      4. Click on Wires under System
                      5. Add a new Timer component and configure the interval at which the OPC-UA server will be sampled
                      6. Add a new Asset with the previously added OPC-UA driver
                      7. Configure the new OPC-UA asset, adding new Channels as shown in the following image.
                      8. Add a new Publisher component and configure the chosen cloud platform stack in cloud.service.pid option
                      9. Add a Logger component
                      10. Connect the Timer to the Asset, and the Asset to the Publisher and Logger as shown in the image below.
                      11. Click on Apply and check the logs and the cloud platform in order to verify that the data is correctly published.
                      "},{"location":"kura-wires/usage-examples/ti-sensortag-application/","title":"TI SensorTag Application","text":"

                      As presented in the Ti SensorTag Driver, Kura provides a specific driver that can be used to interact with Texas Instruments SensorTag devices.

                      This tutorial will explain how to configure a Wire graph that connects with a SensorTag, reads sensor values and publishes data to a cloud platform.

                      Warning

                      The SensorTag driver can be used only with TI SensorTags with firmware version >1.20. If your device has an older firmware, please update it.

                      "},{"location":"kura-wires/usage-examples/ti-sensortag-application/#configure-the-ti-sensortag-application","title":"Configure the TI SensorTag Application","text":"
                      1. Install the TI SensorTag driver from the Eclipse Kura Marketplace

                      2. On the Kura Administrative Web Interface, instantiate a SensorTag Driver:

                        • Under System, select Drivers and Assets and click on the New Driver button.
                        • Select org.eclipse.kura.driver.ble.sensortag as Driver Factory, type a name in Driver Name and click the Apply button: a new driver will be instantiated and listed in the page.
                        • Select the newly created Driver instance and configure the Bluetooth interface name (i.e. hci0).

                      3. In the Drivers and Assets tab add a new asset and associate it to the SensorTag driver:

                      4. Click on the New Asset button and fill the form with the Asset Name, selecting the driver created at point 2. Click Apply and a new asset will be listed.
                      5. Click on the new asset and configure it, adding the channels. Each channel represents a single sensor on the SensorTag and it can be chosen from the sensor.name menu. Fill the sensortag.address with the DB address of the SensorTag you want to connect to. The value.type should be set to double, but also the other choices are possible.
                      6. Click Apply.

                      7. Apply the following configuration for the Asset instance:

                      8. Create a Wire Graph as in the following picture.

                        Please note that the driver supports also unsolicited inputs, setting up a notification for the given channel. In this case, it is sufficient to check the listen option for the chosen channel. The Timer is not needed because the SensorTag will automatically emit the values every notification.period milliseconds.

                      "},{"location":"quality-assurance/intro/","title":"Introduction","text":""},{"location":"quality-assurance/intro/#eclipsetm-kura-qa","title":"Eclipse\u2122 Kura QA","text":"

                      Kura QA activities focused on two main areas:

                      • unit and integration tests that are executed each time the project builds and
                      • manual tests, most of which are executed in the weeks before releases.

                      More on the unit and integration tests can be read in unit testing. More about manual system testing can be read in system testing.

                      "},{"location":"quality-assurance/system-testing/","title":"System Testing","text":""},{"location":"quality-assurance/system-testing/#qa-procedure","title":"QA Procedure","text":"

                      A set of automated and manual test are performed before releasing a new Eclipse\u2122 Kura version to ensure software follows our quality standards.

                      Once a Release Candidate (RC) is tagged on its maintenance branch the QA process starts. The QA process involves a set of automated and manual tests performed on the target environment listed below. These tests are updated continuosly to follow the large amount of features added in each release. The QA process continues with new Release Candidate builds until the amount of defects in the software is reduced. When this happens the RC is promoted to final release and tagged on the maintenance branch.

                      "},{"location":"quality-assurance/system-testing/#environment","title":"Environment","text":""},{"location":"quality-assurance/system-testing/#hardware","title":"Hardware","text":"
                      • Raspberry Pi 3/4
                      • Intel Up2
                      • Nvidia Jetson Nano
                      • Docker
                      "},{"location":"quality-assurance/system-testing/#os","title":"OS","text":"
                      • Raspberry Pi OS
                      • Ubuntu 20.04
                      • Ubuntu 18.04
                      • CentOS 7
                      "},{"location":"quality-assurance/system-testing/#java","title":"Java","text":"
                      • Eclipse Adoptium Temurin\u2122 JDK 1.8
                      "},{"location":"quality-assurance/unit-testing/","title":"Unit Testing","text":""},{"location":"quality-assurance/unit-testing/#build-time-testing","title":"Build-time testing","text":"

                      Build-time testing is further divided into unit testing and integration testing.

                      Unit testing is focused on testing separate methods or groups of methods, preferably in a single class. This way it can verify the correct operation of difficult-to-reach corner cases.

                      Integration testing is a more-high-level testing that tests certain functionality on a group of connected services in an approximation of the real environment. It verifies that the services can successfully register in the environment and connect to other services as well as perform their tasks.

                      Code coverage of the develop branch can be observed in Jenkins.

                      For some tips on running the tests also check Kura GitHub Wiki.

                      "},{"location":"quality-assurance/unit-testing/#unit-testing_1","title":"Unit Testing","text":"

                      Unit tests should try to cover as many corner cases in the code as possible. Add them for (all) the new code you decide to contribute.

                      "},{"location":"quality-assurance/unit-testing/#test-location","title":"Test Location","text":"

                      Kura discourages to introduce test-only dependencies on the implementation level, so all tests are located in their own projects under test/. The proper folder to put the tests in is src/test/java.

                      "},{"location":"quality-assurance/unit-testing/#code-conventions","title":"Code Conventions","text":"
                      • Preferably use <package name>.test as the name of the test project. Add it as a module in test/pom.xml that also serves as maven artifact's parent.
                      • Only add src/main/java to build.properties' source...
                      • Make the bundle a fragment of the class-under-test's bundle so that you gain access to its internal packages.
                      • Use the same package for the test as the class under test. Subpackages are OK.
                      • Use the same coding style as Kura. Try to incorporate the suggestions SonarLint may have for your tests.
                      "},{"location":"quality-assurance/unit-testing/#running-the-tests","title":"Running the Tests","text":"

                      The basic flow is to build your implementation using maven and then also build and run the unit tests using mvn clean test (or some other phase e.g. install, which also runs integration tests).

                      Advanced test running and running them in IDE is described in Kura GitHub Wiki.

                      "},{"location":"quality-assurance/unit-testing/#integration-testing","title":"Integration Testing","text":"

                      These test proper behavior in the OSGi environment. Some additional configuration is therefore necessary.

                      "},{"location":"quality-assurance/unit-testing/#test-location_1","title":"Test Location","text":"

                      We don't want to mess with the implementation code here, either, so all tests are again located in the test projects under test/. It can be the same project as for unit tests. The proper folder to put the tests in is src/main/java.

                      "},{"location":"quality-assurance/unit-testing/#code-conventions_1","title":"Code Conventions","text":"
                      • Preferably use <package name>.test as the name of the test project. Add it as a module in test/pom.xml that also serves as maven artifact's parent.
                      • Only add src/main/java to build.properties' source...
                      • Make the bundle a fragment of the class-under-test's bundle so that you gain access to its internal packages.
                      • Use the <package name>.test as the package to put the test in. Also add .test suffix to any subpackages that are also under test.
                      • Use the same coding style as Kura. Try to incorporate the suggestions SonarLint may have for your tests.
                      "},{"location":"quality-assurance/unit-testing/#running-the-tests_1","title":"Running the Tests","text":"

                      The basic flow is to build your implementation using maven and then also build and run the integration tests using mvn clean install.

                      Advanced test running and running them in IDE is described in Kura GitHub Wiki.

                      "},{"location":"references/javadoc/","title":"Javadoc","text":"
                      • Kura API Documentation.
                      • Java Communications API (javax.comm).
                      • Java USB (javax.usb).
                      • Java HIDAPI Javadoc.
                      • OpenJDK Device I/O API Documentation.
                      • CANBus API Documentation.
                      "},{"location":"references/mqtt-namespace/","title":"MQTT Namespace","text":"

                      This section provides guidelines on how to structure the MQTT topic namespace for messaging interactions with applications running on IoT gateway devices.

                      Interactions may be solicited by a remote server to the gateway using a request/response messaging model, or unsolicited when the gateway simply reports messages or events to a remote server based on periodic or event-driven patterns.

                      The table below defines some basic terms used in this document:

                      Term Description account_name Identifies a group of devices and users. It can be seen as partition of the MQTT topic namespace. For example, access control lists can be defined so that users are only given access to the child topics of a given account_name. client_id Identifies a single gateway device within an account (typically the MAC address of a gateway\u2019s primary network interface). The client_id maps to the Client Identifier (Client ID) as defined in the MQTT specifications. app_id Unique string identifier for application (e.g., \u201cCONF-V1\u201d, \u201cCONF-V2\u201d, etc.). resource_id Identifies a resource(s) that is owned and managed by a particular application. Management of resources (e.g., sensors, actuators, local files, or configuration options) includes listing them, reading the latest value, or updating them to a new value. A resource_id may be a hierarchical topic, where, for example, \u201csensors/temp\u201d may identify a temperature sensor and \u201csensor/hum\u201d a humidity sensor.

                      A gateway, as identified by a specific client_id and belonging to a particular account_name, may have one or more applications running on it (e.g., \u201capp_id1\u201d, \u201capp_id2\u201d, etc.). Each application can manage one or more resources identified by a distinct resource_id(s).

                      Based on this criterion, an IoT application running on an IoT gateway may be viewed in terms of the resources it owns and manages as well as the unsolicited events it reports.

                      "},{"location":"references/mqtt-namespace/#mqtt-requestresponse-conversations","title":"MQTT Request/Response Conversations","text":"

                      Solicited interactions require a request/response message pattern to be established over MQTT. To initiate a solicited conversation, a remote server first sends a request message to a given application running on a specific device and then waits for a response.

                      To ensure the delivery of request messages, applications that support request/response conversations via MQTT should subscribe to the following topic on startup:

                      $EDC/account_name/client_id/app_id/#\n

                      The $EDC prefix is used to mark topics that are used as control topics for remote management. This prefix distinguishes control topics from data topics that are used in unsolicited reports and marks the associated messages as transient (not to be stored in the historical data archive, if present).

                      Note

                      While Kura currently requires \u201c$EDC\u201d as the prefix for control topics, this prefix may change in the future for the following reasons:

                      • MQTT 3.1.1 discourages the use of topic starting with \u201c$\u201d for application purposes.

                      • As a binding of LWM2M over MQTT is taking shape, it would make sense to use a topic prefix for management messages like \u201cLWM2M\u201d or similar abbreviations (e.g. \"LW2\u201d, \u201cLWM\u201d).

                      A requester (i.e., the remote server) initiates a request/response conversation through the following events:

                      1. Generating a conversation identifier known as a request.id (e.g., by concatenating a random number to a timestamp)

                      2. Subscribing to the topic where the response message will be published, where requester.client.id is the client ID of the requester, such as:

                        $EDC/account_name/requester.client.id/app_id/REPLY/request.id\n
                      3. Sending the request message to the appropriate application-specific topic with the following fields in the payload:

                        • request.id (identifier used to match a response with a request)
                        • requester.client.id (client ID of the requester)

                      The application receives the request, processes it, and responds on a REPLY topic structured as:

                      $EDC/account_name/requester.client.id/app_id/REPLY/request.id\n

                      Note

                      While this recommendation does not mandate the format of the message payload, which is application-specific, it is important that the request.id and requester.client.id fields are included in the payload. Kura leverages an MQTT payload encoded through Google Protocol Buffers. Kura includes the request.id and the requester.client.id as two named metrics of the Request messages. The Kura payload definition can be found here.

                      Once the response for a given request is received, the requester unsubscribes from the REPLY topic.

                      "},{"location":"references/mqtt-namespace/#mqtt-requestresponse-example","title":"MQTT Request/Response Example","text":"

                      The following sample request/response conversation shows the device configuration being provided for an application:

                      account_name: guest\ndevice client_id: F0:D2:F1:C4:53:DB\napp_id: CONF-V1\nRemote Service Requester client_id: 00:E0:C7:01:02:03\n

                      The remote server publishes a request message similar to the following:

                      • Request Topic:

                        • $EDC/guest/F0:D2:F1:C4:53:DB/CONF-V1/GET/configurations
                      • Request Payload:

                        • request.id: 1363603920892-8078887174204257595
                        • requester.client.id: 00:E0:C7:01:02:03

                      The gateway device replies with a response message similar to the following:

                      • Response Topic:

                        • $EDC/guest/00:E0:C7:01:02:03/CONF-V1/REPLY/1363603920892-8078887174204257595
                      • Response Payload, where the following properties are mandatory:

                        • response.code Possible response code values include:
                          • 200 (RESPONSE_CODE_OK)
                          • 400 (RESPONSE_CODE_BAD_REQUEST)
                          • 404 (RESPONSE_CODE_NOTFOUND)
                          • 500 (RESPONSE_CODE_ERROR)
                          • response.exception.message (value is null or an exception message) response.exception.message (value is null or an exception stack trace)

                      Note

                      In addition to the mandatory properties, the response payload may also have custom properties whose description is beyond the scope of this document.

                      It is recommended that the requester server employs a timeout to control the length of time that it waits for a response from the gateway device. If a response is not received within the timeout interval, the server can expect that either the device or the application is offline.

                      "},{"location":"references/mqtt-namespace/#mqtt-remote-resource-management","title":"MQTT Remote Resource Management","text":"

                      A remote server interacts with the application\u2019s resources through read, create and update, delete, and execute operations. These operations are based on the previously described request/response conversations.

                      "},{"location":"references/mqtt-namespace/#read-resources","title":"Read Resources","text":"

                      An MQTT message published on the following topic is a read request for the resource identified by the resource_id:

                      $EDC/account_name/client_id/app_id/GET/resource_id\n

                      The receiving application responds with a REPLY message containing the latest value of the requested resource.

                      The resource_id is application specific and may be a hierarchical topic. It is recommended to design resource identifiers following the best practices established for REST API.

                      For example, if an application is managing a set of sensors, a read request issued to the topic \"$EDC/account_name/client_id/app_id/GET/sensors\" will reply with the latest values for all sensors.

                      Similarly, a read request issued to the topic \"$EDC/account_name/client_id/app_id/GET/sensors/temp\" will reply with the latest value for only a temperature sensor that is being managed by the application.

                      "},{"location":"references/mqtt-namespace/#create-or-update-resources","title":"Create or Update Resources","text":"

                      An MQTT message published on the following topic is a create or update request for the resource identified by the resource_id:

                      $EDC/account_name/client_id/app_id/PUT/resource_id\n

                      The receiving application creates the specified resource (or updates it if it already exists) with the value supplied in the message payload and responds with a REPLY message.

                      As in the read operations, the resource_id is application specific and may be a hierarchical topic. It is recommended to design resource identifiers following the best practices established for REST API. For example, to set the value for an actuator, a message can be published to the topic \"$EDC/account_name/client_id/app_id/PUT/actuator/1\" with the new value suplliied in the message payload.

                      "},{"location":"references/mqtt-namespace/#delete-resources","title":"Delete Resources","text":"

                      An MQTT message published on the following topic is a delete request for the resource identified by the resource_id:

                      $EDC/account_name/client_id/app_id/DEL/resource_id\n

                      The receiving application deletes the specified resource, if it exists, and responds with a REPLY message.

                      "},{"location":"references/mqtt-namespace/#execute-resources","title":"Execute Resources","text":"

                      An MQTT message published on the following topic is an execute request for the resource identified by the resource_id:

                      $EDC/account_name/client_id/app_id/EXEC/resource_id\n

                      The receiving application executes the specified resource, if it exists, and responds with a REPLY message. The semantics of the execute operation is application specific.

                      "},{"location":"references/mqtt-namespace/#other-operations","title":"Other Operations","text":"

                      The IoT application may respond to certain commands, such as taking a snapshot of its configuration or executing an OS-level command. The following topic namespace is recommended for command operations:

                      $EDC/account_name/client_id/app_id/EXEC/command_name\n

                      An MQTT message published with this topic triggers the execution of the associated command. The EXEC message may contain properties in the MQTT payload that can be used to parameterize the command execution.

                      "},{"location":"references/mqtt-namespace/#mqtt-unsolicited-events","title":"MQTT Unsolicited Events","text":"

                      IoT applications have the ability to send unsolicited messages to a remote server using events to periodically report data readings from their resources, or to report special events and observed conditions.

                      Tip

                      It is recommended to not use MQTT control topics for unsolicited events, and subsequently, to avoid the $EDC topic prefix.

                      Event MQTT topics generally follow the pattern shown below to report unsolicited data observations for a given resource:

                      account_name/client_id/app_id/resource_id\n
                      "},{"location":"references/mqtt-namespace/#discoverability","title":"Discoverability","text":"

                      The MQTT namespace guidelines in this document do not address remote discoverability of a given device\u2019s applications and its resources. The described interaction pattern can be easily adopted to define an application whose only responsibility is reporting the device profile in terms of installed applications and available resources.

                      "},{"location":"references/mqtt-namespace/#remote-osgi-management-via-mqtt","title":"Remote OSGi Management via MQTT","text":"

                      The concepts previously described have been applied to develop a solution that allows for the remote management of certain aspects of an OSGi container through the MQTT protocol, including:

                      • Remote deployment of application bundles

                      • Remote start and stop of services

                      • Remote read and update of service configurations

                      The following sections describe the MQTT topic namespaces and the application payloads used to achieve the remote management of an OSGi container via MQTT.

                      Note

                      For the scope of this document, some aspects concerning the encoding and compressing of the payload are not included.

                      The applicability of the remote management solution, as inspired by the OSGi component model, can be extended beyond OSGi as the contract with the managing server based on MQTT topics and XML payloads.

                      "},{"location":"references/mqtt-namespace/#remote-osgi-configurationadmin-interactions-via-mqtt","title":"Remote OSGi ConfigurationAdmin Interactions via MQTT","text":"

                      An application bundle is installed in the gateway to allow for remote management of the configuration properties of the services running in the OSGi container.

                      For information about the OSGi Configuration Admin Service and the OSGi Meta Type Service, please refer to the OSGi Service Platform Service R7 Specifications.

                      The app_id for the remote configuration service of an MQTT application is \u201cCONF-V1\u201d. The resources it manages are the configuration properties of the OSGi services. Service configurations are represented in XML format.

                      The following service configuration XML message is an example of a watchdog service:

                      <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<ns2:configuration xmlns:ns2=<http://eurotech.com/esf/2.0\n  xmlns=<http://www.osgi.org/xmlns/metatype/v1.2.0>\n  pid=\"org.eclipse.kura.watchdog.WatchdogService\">\n\n  <OCD id=\"org.eclipse.kura.watchdog.WatchdogService\"\n    name=\"WatchdogService\"\n    description=\"WatchdogService Configuration\">\n\n    <Icon resource=\"WatchdogService\"/>\n\n    <AD id=\"watchdog.timeout\"\n      name=\"watchdog.timeout\"\n      required=\"true\"\n      default=\"10000\"\n      cardinality=\"0\"\n      type=\"Integer\"\n      description=\"\"/>\n  </OCD>\n\n  <ns2:properties>\n    <ns2:property type=\"Integer\" array=\"false\" name=\"watchdog.timeout\">\n      <ns2:value>10000</ns2:value>\n    </ns2:property>\n  </ns2:properties>\n</ns2:configuration>\n

                      The service configuration XML message is comprised of the following parts:

                      • The Object Class Definition (OCD), which describes the service attributes that may be configured. (The syntax of the OCD element is described in the OSGi Service Platform Service R7 Specifications)

                      • The properties element, which contains one or more properties with their associated type and values. The type name must match the name provided in the corresponding attribute definition identifier (AD id) contained in the OCD.

                      The \u201cCONF-V1\u201d application supports the read and update resource operations as described in the following sections.

                      "},{"location":"references/mqtt-namespace/#read-all-configurations","title":"Read All Configurations","text":"

                      This operation provides all service configurations for which remote administration is supported.

                      • Request Topic:

                        • $EDC/account_name/client_id/CONF-V1/GET/configurations
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Configurations of all the registered services serialized in XML format
                      "},{"location":"references/mqtt-namespace/#read-configuration-for-a-given-service","title":"Read Configuration for a Given Service","text":"

                      This operation provides configurations for a specific service that is identified by an OSGi service persistent identifier pid.

                      • Request Topic:

                        • $EDC/account_name/client_id/CONF-V1/GET/configurations/pid
                      • Request Payload:

                        • Nothing application-specific beyond the request ID and requester client ID
                      • Response Payload:

                        • Configurations of the registered service identified by a pid serialized in XML format
                      "},{"location":"references/mqtt-namespace/#update-all-configurations","title":"Update All Configurations","text":"

                      This operation remotely updates the configuration of a set of services.

                      • Request Topic:

                        • $EDC/account_name/client_id/CONF-V1/PUT/configurations
                      • Request Payload:

                        • Service configurations serialized in XML format
                      • Response Payload:

                        • Nothing application-specific beyond the response code
                      "},{"location":"references/mqtt-namespace/#update-the-configuration-of-a-given-service","title":"Update the Configuration of a Given Service","text":"

                      This operation remotely updates the configuration of the service identified by a pid.

                      • Request Topic:

                        • $EDC/account_name/client_id/CONF-V1/PUT/configurations/pid
                      • Request Payload:

                        • Service configurations serialized in XML format
                      • Response Payload:

                        • Nothing application-specific
                      "},{"location":"references/mqtt-namespace/#example-management-web-application","title":"Example Management Web Application","text":"

                      The previously described read and update resource operations can be leveraged to develop a web application that allows for remote OSGi service configuration updates via MQTT though a web user-interface.

                      The screen capture that follows shows an example administration application where, for a given IoT gateway, a list of all configurable services is presented to the administrator.

                      When one such service is selected, a form is dynamically generated based on the metadata provided in the service OCD. This form includes logic to handle different attribute types, validate acceptable value ranges, and render optional values as drop-downs. When the form is submitted, the new values are communicated to the device through an MQTT resource update message.

                      "},{"location":"references/mqtt-namespace/#remote-osgi-deploymentadmin-interactions-via-mqtt","title":"Remote OSGi DeploymentAdmin Interactions via MQTT","text":"

                      An application is installed in the gateway to allow for the remote management of the deployment packages installed in the OSGi container.

                      For information about the OSGi Deployment Admin Service, please refer to the OSGi Service Platform Service Compendium 4.3 Specifications.

                      The app_id for the remote deployment service of an MQTT application is \u201cDEPLOY-V2\u201d. It allows to perform the following operations:

                      • Download, install and uninstall OSGi Deployment Packages
                      • Download and execute system updates based on shell scripts
                      • Get the list of bundles currently in the runtime
                      • Start and stop bundles
                      "},{"location":"references/mqtt-namespace/#deploy-v2","title":"DEPLOY-V2","text":""},{"location":"references/mqtt-namespace/#download-and-install-messages","title":"Download and Install Messages","text":"

                      The download request allows to download and optionally install a software package. The installation procedure will be performed after the download completes if the dp.install metric is set to true. If the metric is set to false, the installation step will not be performed.

                      The package type must be specified using the dp.install.system.update request metric, the supported types are the following:

                      • OSGi Deployment Package: An OSGi deployment package. Selected with dp.install.system.update = false.
                      • Executable Shell Script: A shell script that applies a system level update. Selected with dp.install.system.update = true.

                      The device will report the download progress and result of the installation to the cloud platform by sending asynchronous download notification messages and install notification messages.

                      The completion notification logic differs depending on the package type.

                      • OSGi Deployment Package: The completion notification will be sent immediately after that the Deployment Package is installed on the system.

                      • Executable Shell Script: The completion notification will not be sent immediately after that the shell script is executed, but is determined by the execution of an additional verifier script. The verifier script will be executed at next framework startup. A install notification message will be sent afterwards, with dp.install.status = COMPLETED if the exit status is 0 or dp.install.status = FAILED otherwise. This is based on the assumption that a system level update will typically require a device restart.

                      The verifier script can be provided in the following ways:

                      • By specifying a download URL as the value of the dp.install.verifier.uri request metric. In this case the framework will download the verifier script from the provided URL.

                      • By installing it during the execution of the main shell script. In this case the file must be placed in the /opt/eclipse/kura/data/persistance/verification directory. The installed verifier file name must have the ${name}-${version}.sh_verifier.sh structure where ${name} and ${version} must be replaced with the values of the dp.name and dp.version request metrics.

                      Warning

                      As said above, in case of Executable Shell Script, the completion notification will not be sent if the verifier script is not provided and/or the framework is not restarted.

                      Request:

                      • Request Topic:

                        • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/download
                      • Payload:

                        • metrics:
                          • dp.job.id (Long). Mandatory. Represents a unique Job ID for the download.
                          • dp.uri (String). Mandatory. Represents the URI of the deployment package.
                          • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                          • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The file will be saved in the temporary directory as -.jar possibly overwriting an existing file.
                          • dp.download.protocol (String) Mandatory. Specifies the protocol to be used to download the bundles/shell scripts. Must be set to HTTP or HTTPS.
                          • dp.download.block.size (Integer). Optional (if not specified by the cloud platform, it is estimated by the device, depending on the total file size. In this case it is fixed at 1% of the total file size). The size in kBi of the blocks used to download the DP.
                          • dp.download.block.delay (Integer). Optional (default: 0). Delay in ms between block transfers.
                          • dp.download.notify.block.size (Integer). Optional (if not specified by the cloud platform, it is estimated by the device, depending on the total file size. In this case it is fixed at 5% of the total file size). The size in kBi between the notification messages sent to the cloud platform.
                          • dp.download.timeout (Integer). Optional (default: 60000). The timeout in seconds for each block that has to be downloaded.
                          • dp.download.resume (Bool). Optional (default: true). Resume download transfer if supported by the server.
                          • dp.download.force (Bool). Optional (default: true). Specifies if the download forces to download again the file, if already exists on target device.
                          • dp.download.username (String). Optional. Username for password protected download. No authentication will be tried if username is not present.
                          • dp.download.password (String). Optional. Password for password protected download. No authentication will be tried if password is not present
                          • dp.download.hash (String). Optional. The algorithm and value of the hash of the file used to verify the integrity of the download. The format is of this property is: {algorithm}:{hash value}
                          • dp.install (Bool). Optional (default: true). Whether the package should be immediately installed after being downloaded.
                          • dp.install.system.update (Bool). Optional (default: false). Sets whether or not this is a system update, rather than a bundle/package update.
                          • dp.install.verifier.uri (String). Optional. The verifier script URI to run after the installation of the system update.
                          • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package installation process. This property is ignored if dp.install.system.update is true. In such case the reboot must be implemented as part of the script.
                          • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if\u00a0dp.reboot is true and dp.install.system.update is false.
                          • Response:

                            The client will reply immediately with an appropriate response.code. If the platform retries the request but the download is in progress, the client will reply that the request is already in progress with a 500 error code. If the DP has already been downloaded, the client will reply that the request has been accepted. If the dp.download.force flag is set to true, the client will start the download from the beginning, if false the device will proceed with the installation.

                            Payload: no application-specific metrics or body.

                            Request:

                            • Request Topic:
                              • $EDC/[account_name]/[client_id]/DEPLOY-V2/GET/download
                            • Payload: no application-specific metrics or body.

                            Response:

                            • Payload:
                              • metrics:
                                • dp.http.transfer.size (Integer). The size in kBi of the DP being downloaded
                                • dp.http.transfer.progress (Integer). The estimated progress of the download in percentage (0-100%). Do not rely on this indicator to detect the download completion.
                                • dp.http.transfer.status (String). An enum specifying the download status (IN_PROGRESS, COMPLETED, FAILED...).
                                • job.id (Long) Optional. The ID of the job to notify status

                            Request:

                            • Request Topic:
                              • $EDC/[account_name]/[client_id]/DEPLOY-V2/DEL/download
                            • Payload: no application-specific metrics or body.

                            Response:

                            • Response Payload:
                              • Nothing application-specific beyond the response code. Unsolicited messages will report the status of the cancel operation.
                            "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-download-progress","title":"Unsolicited messages for download progress","text":"

                            The client will start downloading the DP and will compute the size of the transfer from the HTTP header. This size will be used to estimate the download progress using the request parameter dp.download.block.size. Next, the client will report the download progress to the platform by publishing, with QoS==2, one or more unsolicited messages. If HTTP header is not available, the device will report 50% as dp.download.progress for all the download processes. The value of requester.client.id is one of the last downloads or install request received.

                            Download Notification:

                            • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/download
                            • Payload:
                              • metrics:
                                • job.id (Long). The ID of the job to notify status
                                • dp.download.size (Integer). The size in kBi of the DP being downloaded
                                • dp.download.progress (Integer). The estimated progress of the download in percentage (0-100%). Do not rely on this indicator to detect the download completion.
                                • dp.download.status (String). An enum specifying the download status (IN_PROGRESS, COMPLETED, FAILED, CANCELLED...).
                                • dp.download.error.message (String). In case of FAILED status, this metric will contain information about the error.
                                • dp.download.index (Integer). The index of the file that is currently downloaded. This is supposed to support multiple file downloads.
                            "},{"location":"references/mqtt-namespace/#install-messages","title":"Install Messages","text":"

                            Request:

                            • Request Topic:
                              • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/install
                            • Payload:
                              • metrics:
                                • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                                • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The basename of the DP file will to install is derived as -.jar. The file is assumed to reside in the temporary directory.
                                • dp.install.system.update (Bool). Mandatory. Specifies if the specified resource is a system update or not. It can be applied to the system immediately or after a system reboot.
                                • dp.install.verifier.uri (String). Optional. The verifier script URI to run after the installation of the system update.
                                • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package installation process. There might be DPs requiring a post-installation (from the standpoint of the OSGi Deployment Admin) step requiring a system reboot. Note that the post-install phase is not handled by the Deployment Admin. The installation in this case is complete (and can fail) after the reboot. This property is ignored if dp.install.system.update is true. In such case the reboot must be implemented as part of the script.
                                • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if\u00a0dp.reboot is true and dp.install.system.update is false.
                                • Note

                                  This operation can be retried. Anyway, if it fails once it's likely to fail again.

                                  Response:

                                  • Payload: Nothing application-specific beyond the response code. Unsolicited messages will report the status of the install operation.

                                  Request:

                                  • Request Topic:
                                    • $EDC/[account_name]/[client_id]/DEPLOY-V2/GET/install
                                  • Payload: no application-specific metrics or body.

                                  Response:

                                  • Payload:
                                    • metrics:
                                      • dp.install.status (String). An enum specifying the install status
                                        • IDLE
                                        • INSTALLING BUNDLE
                                      • dp.name (String). Optional. If installing: the value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                                      • dp.version (String). Optional. If installing: the value of the header DeploymentPackage-Version in the DP MANIFEST.
                                  "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-install-progress","title":"Unsolicited messages for install progress","text":"

                                  If the value of dp.install in the original download request is true the client will start installing the DP. Due to the limitations of the OSGi DeploymentAdmin, it's not possible to have feedback on the install progress. However, these operations should normally complete in a few seconds, even for an upgrade. Otherwise (dp.install==false), the platform can request the installation of an already downloaded package through the following message:

                                  Install notification:

                                  • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/install
                                  • Payload:
                                    • metrics:
                                      • job.id (Long). The ID of the job to notify status
                                      • dp.name (String). The name of the package that is installing.
                                      • dp.install.progress (Integer). The estimated progress of the install in percentage (0-100%). Due to limitations of the OSGi DeploymentAdmin, it's not possible to have a linear progress. It will go from 0% to 100% in one step. Do not rely on this indicator to detect the download completion.
                                      • dp.install.status (String). An enum specifying the install status (IN_PROGRESS, COMPLETED, FAILED...).
                                      • dp.install.error.message (String) Optional. In case of FAILED status, this metric will contain information about the error.
                                  "},{"location":"references/mqtt-namespace/#uninstall-messages","title":"Uninstall Messages","text":"

                                  Request:

                                  • Request Topic:
                                    • $EDC/[account_name]/[client_id]/DEPLOY-V2/EXEC/uninstall
                                  • Payload:
                                    • metrics:
                                      • dp.name (String). Mandatory. The value of the header DeploymentPackage-SymbolicName in the DP MANIFEST.
                                      • job.id (Long) Mandatory. The ID of the job to notify status
                                      • dp.version (String). Mandatory. The value of the header DeploymentPackage-Version in the DP MANIFEST. The basename of the DP file will to install is derived as -.jar. The file is assumed to reside in the temporary directory.
                                      • dp.reboot (Bool). Optional (default: false). Whether the system should be rebooted as part of the package uninstall process.
                                      • dp.reboot.delay (Integer). Optional (default: 0 - immediately). Delay after which the device will be rebooted. Only meaningful if dp.reboot==true.
                                      • Response: The client will reply immediately with an appropriate response.code. If the platform retries the request but the uninstall operation is in progress, the client will reply that the request is already in progress with a 500 error code. At the end of the uninstall operation, an unsolicited message is sent to the cloud platform to report the operation status. If a reboot was requested in the received uninstall request, it will be executed with the specified delay.

                                        "},{"location":"references/mqtt-namespace/#unsolicited-messages-for-uninstall-progress","title":"Unsolicited messages for uninstall progress","text":"

                                        Uninstall notification:

                                        • $EDC/account_name/requester.client.id/DEPLOY-V2/NOTIFY/client-id/uninstall
                                        • Payload:
                                          • metrics:
                                            • job.id (Long). The ID of the job to notify status
                                            • dp.name (String). The name of the package that is uninstalling.
                                            • dp.uninstall.progress (Integer). The estimated progress of the install in percentage (0-100%). Due to limitations of the OSGi DeploymentAdmin, it's not possible to have a linear progress. It will go from 0% to 100% in one step. Do not rely on this indicator to detect the download completion.
                                            • dp.uninstall.status (String). An enum specifying the uninstall status (IN_PROGRESS, COMPLETED, FAILED...).
                                            • dp.uninstall.error.message (String) Optional. In case of FAILED status, this metric will contain information about the error.
                                        "},{"location":"references/mqtt-namespace/#read-all-bundles","title":"Read All Bundles","text":"

                                        This operation provides all the bundles installed in the OSGi framework.

                                        • Request Topic:
                                          • $EDC/account_name/client_id/DEPLOY-V2/GET/bundles
                                        • Request Payload:
                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:
                                          • Installed bundles serialized in XML format

                                        The following XML message is an example of a bundle:

                                        <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<bundles>\n    <bundle>\n        <name>org.eclipse.osgi</name>\n        <version>3.8.1.v20120830-144521</version>\n        <id>0</id>\n        <state>ACTIVE</state>\n    </bundle>\n    <bundle>\n        <name>org.eclipse.equinox.cm</name>\n        <version>1.0.400.v20120522-1841</version>\n        <id>1</id>\n        <state>ACTIVE</state>\n    </bundle>\n</bundles>\n

                                        The bundle XML message is comprised of the following bundle elements:

                                        • Symbolic name
                                        • Version
                                        • ID
                                        • State
                                        "},{"location":"references/mqtt-namespace/#start-a-bundle","title":"Start a Bundle","text":"

                                        This operation starts a bundle identified by its ID.

                                        • Request Topic:
                                          • $EDC/account_name/client_id/DEPLOY-V2/EXEC/start/bundle_id
                                        • Request Payload:
                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:
                                          • Nothing application-specific beyond the response code
                                        "},{"location":"references/mqtt-namespace/#stop-a-bundle","title":"Stop a Bundle","text":"

                                        This operation stops a bundle identified by its ID.

                                        • Request Topic:
                                          • $EDC/account_name/client_id/DEPLOY-V2/EXEC/stop/bundle_id
                                        • Request Payload:
                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:
                                          • Nothing application-specific beyond the response code
                                        "},{"location":"references/mqtt-namespace/#example-management-web-application_1","title":"Example Management Web Application","text":"

                                        The previously described read, start/stop, and install/uninstall resources can be used to implement a remote management application. An example of such application is Eclipse Kapua. In particular it is possible to use the download and install resources from the following sections in Eclipse Kapua console:

                                        • Devices section:

                                        Selecting the Devices section, a target device, and then clicking on the Install button in the Packages tab will allow to send download and install requests.

                                        • Batch Jobs section

                                        It is possible to create a batch job with the Package Download / Install definition to perform a download / install request on a set of target devices.

                                        "},{"location":"references/mqtt-namespace/#remote-gateway-inventory-via-mqtt","title":"Remote Gateway Inventory via MQTT","text":"

                                        An application is installed in the gateway to allow for the remote query of the resources installed in the OSGi container and the underlying OS.

                                        The app_id for the remote inventory service of an MQTT application is \u201cINVENTORY-V1\u201d. The service allows retrieving all the different resources available/installed on the gateway. The service supports the following resources:

                                        • BUNDLES : represents a OSGi Bundle
                                        • DP : represents a OSGi Deployment Package
                                        • DEB : represents a Linux Debian package
                                        • RPM : represents a Linux RPM package
                                        • APK : represents a Linux Alpine APK package
                                        • DOCKER : represents a container
                                        • CONTAINER IMAGE : represents a container image

                                        The resources are represented in JSON format. The following message is an example of a service deployment:

                                        {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"io.netty.transport-native-unix-common\",\n            \"version\":\"4.1.34.Final\",\n            \"type\":\"BUNDLE\"\n        },\n    ]\n}\n

                                        The inventory JSON message is comprised of the following package elements:

                                        • Name

                                        • Version

                                        • Type

                                        The \u201cINVENTORY-V1\u201d application supports only the read resource operations as described in the following sections.

                                        "},{"location":"references/mqtt-namespace/#inventory-bundles","title":"Inventory Bundles","text":""},{"location":"references/mqtt-namespace/#read-all-bundles_1","title":"Read All Bundles","text":"

                                        This operation provides all the bundles installed in the OSGi framework.

                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/bundles
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:

                                          • Installed bundles serialized in JSON format

                                        The following JSON message is an example of a bundle:

                                        {\n    \"bundles\":[\n        {\n            \"name\":\"org.eclipse.osgi\",\n            \"version\":\"3.16.0.v20200828-0759\",\n            \"id\":0,\n            \"state\":\"ACTIVE\",\n            \"signed\":true\n        },\n        {\n            \"name\":\"org.eclipse.equinox.cm\",\n            \"version\":\"1.4.400.v20200422-1833\",\n            \"id\":1,\n            \"state\":\"ACTIVE\",\n            \"signed\":false\n        }\n    ]\n}\n

                                        The bundle JSON message is comprised of the following bundle elements:

                                        • Symbolic name

                                        • Version

                                        • ID

                                        • State

                                        • Signed

                                        "},{"location":"references/mqtt-namespace/#start-bundle","title":"Start Bundle","text":"

                                        This operation allows to start a bundles installed in the OSGi framework.

                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/EXEC/bundles/_start
                                        • Request Payload:

                                          • A JSON object that identifies the target bundle must be specified in payload body.
                                        • Response Payload:

                                          • Nothing application specific
                                        "},{"location":"references/mqtt-namespace/#stop-bundle","title":"Stop Bundle","text":"
                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/EXEC/bundles/_stop
                                        • Request Payload:

                                          • A JSON object that identifies the target bundle must be specified in payload body.
                                        • Response Payload:

                                          • Nothing application specific
                                        "},{"location":"references/mqtt-namespace/#json-identifier-for-start-and-stop-requests","title":"JSON identifier for start and stop requests","text":"

                                        The requests for starting and stopping a bundle require the application to include a JSON object in request payload for selecting the target bundle, the defined properties are the following:

                                        • name: The symbolic name of the bundle to be started/stopped. This parameter must be of string type and it is mandatory.
                                        • version: The version of the bundle to be stopped. This parameter must be of string type and it is optional.

                                        If multiple bundles match the selection criteria, only one of them will be stopped/started, which one is not defined.

                                        Examples:

                                        {\n    \"name\":\"org.eclipse.kura.example.beacon\"\n}\n
                                        {\n    \"name\":\"org.eclipse.kura.example.beacon\",\n    \"version\":\"1.0.500\"\n}\n
                                        "},{"location":"references/mqtt-namespace/#inventory-deployment-packages","title":"Inventory Deployment Packages","text":""},{"location":"references/mqtt-namespace/#read-all-deployment-packages","title":"Read All Deployment Packages","text":"

                                        This operation provides the deployment packages installed in the OSGi framework.

                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/packages
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:

                                          • Installed deployment packages serialized in JSON format

                                        The following JSON message is an example of a bundle:

                                        {\n    \"deploymentPackages\":[\n        {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"signed\":false,\n            \"bundles\":[\n                {\n                    \"name\":\"org.eclipse.kura.example.beacon\",\n                    \"version\":\"1.0.500\",\n                    \"id\": 171,\n                    \"state\": \"ACTIVE\",\n                    \"signed\": false\n                }\n            ]\n        }\n    ]\n}\n

                                        The deployment package JSON message is comprised of the following package elements:

                                        • Symbolic name

                                        • Version

                                        • Signature: true if all the bundles in the deployment package are signed

                                        • Bundles that are managed by the deployment package along with their symbolic name and version

                                        "},{"location":"references/mqtt-namespace/#inventory-system-packages-debrpmapk","title":"Inventory System Packages (DEB/RPM/APK)","text":""},{"location":"references/mqtt-namespace/#read-all-system-packages","title":"Read All System Packages","text":"

                                        This operation provides the Linux packages installed in OS.

                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/systemPackages
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:

                                          • Installed Linux Packages serialized in JSON format

                                        The following JSON message is an example of a bundle:

                                        {\n    \"systemPackages\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"alsa-utils\",\n            \"version\":\"1.1.8-2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"ansible\",\n            \"version\":\"2.7.7+dfsg-1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apparmor\",\n            \"version\":\"2.13.2-10\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-listchanges\",\n            \"version\":\"3.19\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-transport-https\",\n            \"version\":\"1.8.2.2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-utils\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        }\n    ]\n}\n

                                        The bundle JSON message is comprised of the following bundle elements:

                                        • Name

                                        • Version

                                        • Type

                                        "},{"location":"references/mqtt-namespace/#inventory-containers","title":"Inventory Containers","text":""},{"location":"references/mqtt-namespace/#list-all-containers","title":"List All Containers","text":"

                                        Using the API exposed by Inventory-V1, the user can manage containers via external applications such as Eclipse Kapua. This operation lists all the containers installed in the gateway.

                                        • Request Topic:
                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/containers
                                        • Request Payload:
                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:
                                          • Installed containers serialized in JSON format

                                        The following JSON message is an example of what this request outputs:

                                        {\n  \"containers\":\n  [\n    {\n      \"name\":\"container_1\",\n      \"version\":\"nginx:latest\",\n      \"type\":\"DOCKER\",\n      \"state\":\"active\"\n    }\n  ]\n}\n

                                        The container JSON message is comprised of the following elements:

                                        • Name: The name of the docker container.

                                        • Version: describes both the container's respective image and tag separated by a colon.

                                        • Type: denotes the type of inventory payload

                                        • State: describes the container's current state

                                          • active: Container is running
                                          • installed: Container is starting
                                          • uninstalled: Container has failed, or is stopped
                                          • unknown: Container state can not be determined
                                        "},{"location":"references/mqtt-namespace/#start-a-container","title":"Start a Container","text":"

                                        This operation allows starting a container installed on the gateway. * Request Topic * $EDC/account_name/client_id/INVENTORY-V1/EXEC/containers/_start * Request Payload * A JSON object that identifies the target container must be specified in the payload body. This payload will be described in the following section * Response Payload * Nothing application-specific

                                        "},{"location":"references/mqtt-namespace/#stop-a-container","title":"Stop a Container","text":"
                                        • Request Topic
                                          • $EDC/account_name/client_id/INVENTORY-V1/EXEC/containers/_stop
                                        • Request Payload
                                          • A JSON object that identifies the target container must be specified in the payload body. This payload will be described in the following section.
                                        • Response Payload
                                          • Nothing application-specific
                                        "},{"location":"references/mqtt-namespace/#json-identifierpayload-for-container-start-and-stop-requests","title":"JSON identifier/payload for container start and stop requests","text":"

                                        The requests for starting and stopping a container require the application to include a JSON object in the request payload for selecting the target container. Docker enforces unique container names on a gateway, and thus they can reliably be used as an identifier.

                                        Examples:

                                        {\n    \"name\":\"container_1\",\n    \"version\":\"nginx:latest\",\n    \"type\":\"DOCKER\",\n    \"state\":\"active\"\n}\n
                                        {\n    \"name\":\"container_1\",\n}\n
                                        "},{"location":"references/mqtt-namespace/#inventory-container-images","title":"Inventory Container Images","text":""},{"location":"references/mqtt-namespace/#list-all-images","title":"List All Images","text":"

                                        Using the API exposed by Inventory-V1, the user can manage container images via external applications such as Eclipse Kapua. This operation lists all the images in the gateway.

                                        • Request Topic:
                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/images
                                        • Request Payload:
                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:
                                          • Installed containers serialized in JSON format

                                        The following JSON message is an example of what this request outputs:

                                        {\n  \"images\":\n  [\n    {\n        \"name\":\"nginx\",\n        \"version\":\"latest\",\n        \"type\":\"CONTAINER_IMAGE\"\n    }\n  ]\n}\n

                                        The container JSON message is comprised of the following elements:

                                        • Name: The name of the container image.

                                        • Version: describes the container image's version.

                                        • Type: denotes the type of inventory payload

                                        "},{"location":"references/mqtt-namespace/#delete-a-container-image","title":"Delete a Container Image","text":"

                                        This operation allows deleting a container image not in use on the gateway. * Request Topic * $EDC/account_name/client_id/INVENTORY-V1/EXEC/images/_delete * Request Payload * A JSON object that identifies the target image must be specified in the payload body. This payload will be described in the following section * Response Payload * Nothing application-specific

                                        "},{"location":"references/mqtt-namespace/#json-identifierpayload-for-container-image-delete-requests","title":"JSON identifier/payload for container image delete requests","text":"

                                        The requests for deleting a container image require the application to include a JSON object in the request payload for selecting the target. The JSON requires both name and version fields to be populated.

                                        Examples:

                                        {\n    \"name\":\"nginx\",\n    \"version\":\"latest\",\n    \"type\":\"CONTAINER_IMAGE\"\n}\n
                                        {\n    \"name\":\"nginx\",\n    \"version\":\"latest\",\n}\n
                                        "},{"location":"references/mqtt-namespace/#inventory-summary","title":"Inventory Summary","text":""},{"location":"references/mqtt-namespace/#read-all-resources","title":"Read All Resources","text":"

                                        This operation provides a list of all the resources installed on the gateway

                                        • Request Topic:

                                          • $EDC/account_name/client_id/INVENTORY-V1/GET/inventory
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:

                                          • Installed Linux Packages serialized in JSON format

                                        The following JSON message is an example of a bundle:

                                        {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"com.eclipsesource.jaxrs.provider.gson\",\n            \"version\":\"2.3.0.201602281253\",\n            \"type\":\"BUNDLE\"\n        },\n              {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"type\":\"DP\"\n        }\n    ]\n}\n

                                        The bundle JSON message is comprised of the following bundle elements:

                                        • Name

                                        • Version

                                        • Type

                                        "},{"location":"references/mqtt-namespace/#remote-certificates-and-keys-management-via-mqtt-keys-v1-and-keys-v2","title":"Remote Certificates and Keys management via MQTT (KEYS-V1 and KEYS-V2)","text":""},{"location":"references/mqtt-namespace/#keys-v1","title":"KEYS-V1","text":"

                                        The KEYS-V1 app-id is exposed by the org.eclipse.kura.core.keystore bundle. This request handler allows the remote management platform to get a list of all the KeystoreService instances and corresponding keys managed by the framework in a given device.

                                        The request handler allows, also, to install new trusted certificate and to generate new key pairs directly in the device. Finally, the remote platform can request, from a defined key pair, the generation of a CSR that can be countersigned remotely by a trusted CA.

                                        "},{"location":"references/mqtt-namespace/#read-all-the-kestoreservices","title":"Read All the KestoreServices","text":"

                                        This operation returns the list of all the KeystoreServices instantiated in the framework.

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/GET/keystores
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID
                                        • Response Payload:

                                          • List of all the managed KeystoreService instances with number of entries stored

                                        The following JSON message is an example of an output provided:

                                        [\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"type\": \"jks\",\n        \"size\": 4\n    },\n    {\n        \"id\": \"org.eclipse.kura.crypto.CryptoService\",\n        \"type\": \"jks\",\n        \"size\": 3\n    },\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    },\n    {\n        \"id\": \"org.eclipse.kura.core.keystore.DMKeystore\",\n        \"type\": \"jks\",\n        \"size\": 1\n    }\n]\n
                                        Each entry of the array is specified by the following values:

                                        • id: the KeystoreService PID
                                        • type: the type of keystore managed by the given instance
                                        • size: the number of entries in a given KeystoreService instance
                                        "},{"location":"references/mqtt-namespace/#read-key-entries","title":"Read Key Entries","text":"

                                        This operation returns the list of all the key entries managed by the framework. If a request payload is specified, the list of entries is filtered based on the parameters in the request

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/GET/keystores/entries
                                        • Request Payload:

                                          • Nothing application-specific beyond the request ID and requester client ID. In this case the response will contain all the entries in all the managed keystoreService instances.
                                          • A JSON object with one of the following:
                                          {\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\"\n}\n
                                          {\n\"alias\": \"ca-godaddyclass2ca\"\n}\n
                                        • Response Payload:

                                          • List of all the key entries managed by the framework eventually filtered based on the parameters in the request.

                                        The following JSON message is an example of an output provided in the response body:

                                        [\n    {\n        \"subjectDN\": \"OU=Go Daddy Class 2 Certification Authority, O=\\\"The Go Daddy Group, Inc.\\\", C=US\",\n        \"issuer\": \"OU=Go Daddy Class 2 Certification Authority,O=The Go Daddy Group\\\\, Inc.,C=US\",\n        \"startDate\": \"Tue, 29 Jun 2004 17:06:20 GMT\",\n        \"expirationDate\": \"Thu, 29 Jun 2034 17:06:20 GMT\",\n        \"algorithm\": \"SHA1withRSA\",\n        \"size\": 2048,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.SSLKeystore\",\n        \"alias\": \"ca-godaddyclass2ca\",\n        \"type\": \"TRUSTED_CERTIFICATE\"\n    },\n    {\n        \"algorithm\": \"RSA\",\n        \"size\": 4096,\n        \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n        \"alias\": \"localhost\",\n        \"type\": \"PRIVATE_KEY\"\n    }\n]\n
                                        "},{"location":"references/mqtt-namespace/#read-key-details","title":"Read Key Details","text":"

                                        This operation returns the details associated to a specified key in a keystore

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/GET/keystores/entries/entry
                                        • Request Payload:

                                          • A JSON with the keystoreServicePid and the alias of the desired key

                                        {\n  \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\"\n  \"alias\": \"localhost\"\n}\n
                                        * Response Payload: * List of all the details associated to a key managed by the framework

                                        The following JSON message is an example of an output provided:

                                        {\n    \"algorithm\": \"RSA\",\n    \"size\": 4096,\n    \"certificateChain\": [\n        \"-----BEGIN CERTIFICATE-----\\nMIIFkTCCA3mgAwIBAgIECtXoiDANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJJ\\nVDELMAkGA1UECBMCVUQxDjAMBgNVBAcTBUFtYXJvMREwDwYDVQQKEwhFdXJvdGVj\\naDEMMAoGA1UECxMDRVNGMQwwCgYDVQQDEwNFU0YwHhcNMjEwNDIyMTUxNTU1WhcN\\nMjQwMTE3MTUxNTU1WjBZMQswCQYDVQQGEwJJVDELMAkGA1UECBMCVUQxDjAMBgNV\\nBAcTBUFtYXJvMREwDwYDVQQKEwhFdXJvdGVjaDEMMAoGA1UECxMDRVNGMQwwCgYD\\nVQQDEwNFU0YwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7iZ3fHUQa\\nTPgnvSxGZK4f6MZYfLclD74yqaCCWAztNxPQoiBoSPGdsBGBLNeFbwY0Yzg3qwXw\\nYvgzLJmoXV9rSix7LgXPzsSYfUGfu7PeYTy5bG9X2UVyw9LloUM5DKnw++5F7Xy7\\nF0KQQi0z6/HbbPkZ2aGyNRtMCTh1iAGy3gDh/mMnjpUYuoq1luoX1x6I77X0C+NP\\nTxldVYrTeQiswItAHZmkK1R8AYedbFBgjDuTrfRODxBwESn4kQSMLJ8yHYDRm8S6\\ngVz5LdkcM48UiV5hhF+bCD3UvYA00ZgZm2oOG1ONchYrE7pJr7eQVCYaXkS1lALB\\nKaVJzn03wiLJJv1FYLmGt5J/MwfqyCtBTLlieEVfwnxFCkymtews6SYK32e9q/uJ\\nfcdpWH7tOoarnAf7j5mE84rRU3HqzghK0bMxntfrSH3t18ZUt1/4Qx78WfiM1Te3\\nJtnWBqUNJtX6lgT8IxTWwyEqD183tyKIo8hPGyeJrzWA5RL5hYF5rCNTWzqz5Upi\\n0b/YI5K09+Rn8XmEzzaWjFq5zu6/WpqwPRA8kc2RAEA2scnOT+3yl9Lof/M7BrfL\\nMdjVOZ4MfXgl/fhFyd16AObXuZRUIeiWowKtEiNaiUn8paLDxG+LNV7p5wEQCZZI\\n+MsXMMp6G8Te4yILLCcGov7OkO2wx4GPWQIDAQABo2EwXzAxBgNVHSUEKjAoBggr\\nBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDCDALBgNVHQ8EBAMC\\nAvwwHQYDVR0OBBYEFKM5PlHoe8qFC6w0quGacazGWE/LMA0GCSqGSIb3DQEBCwUA\\nA4ICAQBvpXmbS9LN8n0A+uq+tM3CNtF3YotWRbQHIGJAFTvdq3003W3CVdmykFc8\\n9Kz8PoY1swBJms7GKjQLkqgTHoq6jU/cIXw+CoLQWmvAugva5C1u/5AHJZqTC06J\\nGZyn1Z9N5Lp0XcgogEyhxdbkHniv7jvcmbCurQijZc9nsd5St7e1pT0Co7KKI6Ff\\nODdVP6kZYBzKo4t20tATdAZJ8t7YHNKNq7ZVs1ej9oYUmmQieNXuE4UoHe5hzVQw\\n567cNHWcTHJoyPve03TSQV91wp5rRUKZm2p0WtFNuv22f5p5sQmttsJltzHCgTwE\\nK0j6qYKnXiq+EQs0A3uF9uiIB/KEDLjxscstqsQGFCFOmjA3GSbmJiKCnss3HkNn\\naknT7XCV6tqgDOfPnNzbWJODjYZ+V0DyNY5uqkG2cyREm/qGbH1kLEXhqdWbKqEs\\nsdW6x8p0ImTaPuRl3XEmXbolavIq+FTtOSz8vW1PsdD3quO6krrwiQMXKv1ZMjup\\nDGIZZ4hUUhN84efjlZyoFRvPRvZ8YvjjrHXLij0vcRxndlicevwl5ezlm0LBOpsT\\nkI2uWrbSbxlue/XdgwFCbN0+mXX88fGj6cjhpvd/xnwHaDHfSG9UoU149LJb6ZIZ\\nru+07QriQQxK8V7AdPr6bhmKPxbbFenvSQmsmgjAY93qtanbNg==\\n-----END CERTIFICATE-----\"\n    ],\n    \"keystoreServicePid\": \"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\": \"localhost\",\n    \"type\": \"PRIVATE_KEY\"\n}\n
                                        "},{"location":"references/mqtt-namespace/#create-csr","title":"Create CSR","text":"

                                        This operation returns the CSR for a specific key pair managed by the framework

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/csr
                                        • Request Payload:

                                          • A JSON with the all the details necessary to generate the CSR
                                          { \n    \"keystoreServicePid\":\"org.eclipse.kura.core.keystore.HttpsKeystore\",\n    \"alias\":\"localhost\",\n    \"signatureAlgorithm\" : \"SHA256withRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                                        • Response Payload:

                                          • The generated CSR in the body of the message
                                          -----BEGIN CERTIFICATE REQUEST-----\nMIIEgTCCAmkCAQAwPDELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VjbGlwc2UxDDAK\nBgNVBAsTA0lvVDENMAsGA1UEAxMES3VyYTCCAiIwDQYJKoZIhvcNAQEBBQADggIP\nADCCAgoCggIBAICTNbBm2wIV/TvddB3OW2s2WJmhAOBxwDSdpxGpgWDzmFAydCt5\nSfWCIeC0kmQfrJpcvcIB7IoE2I7HWtIOxV9c+E+n6R76NvdBQzB8enFfZu4ahIKy\nul2VXQSj0VtYLZvG3yx6af4j8UFWsf2AuAe5Fd1dSBq9aEoRU/D5/uNQOQJi45Hk\nds1KK0FcTkfPjugUCLf1Uf0xXnK1V7yZGrgDpPDbZAYCrcsGomdziO8zkE88gKaa\noC1madGL44yz5tiHTKvbf+O+fKc31N4iDvnIg8f87IMF0D4afDF+3AJjVfcFtp3Q\nxWP3zpKqzPzpzWagTzsW446YMxamZgkDxLsVLitQtesom4ON3HT8s+jxHQhCO5LR\n83Ge10+6viJtkp20GYCqANO85c3TaD9njOE0y8P/T7Nk8MwnBbVgwa15QEWRqjEd\nHB6dF5jKdxlfZhPe2AVnLWAd/W96tCIBSqYu6TTH8npprp/S4t10tRkpaLGa+24c\nVlsjR6AFUX4KksvE/mbXd9QsvKgw/h3g4Jly4W/Ourt1LAH19tzGwULNCS7Ft9rp\nIXUsbmUUwb0V3B3ptcJUDzPUw8LdbItPnXzaPegxmkHO8IllcrdRBXrpcTwJl1ug\nMTMWKW/UjUwKcNQ0mGIxQ18aS0mHk8x8bVTnYLcCnGq3NeiFWvOiJIJpAgMBAAGg\nADANBgkqhkiG9w0BAQsFAAOCAgEATsHVZAEjkMSpwozWbVvDw4iJOSYaQ7ZJXhGZ\nn81puMy/kcdNVD2hfG2c4ern8KPib6hYd1mbQpyNtsbJ68VOPIYOdiaqFd7+lbtM\nIVNETBA9ezXzzXwPCtiJYpmeDYz6HfIzRRzuoJhZtOrgyw8v5wiM0NkenDbTQs4l\nOd/YPFlHnEDkTNM+B/ZJJxRIg3sPhAAgj5HH0Mj2053z66hLDYAo4Tos98MwUcuA\ndY1pcs3brxg6z7xz4vbNKyj0Lh8Gua92OSbl1AFZYb6KXm/7+Md0la/YD+K/E2n6\nhUAcHkr3ayNuTI6lkQFptCHzb4Zr8rdbu63JRno9PFTnW+fa/0xi35DoHD2SAhwA\nCUGXTR+HQXkzB/9NE9X0TxS8SwyrE8sfw4usZm25tACdZ33xziqJXOmbChETyL2b\nJ1IcbsHaeN2Shjnj7UQj+hQFnjVwRLTd0zWMN/l7mPj6TiW9ehubE8ce5siHW7NO\nmqJU1bklxTefefSNHTXrvTInuDXT81gLBRE3x+6uqU2kkJnL8jkrkebDDBhYF+qO\n6dB4W5WGbEHxorX2qfjImvy2Ohsl3rL/DqJgqECZaubTz1Xcj/kl9bdxs0pfa6IY\nInre5iom9bGcA6W6U34jRsrE2pobi6c9Yimrbr/R2O/8Oy2k94FQta8tg8jbAxBi\nZ0Vd1nM=\n-----END CERTIFICATE REQUEST-----\n
                                        "},{"location":"references/mqtt-namespace/#store-trusted-certificate","title":"Store Trusted Certificate","text":"

                                        This operation stores the provided certificate as a Trusted Certificate Entry managed by the framework

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/certificate
                                        • Request Payload:

                                          • A JSON with the all the details necessary to generate create the new entry
                                          {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"myCertTest99\",\n    \"certificate\":\"-----BEGIN CERTIFICATE-----\n        MIIDdzCCAl+gAwIBAgIEQsO0gDANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdV\n        bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n        VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du\n        MB4XDTIxMDQxNDA4MDIyOFoXDTIxMDcxMzA4MDIyOFowbDEQMA4GA1UEBhMHVW5r\n        bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE\n        ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC\n        ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJSWJDxu8UNC4JGOgK31WCvz\n        NKy2ONH+jTVKnBY7Ckb1hljJY0sKO55aG1HNDfkev2lJTsPIz0nJjNsqBvB1flvf\n        r6XVCxdN0yxvU5g9SpRxE/iiPX0Qt7463OfzyKW97haJrrhF005RHYNcORMY/Phj\n        hFDnZhtAwpbQLzq2UuIZ7okJsx0IgRbjH71ZZuvYCqG7Ct/bp1D7w3tT7gTbIKYH\n        ppQyG9rJDEh9+cr9Hyk8Gz7aAbPT/wMH+/vXDjH2j/M1Tmed0ajuGCJumaTQ4eHs\n        9xW3B3ugycb6e7Osl/4ESRO5RQL1k2GBONv10OrKDoZ5b66xwSJmC/w3BRWQ1cMC\n        AwEAAaMhMB8wHQYDVR0OBBYEFPospETb5HNeD/DmS9mwt+v/AYq/MA0GCSqGSIb3\n        DQEBCwUAA4IBAQBxMe1xQVQKt36A5qVlEZyxI9eb6eQRlYzorOgP2tFaOsvDPpRI\n        CALhPmxgQl/5QvKFfCXKoxWj1Spg4sF6fJp6jhSjLpmChS9lf5fRaWS20/pxIddM\n        10diq3r6HxLKSxCYK7Pf5scOeZquvwfo8Kxye01bvCMFf1s1K3ZEZszk5Oo2MnWU\n        U22YnXfZm1C0h2WMUcou35A7CeVAHPWI0Rvefojv1qYlQScJOkCN5lO6C/1qvRhq\n        nDQdQN/m1HQbpfh2DD6F33nBjkyLQyMRF8uMnspLrLLj8lecSTJZO4fGJOaIXh3O\n        44da9A02FAf5nRRQpwP2x/4IZ5RTRBzrqbqD\n        -----END CERTIFICATE-----\"\n}\n
                                        • Response Payload:

                                          • Nothing
                                        "},{"location":"references/mqtt-namespace/#generate-keypair","title":"Generate KeyPair","text":"

                                        This operation will generate a new key pair directly in the device, based on the parameters received from the request

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/POST/keystores/entries/keypair
                                        • Request Payload:

                                          • A JSON with the all the details necessary to create the new key pair
                                          {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"algorithm\" : \"RSA\",\n    \"size\": 1024,\n    \"signatureAlgorithm\" : \"SHA256WithRSA\",\n    \"attributes\" : \"CN=Kura, OU=IoT, O=Eclipse, C=US\"\n}\n
                                        • Response Payload:

                                          • Nothing
                                        "},{"location":"references/mqtt-namespace/#delete-entry","title":"Delete Entry","text":"

                                        This operation will delete the specified entry from the framework managed keystores

                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V1/DEL/keystores/entries
                                        • Request Payload:

                                          • A JSON with the all the details necessary to identify the entry to be deleted
                                          {\n    \"keystoreServicePid\" : \"MyKeystore\",\n    \"alias\" : \"mycerttestec\"\n}\n
                                        • Response Payload:

                                          • Nothing
                                        "},{"location":"references/mqtt-namespace/#keys-v2","title":"KEYS-V2","text":"

                                        Starting from Kura 5.4.0, the KEYS-V2 request handler is also available, it supports all of the request endpoints of KEYS-V1 plus an additional endpoint that allows to upload and modify private key entries:

                                        "},{"location":"references/mqtt-namespace/#uploading-a-private-key-entry","title":"Uploading a Private Key Entry","text":"
                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V2/POST/keystores/entries/privatekey
                                        • Request Payload:

                                          The request should include the private key in unencrypted PEM format and the certificate chain in PEM format, the first certificate in the certificateChain list must use the public key associated with the private key supplied as the privateKey parameter.

                                          The device will overwrite the entry with the provided alias if it already exists.

                                          {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"privateKey\":\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                                        • Response Payload:

                                          • Nothing
                                        "},{"location":"references/mqtt-namespace/#updating-a-private-key-entry","title":"Updating a Private Key Entry","text":"
                                        • Request Topic:

                                          • $EDC/account_name/client_id/KEYS-V2/POST/keystores/entries/privatekey
                                        • Request Payload:

                                          In order to update the certificate chain associated to a specific private key entry it is possible to use the same format as previous request and omit the privateKey parameter.

                                          In this case the certificate chain of the existing entry will be replaced with the one specified in the request and the existing private key will be retained.

                                          This request can be useful for example to create a CSR on the device, sign it externally and then updating the corresponding entry with the resulting certificate.

                                          {\n    \"keystoreServicePid\":\"MyKeystore\",\n    \"alias\":\"keypair1\",\n    \"certificateChain\":[\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n        \"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    ]\n}\n
                                        • Response Payload:

                                          • Nothing
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/","title":"Rest CloudConnection v1 API","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: CLD-V1.

                                        The CloudConnectionRestService APIs provides methods to manage cloud connection related components like CloudEndpoint, CloudPublisher and CloudSubscriber instances.

                                        Identities with rest.cloudconnection permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#find-cloud-component-instances","title":"Find Cloud Component Instances","text":"
                                        • Description: This method returns all the Cloud Component instances, including CloudEndpoint, CloudPublisher and CloudSubscriber instances.
                                        • Method: GET
                                        • API PATH: services/cloudconnection/v1/instances
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses","title":"Responses","text":"
                                        • 200 Ok Status
                                          {\n    \"cloudEndpointInstances\": [\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-todelete\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-2\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-3\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-test\",\n            \"state\": \"DISCONNECTED\",\n            \"cloudEndpointType\": \"CLOUD_CONNECTION_MANAGER\"\n        }\n    ],\n    \"pubsubInstances\": [\n        {\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"pid\": \"testPub\",\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"type\": \"PUBLISHER\"\n        },\n        {\n            \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"pid\": \"testPub\",\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"type\": \"SUBSCRIBER\"\n        }\n    ]\n}\n
                                        • cloudEndpointType: The possible values are:

                                          • CLOUD_CONNECTION_MANAGER if the component implements CloudConnectionManager

                                          • CLOUD_ENDPOINT otherwise.

                                        If cloudEndpointType is CLOUD_CONNECTION_MANAGER, it is possible to use the cloudEndpoint/connect, cloudEndpoint/disconnect and cloudEndpoint/isConnected methods to manage the connection state.

                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-cloudcomponent-factories","title":"Get CloudComponent Factories","text":"
                                        • Description: This method returns all the Factories able to create Component for CloudEndpoint, including CloudConnectionFactory, CloudPublisher, CloudSubscriber
                                        • Method: GET
                                        • API PATH: services/cloudconnection/v1/factories
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_1","title":"Responses","text":"
                                        • 200 Ok Status
                                          {\n    \"cloudConnectionFactories\": [\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService-2\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloud.CloudService\\\\-[a-zA-Z0-9]+$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.CloudEndpoint\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.cloudconnection.raw.mqtt.CloudEndpoint(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.camel.cloud.factory.CamelFactory\",\n            \"defaultCloudEndpointPid\": \"org.eclipse.kura.camel.cloud.factory.CamelFactory\",\n            \"cloudEndpointPidRegex\": \"^org.eclipse.kura.camel.cloud.factory.CamelFactory(\\\\-[a-zA-Z0-9]+)?$\"\n        }\n    ],\n    \"pubSubFactories\": [\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.subscriber.RawMqttSubscriber\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.ConnectionManager\",\n            \"defaultPid\": \"org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher\",\n            \"defaultPidRegex\": \"^org.eclipse.kura.cloudconnection.eclipseiot.mqtt.CloudPublisher(\\\\-[a-zA-Z0-9]+)?$\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloud.subscriber.CloudSubscriber\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.publisher.RawMqttPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\"\n        },\n        {\n            \"factoryPid\": \"org.eclipse.kura.event.publisher.EventPublisher\",\n            \"cloudConnectionFactoryPid\": \"org.eclipse.kura.cloud.CloudService\"\n        }\n    ]\n}\n

                                        For cloudConnectionFactories elements:

                                        • cloudConnectionFactoryPid: the PID of the cloud connection factory
                                        • defaultCloudEndpointPid: If set, it represents the default PID for an instance of a new component suggested by the factory. This can be used by an user interface as a suggestion/placeholder for the name of a new cloud endpoint.
                                        • cloudEndpointPidRegex: If set, its value represents a regular expression that the PID of a new component must match.

                                        For pubSubFactories elements:

                                        • factoryPid: The factory PID of the publisher/subscripter component. It identifies the component type. It can be used for example to create a new component using the pubSub POST method.
                                        • cloudConnectionFactoryPid: Specifies the cloudConnectionFactoryPid of the CloudConnectionFactory associated with this component. Each publisher/subscriber component is only compatible with CloudEndpoint instances created by the factory having this cloudConnectionFactoryPid.
                                        • defaultPid: If set, it represents the default PID for an instance of a new component suggested by the factory. This can be used by an user interface as a suggestion/placeholder for the name of a new publisher/subscriber.
                                        • defaultPidRegex: If set, its value represents a regular expression that the PID of a new component must match.

                                        • 500 Internal Server error

                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#get-stackcomponents-pids","title":"Get StackComponents Pids","text":"
                                        • Description: This method retrieves all all PIDs of Component instances that make up the stack for a specific CloudEndpoint. Examples of such components can be CloudService, DataService, DataTransportService.
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint/stackComponentPids
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request","title":"Request","text":"
                                        {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\": \"org.eclipse.kura.cloud.CloudService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_2","title":"Responses","text":"
                                        • 200 Ok Status
                                          {\n    \"pids\": [\n        \"org.eclipse.kura.cloud.CloudService\",\n        \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n        \"org.eclipse.kura.data.DataService\"\n    ]\n}\n
                                        • 404 if cloudConnectionFactoryPid or cloudEndpointPid are not found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#create-cloud-endpoint","title":"Create Cloud Endpoint","text":"
                                        • Description: This method create a new CloudEndpoint. The CloudConnectionFactory will always create a CloudEndpoint instance with the given cloudEndpointPid and optionally other associated stack components, for example CloudService, DataService, DataTransportService.
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_1","title":"Request","text":"
                                        {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService-1\"\n}\n
                                        • cloudEndpointPid: The cloudEndpointPid of the new instance that will be created. If the associated factory specifies the cloudEndpointPidRegex property, this parameter must match the provided regex.
                                        • cloudConnectionFactoryPid: The cloudConnectionFactoryPid of the factory that should be used to create the new component.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_3","title":"Responses","text":"
                                        • 204 Ok Status
                                        • 400 malformed cloudConnectionFactoryPid or cloudEndpointPid.
                                        • 404 Wrong cloudConnectionFactoryPid
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#create-publishersubscriber-instance","title":"Create Publisher/Subscriber instance","text":"
                                        • Description: his method create a new CloudPublisher or a CloudSubscriber instance for a specific CloudEndpoint CloudEndpoint. The type of the instance depends from the type of the specified factory.
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/pubSub
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_2","title":"Request","text":"
                                        {\n    \"pid\" : \"testPub\",\n    \"factoryPid\" : \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                                        • pid: The PID of the new publisher/subscriber component. If the publisher/subscriber factory specifies the defaultPidRegex property, this parameter must match the provided regex.
                                        • factoryPid: The factoryPid of the publisher/subscriber factory.
                                        • cloudEndpointPid: The PID of the CloudEndpoint that the new publisher/subscriber component will be associated with. The associated CloudEndpoint must have been created by the CloudConnectionFactory with the cloudConnectionFactoryPid specified by the publisher/subscriber factory.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_4","title":"Responses","text":"
                                        • 204 Ok Status
                                        • 400 malformed pid, factoryPid or cloudEndpointPid
                                        • 404 If factoryPid or cloudEndpointPid are wrong.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#get-configurations","title":"Get Configurations","text":"
                                        • Description: This method retrieves the complete configuration, including the metatype description, of a cloud component instance. This method will return the configuration of CloudPublisher instances, CloudSubscriber instances, or the configuration of components that are part of a cloud stack, whose pids are returned by the cloudEndpoint/stackComponentPids method.
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/configurations
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_3","title":"Request","text":"
                                        {\n    \"pids\" : [\"testPub\", \"org.eclipse.kura.cloud.CloudService\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_5","title":"Responses","text":"

                                        {\n    \"configs\": [\n        {\n            \"pid\": \"testPub\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"Application Id\",\n                        \"description\": \"The application id used to publish messages.\",\n                        \"id\": \"appId\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"W1\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Application Topic\",\n                        \"description\": \"Follows the application Id and specifies the rest of the publishing topic. Wildcards can be defined in the topic by specifing a $value in the field. The publisher will try to match \\\"value\\\" with a corresponding property in the received KuraMessage. If possible, the $value placeholder will be substituted with the real value specified in the KuraMessage received from the user application.\",\n                        \"id\": \"app.topic\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"A1/$assetName\",\n                        \"isRequired\": false\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"0\",\n                                \"value\": \"0\"\n                            },\n                            {\n                                \"label\": \"1\",\n                                \"value\": \"1\"\n                            }\n                        ],\n                        \"name\": \"Qos\",\n                        \"description\": \"The desired quality of service for the messages that have to be published. If Qos is 0, the message is delivered at most once, or it is not delivered at all. If Qos is set to 1, the message is always delivered at least once.\",\n                        \"id\": \"qos\",\n                        \"type\": \"INTEGER\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"0\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Retain\",\n                        \"description\": \"Default retaing flag for the published messages.\",\n                        \"id\": \"retain\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Data\",\n                                \"value\": \"data\"\n                            },\n                            {\n                                \"label\": \"Control\",\n                                \"value\": \"control\"\n                            }\n                        ],\n                        \"name\": \"Kind of Message\",\n                        \"description\": \"Type of message to be published.\",\n                        \"id\": \"message.type\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"data\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Priority\",\n                        \"description\": \"Message priority. Priority level 0 (highest) should be used sparingly and reserved for messages that should be sent with the minimum latency. Default is set to 7.\",\n                        \"id\": \"priority\",\n                        \"type\": \"INTEGER\",\n                        \"cardinality\": 0,\n                        \"min\": \"0\",\n                        \"defaultValue\": \"7\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"CloudPublisher\",\n                \"description\": \"The CloudPublisher allows to define publishing parameters and provide a simple endpoint where the applications can attach to publish their messages.\",\n                \"id\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\"\n            },\n            \"properties\": {\n                \"app.topic\": {\n                    \"value\": \"A1/$assetName\",\n                    \"type\": \"STRING\"\n                },\n                \"message.type\": {\n                    \"value\": \"data\",\n                    \"type\": \"STRING\"\n                },\n                \"qos\": {\n                    \"value\": 0,\n                    \"type\": \"INTEGER\"\n                },\n                \"appId\": {\n                    \"value\": \"W1\",\n                    \"type\": \"STRING\"\n                },\n                \"retain\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"priority\": {\n                    \"value\": 7,\n                    \"type\": \"INTEGER\"\n                },\n                \"service.factoryPid\": {\n                    \"value\": \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n                    \"type\": \"STRING\"\n                },\n                \"cloud.endpoint.service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"testPub\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.publisher.CloudPublisher-1699623980455-20\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.cloud.CloudService\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Set display name as device name\",\n                                \"value\": \"device-name\"\n                            },\n                            {\n                                \"label\": \"Set display name from hostname\",\n                                \"value\": \"hostname\"\n                            },\n                            {\n                                \"label\": \"Custom\",\n                                \"value\": \"custom\"\n                            },\n                            {\n                                \"label\": \"Server defined\",\n                                \"value\": \"server\"\n                            }\n                        ],\n                        \"name\": \"Device Display-Name\",\n                        \"description\": \"Friendly name of the device. Device name is the common name of the device (eg: Reliagate 20-25, Raspberry Pi, etc.). Hostname will use the linux hostname utility.                  Custom allows for defining a unique string. Server defined relies on the remote management server to define a name.\",\n                        \"id\": \"device.display-name\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"device-name\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Device Custom-Name\",\n                        \"description\": \"Custom name for the device. This value is applied ONLY if device.display-name is set to \\\"Custom\\\"\",\n                        \"id\": \"device.custom-name\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"isRequired\": false\n                    },\n                    {\n                        \"name\": \"Topic Control-Prefix\",\n                        \"description\": \"Topic prefix for system and device management messages.\",\n                        \"id\": \"topic.control-prefix\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"$EDC\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Encode gzip\",\n                        \"description\": \"Compress message payloads before sending them to the remote server to reduce the network traffic.\",\n                        \"id\": \"encode.gzip\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": false\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Gps Lock\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on GPS lock event\",\n                        \"id\": \"republish.mqtt.birth.cert.on.gps.lock\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Modem Detect\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on modem detection event\",\n                        \"id\": \"republish.mqtt.birth.cert.on.modem.detect\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"false\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Republish Mqtt Birth Cert On Tamper Event\",\n                        \"description\": \"Whether or not to republish the MQTT Birth Certificate on a tamper event. This has effect only if a TamperDetectionService is available in the framework.\",\n                        \"id\": \"republish.mqtt.birth.cert.on.tamper.event\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"Enable Default Subscriptions\",\n                        \"description\": \"Manages the default subscriptions to the gateway management MQTT topics. When disabled, the gateway will not be remotely manageable.\",\n                        \"id\": \"enable.default.subscriptions\",\n                        \"type\": \"BOOLEAN\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"true\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"option\": [\n                            {\n                                \"label\": \"Kura Protobuf\",\n                                \"value\": \"kura-protobuf\"\n                            },\n                            {\n                                \"label\": \"Simple JSON\",\n                                \"value\": \"simple-json\"\n                            }\n                        ],\n                        \"name\": \"Payload Encoding\",\n                        \"description\": \"Specify the message payload encoding.\",\n                        \"id\": \"payload.encoding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"kura-protobuf\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"icon\": [\n                    {\n                        \"resource\": \"CloudService\",\n                        \"size\": 32\n                    }\n                ],\n                \"name\": \"CloudService\",\n                \"description\": \"The CloudService allows for setting a user friendly name for the current device. It also provides the option to compress message payloads to reduce network traffic.\",\n                \"id\": \"org.eclipse.kura.cloud.CloudService\"\n            },\n            \"properties\": {\n                \"topic.control-prefix\": {\n                    \"value\": \"$EDC\",\n                    \"type\": \"STRING\"\n                },\n                \"republish.mqtt.birth.cert.on.tamper.event\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"device.custom-name\": {\n                    \"value\": \"Intel UP\u00b2\",\n                    \"type\": \"STRING\"\n                },\n                \"device.display-name\": {\n                    \"value\": \"device-name\",\n                    \"type\": \"STRING\"\n                },\n                \"payload.encoding\": {\n                    \"value\": \"kura-protobuf\",\n                    \"type\": \"STRING\"\n                },\n                \"republish.mqtt.birth.cert.on.modem.detect\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"service.factoryPid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.cloud.CloudService-1699623980404-13\",\n                    \"type\": \"STRING\"\n                },\n                \"enable.default.subscriptions\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"republish.mqtt.birth.cert.on.gps.lock\": {\n                    \"value\": false,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"encode.gzip\": {\n                    \"value\": true,\n                    \"type\": \"BOOLEAN\"\n                },\n                \"DataService.target\": {\n                    \"value\": \"(kura.service.pid=org.eclipse.kura.data.DataService)\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.cloud.service.factory.pid\": {\n                    \"value\": \"org.eclipse.kura.core.cloud.factory.DefaultCloudServiceFactory\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ]\n}\n
                                        - 200 Status OK - 500 Internal Error

                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#connect-cloudendpoint","title":"Connect CloudEndpoint","text":"
                                        • Description: This method allows to trigger the connection of the specified CloudEndpoint
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint/connect
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_4","title":"Request","text":"
                                        {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_6","title":"Responses","text":"
                                        • 204 Status OK
                                        • 404 Wrong cloudEndpointPid
                                        • 500 Internal Server Error in case of connection problems.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#disconnect-cloudendpoint","title":"Disconnect CloudEndpoint","text":"
                                        • Description: This method allows to trigger the disconnection of the specified CloudEndpoint
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint/disconnect
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_5","title":"Request","text":"
                                        {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_7","title":"Responses","text":"
                                        • 204 Status OK
                                        • 404 Wrong cloudEndpointPid
                                        • 500 Internal Server Error in case of connection problems.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#check-cloudendpoint-connection-status","title":"Check CloudEndpoint connection status","text":"
                                        • Description: This method allows to check the status of the connection of the specified CloudEndpoint
                                        • Method: POST
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint/isConnected
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_6","title":"Request","text":"
                                        {\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_8","title":"Responses","text":"

                                        {\n    \"connected\": false\n}\n
                                        - 200 Status OK - 404 Wrong cloudEndpointPid - 500 Internal Server Error

                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#update-cloudendpoint-stack-component-configurations","title":"Update CloudEndpoint stack component configurations","text":"
                                        • Description: This method allows to update the configuration of a cloud component instance. This method can be used to update the configuration of CloudPublisher instances, CloudSubscriber instances, or the configuration of components that are part of a cloud stack, whose pids are returned by the cloudEndpoint/stackComponentPids method.
                                        • Method: PUT
                                        • API PATH: services/cloudconnection/v1/configurations
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_7","title":"Request","text":"
                                        • takeSnapshot set to true o false to specify if the ConfigurationService must save a snapshot with the updated configuration.
                                        {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n            \"properties\": {\n                \"broker-url\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"mqtt://mqtt.eclipseprojects.io:1883\"\n                },\n                \"topic.context.account-name\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"account-name-testX2\"\n                },\n                \"username\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"username\"\n                },\n                \"password\": {\n                    \"type\": \"PASSWORD\",\n                    \"value\": \"Placeholder\"\n                },\n                \"client-id\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"\"\n                },\n                \"keep-alive\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 30\n                },\n                \"timeout\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 20\n                },\n                \"clean-session\": {\n                    \"type\": \"BOOLEAN\",\n                    \"value\": true\n                },\n                \"lwt.topic\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"$EDC/#account-name/#client-id/MQTT/LWT\"\n                },\n                \"lwt.payload\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"\"\n                },\n                \"lwt.qos\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 0\n                },\n                \"lwt.retain\": {\n                    \"type\": \"BOOLEAN\",\n                    \"value\": false\n                },\n                \"in-flight.persistence\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"memory\"\n                },\n                \"protocol-version\": {\n                    \"type\": \"INTEGER\",\n                    \"value\": 4\n                },\n                \"SslManagerService.target\": {\n                    \"type\": \"STRING\",\n                    \"value\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\"\n                }\n            }\n        }\n    ],\n    \"takeSnapshot\" : true\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_9","title":"Responses","text":"
                                        • 204 Status OK
                                        • 400 Bad request if pid is wrong or non existent.
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-cloudendpoint","title":"Delete CloudEndpoint","text":"
                                        • Description: This method allow to delete a CloudEndpoint instance.
                                        • Method: DELETE
                                        • API PATH: services/cloudconnection/v1/cloudEndpoint
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_8","title":"Request","text":"
                                        {\n    \"cloudConnectionFactoryPid\" : \"org.eclipse.kura.cloud.CloudService\",\n    \"cloudEndpointPid\" : \"org.eclipse.kura.cloud.CloudService-1\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_10","title":"Responses","text":"
                                        • 204 Status OK
                                        • 404 if cloudConnectionFactoryPid or cloudEndpointPid are not found
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#delete-publishersubscriber-instance","title":"Delete Publisher/Subscriber instance","text":"
                                        • Description: This method allows to delete a CloudPublisher or CloudSubscriber instance.
                                        • Method: DELETE
                                        • API PATH: services/cloudconnection/v1/pubSub
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#request_9","title":"Request","text":"
                                        {\n    \"pid\" : \"testPub\"\n}\n
                                        "},{"location":"references/rest-apis/rest-cloudconnection-api/#responses_11","title":"Responses","text":"
                                        • 204 Status OK
                                        • 404 Wrong cloudConnectionFactoryPid or cloudEndpointPid.
                                        "},{"location":"references/rest-apis/rest-command-api/","title":"Rest Command v1 API","text":""},{"location":"references/rest-apis/rest-command-api/#execute-command","title":"Execute Command","text":"
                                        • Method: POST
                                        • API PATH: /services/command/v1/command/
                                        "},{"location":"references/rest-apis/rest-command-api/#request-body","title":"Request Body","text":"
                                        {\n    //Command to be executed on gateway\n    \"command\":\"printenv TextEnvVarName1\",\n\n    //Service Password for command Service\n    \"password\":\"s3curePassw0rd\",\n\n    //String base64 encoding of a zip file to transfer to gateway\n    \"zipBytes\": \"UEsDBAoACAAAAIyD1lYAA AAAAAAAAAAAAAAJACAAdGVzdGZpbGUxVVQNAAfprpRk6a6UZOmulGR1eAsAAQT1AQAABBQAAABQSwcIAAAAAAAAAAAAAAAAUEsBAgoDCgAIAAAAjIPWVgAAAAAAAAAAAAAAAAkAIAAAAAAAAAAAAKSBAAAAAHRlc3RmaWxlMVVUDQAH6a6UZOmulGTprpRkdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEAVwAAAFcAAAAAAA==\",\n\n    //Command argument String array\n    \"arguments\":[\"arg 1\"],\n\n    //Shell environment Pairs Map\n    \"environmentPairs\": \n    {\n        \"TextEnvVarName1\":\"TextEnvVarValue1\",\n        \"TextEnvVarName2\":\"TextEnvVarValue2\"\n    },\n    //Working directory of command to be executed\n    \"workingDirectory\":\"/tmp\",\n\n}\n
                                        "},{"location":"references/rest-apis/rest-command-api/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"stdout\": \"Command error output is displayed in this field\",\n    \"stderr\": \"Command output is displayed in this field\",\n    \"exitCode\": 0,\n    \"isTimeOut\": false\n}\n
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-command-api/#execute-asynchronous-command","title":"Execute Asynchronous Command","text":"
                                        • Method: POST
                                        • API PATH: /services/command/v1/command/async
                                        "},{"location":"references/rest-apis/rest-command-api/#request-body_1","title":"Request Body","text":"
                                        {\n    //Command to be executed on gateway\n    \"command\":\"printenv TextEnvVarName1\",\n\n    //Service Password for command Service\n    \"password\":\"s3curePassw0rd\",\n\n    //String base64 encoding of a zip file to transfer to gateway\n    \"zipBytes\": \"UEsDBAoACAAAAIyD1lYAA AAAAAAAAAAAAAAJACAAdGVzdGZpbGUxVVQNAAfprpRk6a6UZOmulGR1eAsAAQT1AQAABBQAAABQSwcIAAAAAAAAAAAAAAAAUEsBAgoDCgAIAAAAjIPWVgAAAAAAAAAAAAAAAAkAIAAAAAAAAAAAAKSBAAAAAHRlc3RmaWxlMVVUDQAH6a6UZOmulGTprpRkdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEAVwAAAFcAAAAAAA==\",\n\n    //Command argument String array\n    \"arguments\":[\"arg 1\"],\n\n    //Shell environment Pairs Map\n    \"environmentPairs\": \n    {\n        \"TextEnvVarName1\":\"TextEnvVarValue1\",\n        \"TextEnvVarName2\":\"TextEnvVarValue2\"\n    },\n    //Working directory of command to be executed\n    \"workingDirectory\":\"/tmp\",\n\n}\n
                                        "},{"location":"references/rest-apis/rest-command-api/#responses_1","title":"Responses","text":"
                                        • 202 Accepted
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error

                                        Note

                                        Use the following command to retrieve the base64 representation of a zip file. base64 -i <filename.zip>

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/","title":"Configuration V1 REST APIs","text":"

                                        This page describes the configuration V1 rest APIs.

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#rest-apis","title":"REST APIs","text":"

                                        The Configuration Service REST APIs are exposed by the org.eclipse.kura.rest.configuration bundle, providing the following REST APIs under the /configuration/v1 path.

                                        Method Path Allowed roles Encoding Request parameters Description GET /factoryComponents configuration JSON None The method lists all the FactoryComponents Pids tracked by the ConfigurationService POST /factoryComponents configuration JSON FactoryComponentConfiguration object Creates a new ConfigurableComponent instance by creating a new configuration from a Configuration Admin factory. The FactoryComponentConfiguration object passed as request parameter will provide all the information needed to generate the instance. It links the factory Pid to be used, the target instance Pid, the properties to be used when creating the instance, and if the request should be persisted with a snapshot DELETE /factoryComponents/{pid}?takeSnapshot={takeSnapshot} configuration JSON pid = A String representing the pid of the instance generated by a Factory Component that needs to deleted; takeSnapshot = an optional (default false) boolean to specify if a new snapshot needs to be created after the delete operation For the specified Pid and optional takeSnapshot query parameter, the ConfigurationService instance will delete the corresponding ConfigurableComponent instance GET /configurableComponents configuration JSON None Lists the tracked configurable component Pids GET /configurableComponents/configurations configuration JSON None Lists all the component configurations of all the ConfigurableComponents tracked by the ConfigurationService GET /configurableComponents/configurations/byFilter/{filter} configuration JSON filter = A String representing an OSGi filter. Lists the component configurations of all the ConfigurableComponents tracked by the ConfigurationService that match the filter specified GET /configurableComponents/configurations/byPid/{pid} configuration JSON pid = A String representing the pid of a configurable component instance Provides the ComponentConfiguration of the ConfigurableComponent matching the specified Pid GET /configurableComponents/configurations/byPid/{pid}/_default configuration JSON pid = A String representing the pid of a configurable component instance Provides the default Component Configuration for the component identified by the specified Pid POST /configurableComponents/configurations/byPid/{pid}/_update configuration JSON pid = A String representing the pid of a configurable component instance; ComponentConfigurationUpdateRequest = the updated configuration provided in the request body Allows to update the component configuration identified by the provided PID POST /configurableComponents/configurations/_update configuration JSON componentConfigurations = the list of updated configurations provided in the request body Allows to update the configuration of multiple configurable components GET /snapshots configuration JSON None Lists all the available snapshot IDs managed by the framework GET /snapshots/{id} configuration JSON id = the snapshot Id Returns the content of a given snapshot tracked by the framework POST /snapshots/_write configuration JSON None Triggers the framework to take and persist a snapshot POST /snapshots/_rollback configuration JSON None Rollbacks the framework to the last saved snapshot if available. POST /snapshots/{id}/_rollback configuration JSON id = the snapshot Id Rollbacks the framework to the snapshot identified by the provided ID POST /snapshots/_upload configuration Consumes: XML Framework snapshot in XML form provided in the request body Uploads a snapshot. The framework will update the component(s) configuration accordingly to the configurations received"},{"location":"references/rest-apis/rest-configuration-service-v1/#get-all-the-factory-components-pids","title":"Get all the factory components pids","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents

                                        Response:

                                        [\n    \"org.eclipse.kura.wire.Conditional\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.publisher.RawMqttPublisher\",\n    \"org.eclipse.kura.misc.cloudcat.CloudCat\",\n    \"org.eclipse.kura.core.db.H2DbServer\",\n    \"org.eclipse.kura.wire.Fifo\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.cloud.RawMqttCloudEndpoint\",\n    \"org.eclipse.kura.core.keystore.FilesystemKeystoreServiceImpl\",\n    \"org.eclipse.kura.cloud.publisher.CloudPublisher\",\n    \"org.eclipse.kura.core.db.H2DbService\",\n    \"org.eclipse.kura.wire.CloudSubscriber\",\n    \"org.eclipse.kura.wire.RegexFilter\",\n    \"org.eclipse.kura.wire.Logger\",\n    \"org.eclipse.kura.wire.Timer\",\n    \"com.eurotech.framework.log.manager.LogManager\",\n    \"com.eurotech.framework.log.journald.wire.JournaldWireComponent\",\n    \"org.eclipse.kura.cloudconnection.raw.mqtt.subscriber.RawMqttSubscriber\",\n    \"org.eclipse.kura.cloud.subscriber.CloudSubscriber\",\n    \"org.eclipse.kura.ssl.SslManagerService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"com.eurotech.framework.log.journald.JournaldLogReader\",\n    \"org.eclipse.kura.provisioning.ProvisioningService\",\n    \"org.eclipse.kura.wire.CloudPublisher\",\n    \"org.eclipse.kura.wire.H2DbWireRecordFilter\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"org.eclipse.kura.data.DataService\",\n    \"org.eclipse.kura.wire.H2DbWireRecordStore\",\n    \"org.eclipse.kura.wire.Join\",\n    \"com.eurotech.framework.log.publisher.LogPublisher\"\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#create-component-from-factory","title":"Create component from factory","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents

                                        Request body:

                                        {\n    \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n    \"pid\": \"myH2DbServer\",\n    \"properties\" : [],\n    \"takeSnapshot\" : false\n}\n

                                        The request must be provided with the following elements:

                                        • factoryPid: the factory used to generate the new instance
                                        • pid: the new instance process id
                                        • properties: eventual properties that will be used to instantiate the new instance and will override the defaults. A list of string, object pair is needed and can be optionally empty.
                                        • takeSnapshot: specifies if after the creation of a new component a snapshot needs to be taken.
                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#delete-a-component-and-optionally-take-a-snapshot","title":"Delete a component and optionally take a snapshot","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/factoryComponents/{pid}?takeSnapshot={takeSnapshot}

                                        • takeSnapshot: can be either true or false. If set to true, after the deletion, the framework will take a snapshot.
                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-the-tracked-configurable-component-pids","title":"List the tracked configurable component Pids","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents

                                        Response:

                                        [\n    \"org.eclipse.kura.clock.ClockService\",\n    \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n    \"org.eclipse.kura.position.PositionService\",\n    \"com.eurotech.framework.internal.ansible.provider.AnsibleServiceImpl\",\n    \"org.eclipse.kura.internal.useradmin.store.RoleRepositoryStoreImpl\",\n    \"com.eurotech.framework.internal.ansible.cloud.AnsibleActivityHandler\",\n    \"default.diagnostic.publisher\",\n    \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n    \"com.eurotech.framework.internal.fail2ban.Fail2BanConfigurator\",\n    \"default.log.publisher\",\n    \"org.eclipse.kura.wire.graph.WireGraphService\",\n    \"com.eurotech.framework.internal.floodingprotection.FloodingProtectionConfigurator\",\n    \"org.eclipse.kura.ssl.SslManagerService\",\n    \"org.eclipse.kura.http.server.manager.HttpService\",\n    \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n    \"default.ping.publisher\",\n    \"org.eclipse.kura.db.H2DbService\",\n    \"com.eurotech.framework.diagnostics.DiagnosticsService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"org.eclipse.kura.deployment.agent\",\n    \"DMKeystore\",\n    \"LogReaderJournald\",\n    \"default.alert.publisher\",\n    \"org.eclipse.kura.core.deployment.CloudDeploymentHandlerV2\",\n    \"HttpsKeystore\",\n    \"com.eurotech.framework.security.aide.AideTamperDetectionServiceConfigurator\",\n    \"org.eclipse.kura.provisioning.ProvisioningService\",\n    \"LogManagerAuth\",\n    \"com.eurotech.framework.security.journald.fss.FssTamperDetectionServiceConfigurator\",\n    \"org.eclipse.kura.watchdog.WatchdogService\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"LogManagerActivity\",\n    \"org.eclipse.kura.data.DataService\",\n    \"LogManagerDefault\",\n    \"org.eclipse.kura.web.Console\",\n    \"SSLKeystore\",\n    \"com.eurotech.framework.net.vpn.client.VpnClient\",\n    \"org.eclipse.kura.internal.rest.provider.RestService\",\n    \"com.eurotech.framework.reboot.RebootService\"\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#lists-all-the-component-configurations-of-all-the-configurablecomponents","title":"Lists all the component configurations of all the ConfigurableComponents","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations

                                        Response:

                                        [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"definition\": {\n            \"ad\": [\n                {\n                    \"option\": [],\n                    \"name\": \"enabled\",\n                    \"description\": \"Whether or not to enable the ClockService\",\n                    \"id\": \"enabled\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.set.hwclock\",\n                    \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                    \"id\": \"clock.set.hwclock\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [\n                        {\n                            \"label\": \"java-ntp\",\n                            \"value\": \"java-ntp\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"ntpd\",\n                            \"value\": \"ntpd\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"chrony-advanced\",\n                            \"value\": \"chrony-advanced\",\n                            \"otherAttributes\": {}\n                        }\n                    ],\n                    \"name\": \"clock.provider\",\n                    \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                    \"id\": \"clock.provider\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"java-ntp\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.host\",\n                    \"description\": \"The hostname that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.host\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"0.pool.ntp.org\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.port\",\n                    \"description\": \"The port number that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.port\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"max\": \"65535\",\n                    \"_default\": \"123\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.timeout\",\n                    \"description\": \"The NTP timeout in milliseconds\",\n                    \"id\": \"clock.ntp.timeout\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1000\",\n                    \"_default\": \"10000\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.max-retry\",\n                    \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                    \"id\": \"clock.ntp.max-retry\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"0\",\n                    \"_default\": \"0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.retry.interval\",\n                    \"description\": \"When sync fails, interval in seconds between each retry.\",\n                    \"id\": \"clock.ntp.retry.interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"_default\": \"5\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.refresh-interval\",\n                    \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                    \"id\": \"clock.ntp.refresh-interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"_default\": \"3600\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"RTC File Name\",\n                    \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                    \"id\": \"rtc.filename\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"/dev/rtc0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"Chrony Configuration\",\n                    \"description\": \"Chrony configuration file.|TextArea\",\n                    \"id\": \"chrony.advanced.config\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"required\": false,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"icon\": [\n                {\n                    \"resource\": \"ClockService\",\n                    \"size\": 32,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"name\": \"ClockService\",\n            \"description\": \"ClockService Configuration\",\n            \"id\": \"org.eclipse.kura.clock.ClockService\",\n            \"otherAttributes\": {}\n        },\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": false\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"chrony.advanced.config\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"\"\n            }\n        }\n    },\n  ...\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-the-configurations-of-all-the-configurablecomponents-that-match-a-filter","title":"List the configurations of all the ConfigurableComponents that match a filter","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byFilter/(service.pid=org.eclipse.kura.clock.ClockService)

                                        Response:

                                        [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"definition\": {\n            \"ad\": [\n                {\n                    \"option\": [],\n                    \"name\": \"enabled\",\n                    \"description\": \"Whether or not to enable the ClockService\",\n                    \"id\": \"enabled\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.set.hwclock\",\n                    \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                    \"id\": \"clock.set.hwclock\",\n                    \"type\": \"BOOLEAN\",\n                    \"cardinality\": 0,\n                    \"_default\": \"true\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [\n                        {\n                            \"label\": \"java-ntp\",\n                            \"value\": \"java-ntp\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"ntpd\",\n                            \"value\": \"ntpd\",\n                            \"otherAttributes\": {}\n                        },\n                        {\n                            \"label\": \"chrony-advanced\",\n                            \"value\": \"chrony-advanced\",\n                            \"otherAttributes\": {}\n                        }\n                    ],\n                    \"name\": \"clock.provider\",\n                    \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                    \"id\": \"clock.provider\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"java-ntp\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.host\",\n                    \"description\": \"The hostname that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.host\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"0.pool.ntp.org\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.port\",\n                    \"description\": \"The port number that provides the system time via NTP\",\n                    \"id\": \"clock.ntp.port\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"max\": \"65535\",\n                    \"_default\": \"123\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.timeout\",\n                    \"description\": \"The NTP timeout in milliseconds\",\n                    \"id\": \"clock.ntp.timeout\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1000\",\n                    \"_default\": \"10000\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.max-retry\",\n                    \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                    \"id\": \"clock.ntp.max-retry\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"0\",\n                    \"_default\": \"0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.retry.interval\",\n                    \"description\": \"When sync fails, interval in seconds between each retry.\",\n                    \"id\": \"clock.ntp.retry.interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"min\": \"1\",\n                    \"_default\": \"5\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"clock.ntp.refresh-interval\",\n                    \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                    \"id\": \"clock.ntp.refresh-interval\",\n                    \"type\": \"INTEGER\",\n                    \"cardinality\": 0,\n                    \"_default\": \"3600\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"RTC File Name\",\n                    \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                    \"id\": \"rtc.filename\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"_default\": \"/dev/rtc0\",\n                    \"required\": true,\n                    \"otherAttributes\": {}\n                },\n                {\n                    \"option\": [],\n                    \"name\": \"Chrony Configuration\",\n                    \"description\": \"Chrony configuration file.|TextArea\",\n                    \"id\": \"chrony.advanced.config\",\n                    \"type\": \"STRING\",\n                    \"cardinality\": 0,\n                    \"required\": false,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"icon\": [\n                {\n                    \"resource\": \"ClockService\",\n                    \"size\": 32,\n                    \"otherAttributes\": {}\n                }\n            ],\n            \"name\": \"ClockService\",\n            \"description\": \"ClockService Configuration\",\n            \"id\": \"org.eclipse.kura.clock.ClockService\",\n            \"otherAttributes\": {}\n        },\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": false\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"chrony.advanced.config\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"\"\n            }\n        }\n    }\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-configuration-of-the-configurablecomponent-matching-a-pid","title":"Get the configuration of the ConfigurableComponent matching a pid","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService

                                        Response:

                                        {\n    \"pid\": \"org.eclipse.kura.clock.ClockService\",\n    \"definition\": {\n        \"ad\": [\n            {\n                \"option\": [],\n                \"name\": \"enabled\",\n                \"description\": \"Whether or not to enable the ClockService\",\n                \"id\": \"enabled\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.set.hwclock\",\n                \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                \"id\": \"clock.set.hwclock\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [\n                    {\n                        \"label\": \"java-ntp\",\n                        \"value\": \"java-ntp\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"ntpd\",\n                        \"value\": \"ntpd\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"chrony-advanced\",\n                        \"value\": \"chrony-advanced\",\n                        \"otherAttributes\": {}\n                    }\n                ],\n                \"name\": \"clock.provider\",\n                \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                \"id\": \"clock.provider\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"java-ntp\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.host\",\n                \"description\": \"The hostname that provides the system time via NTP\",\n                \"id\": \"clock.ntp.host\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"0.pool.ntp.org\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.port\",\n                \"description\": \"The port number that provides the system time via NTP\",\n                \"id\": \"clock.ntp.port\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"max\": \"65535\",\n                \"_default\": \"123\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.timeout\",\n                \"description\": \"The NTP timeout in milliseconds\",\n                \"id\": \"clock.ntp.timeout\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1000\",\n                \"_default\": \"10000\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.max-retry\",\n                \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                \"id\": \"clock.ntp.max-retry\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"0\",\n                \"_default\": \"0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.retry.interval\",\n                \"description\": \"When sync fails, interval in seconds between each retry.\",\n                \"id\": \"clock.ntp.retry.interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"_default\": \"5\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.refresh-interval\",\n                \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                \"id\": \"clock.ntp.refresh-interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"_default\": \"3600\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"RTC File Name\",\n                \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                \"id\": \"rtc.filename\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"/dev/rtc0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"Chrony Configuration\",\n                \"description\": \"Chrony configuration file.|TextArea\",\n                \"id\": \"chrony.advanced.config\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"required\": false,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"icon\": [\n            {\n                \"resource\": \"ClockService\",\n                \"size\": 32,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"name\": \"ClockService\",\n        \"description\": \"ClockService Configuration\",\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"otherAttributes\": {}\n    },\n    \"properties\": {\n        \"clock.ntp.host\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.ntp.max-retry\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 0\n        },\n        \"clock.set.hwclock\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"clock.ntp.timeout\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 10000\n        },\n        \"enabled\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": false\n        },\n        \"clock.ntp.retry.interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 5\n        },\n        \"kura.service.pid\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"service.pid\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"clock.ntp.port\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 123\n        },\n        \"clock.provider\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"java-ntp\"\n        },\n        \"clock.ntp.refresh-interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 3600\n        },\n        \"rtc.filename\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"/dev/rtc1\"\n        },\n        \"chrony.advanced.config\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"\"\n        }\n    }\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-default-configuration-of-the-configurablecomponent-matching-a-pid","title":"Get the default configuration of the ConfigurableComponent matching a pid","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService/_default

                                        Response:

                                        {\n    \"pid\": \"org.eclipse.kura.clock.ClockService\",\n    \"definition\": {\n        \"ad\": [\n            {\n                \"option\": [],\n                \"name\": \"enabled\",\n                \"description\": \"Whether or not to enable the ClockService\",\n                \"id\": \"enabled\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.set.hwclock\",\n                \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n                \"id\": \"clock.set.hwclock\",\n                \"type\": \"BOOLEAN\",\n                \"cardinality\": 0,\n                \"_default\": \"true\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [\n                    {\n                        \"label\": \"java-ntp\",\n                        \"value\": \"java-ntp\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"ntpd\",\n                        \"value\": \"ntpd\",\n                        \"otherAttributes\": {}\n                    },\n                    {\n                        \"label\": \"chrony-advanced\",\n                        \"value\": \"chrony-advanced\",\n                        \"otherAttributes\": {}\n                    }\n                ],\n                \"name\": \"clock.provider\",\n                \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n                \"id\": \"clock.provider\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"java-ntp\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.host\",\n                \"description\": \"The hostname that provides the system time via NTP\",\n                \"id\": \"clock.ntp.host\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"0.pool.ntp.org\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.port\",\n                \"description\": \"The port number that provides the system time via NTP\",\n                \"id\": \"clock.ntp.port\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"max\": \"65535\",\n                \"_default\": \"123\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.timeout\",\n                \"description\": \"The NTP timeout in milliseconds\",\n                \"id\": \"clock.ntp.timeout\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1000\",\n                \"_default\": \"10000\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.max-retry\",\n                \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n                \"id\": \"clock.ntp.max-retry\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"0\",\n                \"_default\": \"0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.retry.interval\",\n                \"description\": \"When sync fails, interval in seconds between each retry.\",\n                \"id\": \"clock.ntp.retry.interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"min\": \"1\",\n                \"_default\": \"5\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"clock.ntp.refresh-interval\",\n                \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n                \"id\": \"clock.ntp.refresh-interval\",\n                \"type\": \"INTEGER\",\n                \"cardinality\": 0,\n                \"_default\": \"3600\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"RTC File Name\",\n                \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n                \"id\": \"rtc.filename\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"_default\": \"/dev/rtc0\",\n                \"required\": true,\n                \"otherAttributes\": {}\n            },\n            {\n                \"option\": [],\n                \"name\": \"Chrony Configuration\",\n                \"description\": \"Chrony configuration file.|TextArea\",\n                \"id\": \"chrony.advanced.config\",\n                \"type\": \"STRING\",\n                \"cardinality\": 0,\n                \"required\": false,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"icon\": [\n            {\n                \"resource\": \"ClockService\",\n                \"size\": 32,\n                \"otherAttributes\": {}\n            }\n        ],\n        \"name\": \"ClockService\",\n        \"description\": \"ClockService Configuration\",\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"otherAttributes\": {}\n    },\n    \"properties\": {\n        \"clock.ntp.host\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.provider\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"java-ntp\"\n        },\n        \"clock.ntp.port\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 123\n        },\n        \"clock.ntp.max-retry\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 0\n        },\n        \"clock.ntp.refresh-interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 3600\n        },\n        \"rtc.filename\": {\n            \"array\": false,\n            \"type\": \"String\",\n            \"value\": \"/dev/rtc0\"\n        },\n        \"clock.set.hwclock\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"enabled\": {\n            \"array\": false,\n            \"type\": \"Boolean\",\n            \"value\": true\n        },\n        \"clock.ntp.timeout\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 10000\n        },\n        \"clock.ntp.retry.interval\": {\n            \"array\": false,\n            \"type\": \"Integer\",\n            \"value\": 5\n        }\n    }\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#update-the-component-configuration-identified-matching-a-pid","title":"Update the component configuration identified matching a pid","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/byPid/org.eclipse.kura.clock.ClockService/_update

                                        Request body:

                                        {\n    \"takeSnapshot\":true,\n    \"componentConfigurationRequest\": {\n        \"properties\": {\n            \"enabled\": {\n                \"type\": \"boolean\",\n                \"value\": false,\n                \"array\": false\n            }\n        }\n    }\n}\n

                                        Warning

                                        Every service may need a different set of parameters and combination of them to reach the desired result. For the NetworkAdminService, for example, the following configuration is required to disable a specific network interface (enp2s0) and prevent re-enabling at reboot. Please verify offline the REST APIs executed and the different deploying scenarios before distributing updates to the fleet of devices.

                                        {\n    \"takeSnapshot\":true,\n    \"componentConfigurationRequest\": {\n        \"properties\": {\n            \"net.interface.enp2s0.config.ip4.status\": {\n                \"type\": \"string\",\n                \"value\": \"netIPv4StatusDisabled\",\n                \"array\": false\n            },\n            \"net.interface.enp2s0.config.autoconnect\": {\n                \"type\": \"boolean\",\n                \"value\": false,\n                \"array\": false\n            }\n        }\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#update-the-configuration-of-multiple-configurable-components","title":"Update the configuration of multiple configurable components","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/configurableComponents/configurations/_update

                                        Request body:

                                        {\n    \"takeSnapshot\": true,\n    \"componentConfigurations\": [\n        {\n            \"pid\": \"org.eclipse.kura.clock.ClockService\",\n            \"properties\": {\n                \"enabled\": {\n                    \"type\": \"boolean\",\n                    \"value\": false,\n                    \"array\": false\n                }\n            }\n        }\n    ]\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#list-all-the-available-snapshot-ids","title":"List all the available snapshot IDs","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots

                                        Response:

                                        [\n    0,\n    1630930775789,\n    1630930776355,\n    1630930776839,\n    1630930797402,\n    1630930805305\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#get-the-content-of-a-given-snapshot","title":"Get the content of a given snapshot","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/{id}

                                        Response:

                                        [\n    {\n        \"pid\": \"org.eclipse.kura.clock.ClockService\",\n        \"properties\": {\n            \"clock.ntp.host\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"0.pool.ntp.org\"\n            },\n            \"clock.ntp.port\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 123\n            },\n            \"clock.provider\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"java-ntp\"\n            },\n            \"clock.ntp.max-retry\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 0\n            },\n            \"clock.ntp.refresh-interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 3600\n            },\n            \"rtc.filename\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"/dev/rtc1\"\n            },\n            \"clock.set.hwclock\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.timeout\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 10000\n            },\n            \"enabled\": {\n                \"array\": false,\n                \"type\": \"Boolean\",\n                \"value\": true\n            },\n            \"clock.ntp.retry.interval\": {\n                \"array\": false,\n                \"type\": \"Integer\",\n                \"value\": 5\n            },\n            \"kura.service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            },\n            \"service.pid\": {\n                \"array\": false,\n                \"type\": \"String\",\n                \"value\": \"org.eclipse.kura.clock.ClockService\"\n            }\n        }\n    },\n  ...\n]\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#trigger-the-framework-to-take-and-persist-a-snapshot","title":"Trigger the framework to take and persist a snapshot","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_write

                                        Response:

                                        1631095409516\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#rollbacks-to-the-snapshot-identified-by-the-provided-id","title":"Rollbacks to the snapshot identified by the provided ID","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_rollback Response:

                                        1631093011618\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v1/#upload-a-snapshot-as-xml","title":"Upload a snapshot as XML","text":"

                                        Request: URL - https://<gateway-ip>/services/configuration/v1/snapshots/_upload

                                        Request body:

                                        <?xml version=\"1.0\" encoding=\"UTF-8\"?><esf:configurations xmlns:esf=\"http://eurotech.com/esf/2.0\" xmlns:ocd=\"http://www.osgi.org/xmlns/metatype/v1.2.0\">\n    <esf:configuration pid=\"org.eclipse.kura.clock.ClockService\">\n        <esf:properties>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.host\" type=\"String\">\n                <esf:value>0.pool.ntp.org</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.port\" type=\"Integer\">\n                <esf:value>123</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.provider\" type=\"String\">\n                <esf:value>java-ntp</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.max-retry\" type=\"Integer\">\n                <esf:value>0</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.refresh-interval\" type=\"Integer\">\n                <esf:value>3600</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"rtc.filename\" type=\"String\">\n                <esf:value>/dev/rtc1</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.set.hwclock\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.timeout\" type=\"Integer\">\n                <esf:value>10000</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"enabled\" type=\"Boolean\">\n                <esf:value>true</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"clock.ntp.retry.interval\" type=\"Integer\">\n                <esf:value>5</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"kura.service.pid\" type=\"String\">\n                <esf:value>org.eclipse.kura.clock.ClockService</esf:value>\n            </esf:property>\n            <esf:property array=\"false\" encrypted=\"false\" name=\"service.pid\" type=\"String\">\n                <esf:value>org.eclipse.kura.clock.ClockService</esf:value>\n            </esf:property>\n        </esf:properties>\n    </esf:configuration>\n</esf:configurations>\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/","title":"Configuration V2 REST APIs and CONF-V2 Request Handler","text":"

                                        This page describes the CONF-V2 request handler and configuration/v2 rest APIs. Accessing the REST APIs requires to use an identity with the rest.configuration permission assigned.

                                        • Request definitions
                                          • GET/snapshots
                                          • GET/factoryComponents
                                          • POST/factoryComponents
                                          • DEL/factoryComponents/byPid
                                          • GET/factoryComponents/ocd
                                          • POST/factoryComponents/ocd/byFactoryPid
                                          • GET/configurableComponents
                                          • GET/configurableComponents/pidsWithFactory
                                          • GET/configurableComponents/configurations
                                          • POST/configurableComponents/configurations/byPid
                                          • POST/configurableComponents/configurations/byPid/_default
                                          • PUT/configurableComponents/configurations/_update
                                          • EXEC/snapshots/_write
                                          • EXEC/snapshots/_rollback
                                          • EXEC/snapshots/byId/_rollback
                                        • JSON definitions
                                          • PidAndFactoryPidSet
                                          • SnapshotIdSet
                                          • PropertyType
                                          • ConfigurationProperty
                                          • ConfigurationProperties
                                          • Option
                                          • AttributeDefinition
                                          • ObjectClassDefinition
                                          • ComponentConfiguration
                                          • ComponentConfigurationList
                                          • CreateFactoryComponentConfigurationsRequest
                                          • UpdateComponentConfigurationRequest
                                          • DeleteFactoryComponentConfigurationsRequest
                                          • PidSet
                                          • SnaphsotId
                                          • GenericFailureReport
                                          • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-configuration-service-v2/#getsnapshots","title":"GET/snapshots","text":"
                                        • REST API path : /services/configuration/v2/snapshots
                                        • description : Returns the ids of the snapshots currently stored on the device.
                                        • responses :
                                          • 200
                                          • description : The snapshot id set.
                                          • response body :
                                            • SnapshotIdSet
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#getfactorycomponents","title":"GET/factoryComponents","text":"
                                        • REST API path : /services/configuration/v2/factoryComponents
                                        • description : Returns the ids of the component factories available on the device.
                                        • responses :
                                          • 200
                                          • description : The factory pid set.
                                          • response body :
                                            • PidSet
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#postfactorycomponents","title":"POST/factoryComponents","text":"
                                        • REST API path : /services/configuration/v2/factoryComponents
                                        • description : This is a batch request that allows to create one or more factory component instances and optionally create a new snapshot.
                                        • request body :
                                          • CreateFactoryComponentConfigurationsRequest
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: create:$pid for component creation operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                                          • response body :

                                            Variants:

                                            • object
                                              • GenericFailureReport
                                            • object
                                              • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#delfactorycomponentsbypid","title":"DEL/factoryComponents/byPid","text":"
                                        • REST API path : /services/configuration/v2/factoryComponents/byPid
                                        • description : This is a batch request that allows to delete one or more factory component instances and optionally create a new snapshot.
                                        • request body :
                                          • DeleteFactoryComponentConfigurationsRequest
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for component delete operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                                          • response body :

                                            Variants:

                                            • object
                                              • GenericFailureReport
                                            • object
                                              • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#getfactorycomponentsocd","title":"GET/factoryComponents/ocd","text":"
                                        • REST API path : /services/configuration/v2/factoryComponents/ocd
                                        • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to all available factories.
                                        • responses :
                                          • 200
                                          • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present.
                                          • response body :
                                            • ComponentConfigurationList
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#postfactorycomponentsocdbyfactorypid","title":"POST/factoryComponents/ocd/byFactoryPid","text":"
                                        • REST API path : /services/configuration/v2/factoryComponents/ocd/byFactoryPid
                                        • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to a user selected set of factories.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                          • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present. If the OCD for a given factory pid cannot be found, it will not be included in the result.
                                          • response body :
                                            • ComponentConfigurationList
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponents","title":"GET/configurableComponents","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents
                                        • description : Returns the list of the pids available on the system.
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • response body :
                                            • PidSet
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponentspidswithfactory","title":"GET/configurableComponents/pidsWithFactory","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents/pidsWithFactory
                                        • description : Returns the list of the pids available on the system, reporting also the factory pid where applicable.
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • response body :
                                            • PidAndFactoryPidSet
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#getconfigurablecomponentsconfigurations","title":"GET/configurableComponents/configurations","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents/configurations
                                        • description : Returns all of component configurations available on the system. This request will return the pid, ocd and properties.
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • response body :
                                            • ComponentConfigurationList
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#postconfigurablecomponentsconfigurationsbypid","title":"POST/configurableComponents/configurations/byPid","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents/configurations/byPid
                                        • description : Returns a user selected set of configurations. This request will return the pid, ocd and properties.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                          • description : The request succeeded. If the configuration for a given pid cannot be found, it will not be included in the result.
                                          • response body :
                                            • ComponentConfigurationList
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#postconfigurablecomponentsconfigurationsbypid_default","title":"POST/configurableComponents/configurations/byPid/_default","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents/configurations/byPid/_default
                                        • description : Returns the default configuration for a given set of component pids. The default configurations are generated basing on component definition only, user applied modifications will not be taken into account. This request will return the pid, ocd and properties.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                          • description : The request succeeded. If the configuration for a given pid cannot be found, it will not be included in the result.
                                          • response body :
                                            • ComponentConfigurationList
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#putconfigurablecomponentsconfigurations_update","title":"PUT/configurableComponents/configurations/_update","text":"
                                        • REST API path : /services/configuration/v2/configurableComponents/configurations/_update
                                        • description : Updates a given set of component configurations. This request can be also used to apply a configuration snapshot.
                                        • request body :
                                          • UpdateComponentConfigurationRequest
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid for component update operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                                          • response body :

                                            Variants:

                                            • object
                                              • GenericFailureReport
                                            • object
                                              • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshots_write","title":"EXEC/snapshots/_write","text":"
                                        • REST API path : /services/configuration/v2/snapshots/_write
                                        • description : Requests the device to create a new snasphot based on the current defice configuration. If this request is used through REST API, the POST method must be used.
                                        • responses :
                                          • 200
                                          • description : The request succeeded. The result is the identifier of the new snsapsot
                                          • response body :
                                            • SnaphsotId
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshots_rollback","title":"EXEC/snapshots/_rollback","text":"
                                        • REST API path : /services/configuration/v2/snapshots/_rollback
                                        • description : Rollbacks the framework to the last saved snapshot if available. If this request is used through REST API, the POST method must be used.
                                        • responses :
                                          • 200
                                          • description : The request succeeded. The result contains the id of the snapshot used for rollback.
                                          • response body :
                                            • SnaphsotId
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#execsnapshotsbyid_rollback","title":"EXEC/snapshots/byId/_rollback","text":"
                                        • REST API path : /services/configuration/v2/snapshots/byId/_rollback
                                        • description : Performs a rollback to the snapshot id specified by the user.
                                        • responses :
                                          • 200
                                          • description : The request succeeded.
                                          • 400
                                          • description : The request body is not valid JSON or it contains invalid parameters.
                                          • response body :
                                            • GenericFailureReport
                                          • 500
                                          • description : An unexpected internal error occurred.
                                          • response body :
                                            • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-configuration-service-v2/#pidandfactorypidset","title":"PidAndFactoryPidSet","text":"

                                        Represents a set of pids with the corresponding factory pid. Properties:

                                        • components: array The set of pids and factory pids

                                          • array elements: object The pid and factory pid Properties:

                                          • pid: string The component pid.

                                          • factoryPid: string
                                            • optional Can be missing if the described component is not a factory instance. The factory pid
                                        {\n  \"components\": [\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbService\",\n      \"pid\": \"org.eclipse.kura.db.H2DbService\"\n    },\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n      \"pid\": \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\"\n    },\n    {\n      \"pid\": \"org.eclipse.kura.web.Console\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#snapshotidset","title":"SnapshotIdSet","text":"

                                        An object decribing a set of configuration snapshot ids Properties:

                                        • ids: array The set of snapshot ids.
                                          • array elements: number A snapshot id.
                                        {\n  \"ids\": [\n    0,\n    1638438049921,\n    1638438146960,\n    1638439710944,\n    1638439717931,\n    1638439734077,\n    1638439767252,\n    1638521986953,\n    1638521993692,\n    1638522572822\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#propertytype","title":"PropertyType","text":"

                                        A string that describes the type of a configuration property. * Possible values * STRING * LONG * DOUBLE * FLOAT * INTEGER * BYTE * CHAR * BOOLEAN * SHORT * PASSWORD

                                        \"STRING\"\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#configurationproperty","title":"ConfigurationProperty","text":"

                                        An object describing a configuration property. Properties:

                                        • type: string (enumerated)
                                          • PropertyType
                                        • value: variant
                                        • optional In requests, this field can be omitted or set to null to assign the null value to a non required configuration property. Describes the property value. The value type depends on the type property. Variants:

                                          • number If type is LONG, DOUBLE, FLOAT, INTEGER, BYTE or SHORT and the property does not represent an array.
                                          • string If type is STRING, PASSWORD or CHAR and the property is not an array. In case of CHAR type, the value must have a length of 1.
                                          • bool If type is BOOLEAN and the property is not an array
                                          • array If type is LONG, DOUBLE, FLOAT, INTEGER, BYTE or SHORT and the property represents an array.
                                            • array elements: number The property values as numbers.
                                          • array If type is STRING, PASSWORD or CHAR and the property is an array.
                                            • array elements: string The property values as strings. In case of CHAR type, the values must have a length of 1.
                                          • array If type is BOOLEAN and the property is an array
                                            • array elements: bool The property values as booleans.

                                        {\n  \"type\": \"STRING\",\n  \"value\": \"foo\"\n}\n
                                        {\n  \"type\": \"STRING\",\n  \"value\": [\n    \"foo\",\n    \"bar\"\n  ]\n}\n
                                        {\n  \"type\": \"INTEGER\",\n  \"value\": 12\n}\n
                                        {\n  \"type\": \"LONG\",\n  \"value\": [\n    1,\n    2,\n    3,\n    4\n  ]\n}\n
                                        {\n  \"type\": \"PASSWORD\",\n  \"value\": \"myPassword\"\n}\n
                                        {\n  \"type\": \"PASSWORD\",\n  \"value\": [\n    \"my\",\n    \"password\",\n    \"array\"\n  ]\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#configurationproperties","title":"ConfigurationProperties","text":"

                                        An object representing a set of configuration properties. The members of this object represent configuration property, the member names represent the configuration property ids. This object can have a variable number of members. Properties:

                                        • propertyName: object
                                          • ConfigurationProperty
                                        {\n  \"KeystoreService.target\": {\n    \"type\": \"STRING\",\n    \"value\": \"(kura.service.pid=HttpsKeystore)\"\n  },\n  \"https.client.auth.ports\": {\n    \"type\": \"INTEGER\",\n    \"value\": [\n      4443\n    ]\n  },\n  \"https.client.revocation.soft.fail\": {\n    \"type\": \"BOOLEAN\",\n    \"value\": false\n  },\n  \"https.ports\": {\n    \"type\": \"INTEGER\",\n    \"value\": [\n      443\n    ]\n  },\n  \"https.revocation.check.enabled\": {\n    \"type\": \"BOOLEAN\",\n    \"value\": false\n  },\n  \"kura.service.pid\": {\n    \"type\": \"STRING\",\n    \"value\": \"org.eclipse.kura.http.server.manager.HttpService\"\n  },\n  \"service.pid\": {\n    \"type\": \"STRING\",\n    \"value\": \"org.eclipse.kura.http.server.manager.HttpService\"\n  },\n  \"ssl.revocation.mode\": {\n    \"type\": \"STRING\",\n    \"value\": \"PREFER_OCSP\"\n  }\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#option","title":"Option","text":"

                                        An object describing an allowed element for a multi choiche field. Properties:

                                        • label: string
                                        • optional This parameter may not be specified by component configuration. An user friendly label for the option.
                                        • value: string The option value encoded as a string.

                                        {\n  \"label\": \"Value 1\",\n  \"value\": \"1\"\n}\n
                                        {\n  \"value\": \"foo\"\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#attributedefinition","title":"AttributeDefinition","text":"

                                        A descriptor of a configuration property. Properties:

                                        • id: string The id of the attribute definition. This field corresponds to the configuration property name.
                                        • type: string (enumerated)
                                          • PropertyType
                                        • name: string
                                        • optional This parameter may not be specified by component configuration. An user friendly name for the property.
                                        • description: string
                                        • optional This parameter may not be specified by component configuration. An user friendly description for the property.
                                        • cardinality: number An integer describing the property cardinality. If the value is 0, then the property is a singleton value (not an array), if it is > 0, then the configuration property is an array and this property specifies the maximum allowed array length.
                                        • min: string
                                        • optional This parameter may not be specified by component configuration. If not specified, the property does not have a minimum value. Specifies the minimum value for this property as a string.
                                        • max: string
                                        • optional This parameter may not be specified by component configuration. If not specified, the property does not have a maximum value. Specifies the maximum value for this property as a string.
                                        • isRequired: bool Specifies whether the configuration parameter is required or not.
                                        • defaultValue: string
                                        • optional This parameter may not be specified by component configuration. Specifies the default value for this property as a string.
                                        • option: array
                                        • optional If specified, describes a set of allowed values for the configuration property. The allowed values for this configuration properties
                                          • array elements: object
                                          • Option

                                        {\n  \"defaultValue\": \"false\",\n  \"description\": \"Specifies whether the DB server is enabled or not.\",\n  \"id\": \"db.server.enabled\",\n  \"isRequired\": true,\n  \"name\": \"db.server.enabled\",\n  \"type\": \"BOOLEAN\"\n}\n
                                        {\n  \"defaultValue\": \"TCP\",\n  \"description\": \"Specifies the server type, see http://www.h2database.com/javadoc/org/h2/tools/Server.html for more details.\",\n  \"id\": \"db.server.type\",\n  \"isRequired\": true,\n  \"name\": \"db.server.type\",\n  \"option\": [\n    {\n      \"label\": \"WEB\",\n      \"value\": \"WEB\"\n    },\n    {\n      \"label\": \"TCP\",\n      \"value\": \"TCP\"\n    },\n    {\n      \"label\": \"PG\",\n      \"value\": \"PG\"\n    }\n  ],\n  \"type\": \"STRING\"\n}\n

                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#objectclassdefinition","title":"ObjectClassDefinition","text":"

                                        Provides some metadata information about a component configuration. Properties:

                                        • ad: array The metadata about the configuration properties.
                                          • array elements: object
                                          • AttributeDefinition
                                        • icon: array
                                        • optional Can be missing if the OCD does not define icons. A list of icons that visually represent the configuration.

                                          • array elements: object

                                          Properties:

                                          • resource: string An identifier of the icon image resource.
                                          • size: number The icon width and height in pixels.
                                          • name: string A user friendly name for the component configuration.
                                          • description: string A user friendly description of the component configuration.
                                          • id: string An identifier of the component configuration.
                                        {\n  \"ad\": [\n    {\n      \"defaultValue\": \"false\",\n      \"description\": \"The WatchdogService monitors CriticalComponents and reboots the system if one of them hangs. Once enabled the WatchdogService starts refreshing the watchdog device, which will reset the system if WatchdogService hangs.\",\n      \"id\": \"enabled\",\n      \"isRequired\": true,\n      \"name\": \"Watchdog enable\",\n      \"type\": \"BOOLEAN\"\n    },\n    {\n      \"defaultValue\": \"10000\",\n      \"description\": \"WatchdogService's refresh interval in ms of the Watchdog device. The value can be set between 1 and 60 seconds and should not be set to a value greater or equal to the Watchdog device's timeout value\",\n      \"id\": \"pingInterval\",\n      \"isRequired\": true,\n      \"max\": \"60000\",\n      \"name\": \"Watchdog refresh interval\",\n      \"type\": \"INTEGER\"\n    },\n    {\n      \"defaultValue\": \"/dev/watchdog\",\n      \"description\": \"Watchdog device path e.g. /dev/watchdog.\",\n      \"id\": \"watchdogDevice\",\n      \"isRequired\": true,\n      \"name\": \"Watchdog device path\",\n      \"type\": \"STRING\"\n    },\n    {\n      \"defaultValue\": \"/opt/eclipse/kura/data/kura-reboot-cause\",\n      \"description\": \"The path for the file that will contain the reboot cause information.\",\n      \"id\": \"rebootCauseFilePath\",\n      \"isRequired\": true,\n      \"name\": \"Reboot Cause File Path\",\n      \"type\": \"STRING\"\n    }\n  ],\n  \"description\": \"The WatchdogService handles the hardware watchdog of the platform.  The parameter define the ping periodicity of the hardware watchdog to ensure it does not reboot. The WatchdogService will reset the watchdog timeout, can disable it (where supported) with the Magic Character, but cannot set the refresh rate of a watchdog device.\",\n  \"icon\": [\n    {\n      \"resource\": \"WatchdogService\",\n      \"size\": 32\n    }\n  ],\n  \"id\": \"org.eclipse.kura.watchdog.WatchdogService\",\n  \"name\": \"WatchdogService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#componentconfiguration","title":"ComponentConfiguration","text":"

                                        Describes a component configuration. Properties:

                                        • pid: string The identifier of this configuration.
                                        • ocd: object
                                        • optional Can be omitted in some requests and responses, see request documentation for more information.
                                          • ObjectClassDefinition
                                        • properties: object
                                        • optional Can be omitted in some requests and responses, see request documentation for more information.
                                          • ConfigurationProperties
                                        {\n  \"definition\": {\n    \"ad\": [\n      {\n        \"cardinality\": 3,\n        \"description\": \"If set to a non empty list, REST API access will be allowed only on the specified ports. If set to an empty list, access will be allowed on all ports. Please make sure that the allowed ports are open in HttpService and Firewall configuration.\",\n        \"id\": \"allowed.ports\",\n        \"isRequired\": false,\n        \"max\": \"65535\",\n        \"min\": \"1\",\n        \"name\": \"Allowed ports\",\n        \"type\": \"INTEGER\"\n      }\n    ],\n    \"description\": \"This service allows to configure settings related to Kura REST APIs\",\n    \"id\": \"org.eclipse.kura.internal.rest.provider.RestService\",\n    \"name\": \"RestService\"\n  },\n  \"pid\": \"org.eclipse.kura.internal.rest.provider.RestService\",\n  \"properties\": {\n    \"kura.service.pid\": {\n      \"type\": \"STRING\",\n      \"value\": \"org.eclipse.kura.internal.rest.provider.RestService\"\n    },\n    \"service.pid\": {\n      \"type\": \"STRING\",\n      \"value\": \"org.eclipse.kura.internal.rest.provider.RestService\"\n    }\n  }\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#componentconfigurationlist","title":"ComponentConfigurationList","text":"

                                        Represents a list of component configurations. Properties:

                                        • configs: array The component configurations
                                          • array elements: object
                                          • ComponentConfiguration
                                        {\n  \"configs\": [\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"defaultValue\": \"true\",\n            \"description\": \"Whether or not to enable the ClockService\",\n            \"id\": \"enabled\",\n            \"isRequired\": true,\n            \"name\": \"enabled\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"defaultValue\": \"true\",\n            \"description\": \"Whether or not to sync the system hardware clock after the system time gets set\",\n            \"id\": \"clock.set.hwclock\",\n            \"isRequired\": true,\n            \"name\": \"clock.set.hwclock\",\n            \"type\": \"BOOLEAN\"\n          },\n          {\n            \"defaultValue\": \"java-ntp\",\n            \"description\": \"Source for setting the system clock. Verify the availabiliy of the selected provider before activate it.\",\n            \"id\": \"clock.provider\",\n            \"isRequired\": true,\n            \"name\": \"clock.provider\",\n            \"option\": [\n              {\n                \"label\": \"java-ntp\",\n                \"value\": \"java-ntp\"\n              },\n              {\n                \"label\": \"ntpd\",\n                \"value\": \"ntpd\"\n              },\n              {\n                \"label\": \"chrony-advanced\",\n                \"value\": \"chrony-advanced\"\n              }\n            ],\n            \"type\": \"STRING\"\n          },\n          {\n            \"defaultValue\": \"0.pool.ntp.org\",\n            \"description\": \"The hostname that provides the system time via NTP\",\n            \"id\": \"clock.ntp.host\",\n            \"isRequired\": true,\n            \"name\": \"clock.ntp.host\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"defaultValue\": \"123\",\n            \"description\": \"The port number that provides the system time via NTP\",\n            \"id\": \"clock.ntp.port\",\n            \"isRequired\": true,\n            \"max\": \"65535\",\n            \"min\": \"1\",\n            \"name\": \"clock.ntp.port\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"10000\",\n            \"description\": \"The NTP timeout in milliseconds\",\n            \"id\": \"clock.ntp.timeout\",\n            \"isRequired\": true,\n            \"min\": \"1000\",\n            \"name\": \"clock.ntp.timeout\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"0\",\n            \"description\": \"The maximum number of retries for the initial synchronization (with interval clock.ntp.retry.interval). If set to 0 the service will retry forever.\",\n            \"id\": \"clock.ntp.max-retry\",\n            \"isRequired\": true,\n            \"min\": \"0\",\n            \"name\": \"clock.ntp.max-retry\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"5\",\n            \"description\": \"When sync fails, interval in seconds between each retry.\",\n            \"id\": \"clock.ntp.retry.interval\",\n            \"isRequired\": true,\n            \"min\": \"1\",\n            \"name\": \"clock.ntp.retry.interval\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"3600\",\n            \"description\": \"Whether or not to sync the clock and if so, the frequency in seconds.  If less than zero - no update, if equal to zero - sync once at startup, if greater than zero - the frequency in seconds to perform a new clock sync\",\n            \"id\": \"clock.ntp.refresh-interval\",\n            \"isRequired\": true,\n            \"name\": \"clock.ntp.refresh-interval\",\n            \"type\": \"INTEGER\"\n          },\n          {\n            \"defaultValue\": \"/dev/rtc0\",\n            \"description\": \"The RTC File Name. It defaults to /dev/rtc0. This option is not used if chrony-advanced option is selected in clock.provider.\",\n            \"id\": \"rtc.filename\",\n            \"isRequired\": true,\n            \"name\": \"RTC File Name\",\n            \"type\": \"STRING\"\n          },\n          {\n            \"description\": \"Chrony configuration file.|TextArea\",\n            \"id\": \"chrony.advanced.config\",\n            \"isRequired\": false,\n            \"name\": \"Chrony Configuration\",\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"ClockService Configuration\",\n        \"icon\": [\n          {\n            \"resource\": \"ClockService\",\n            \"size\": 32\n          }\n        ],\n        \"id\": \"org.eclipse.kura.clock.ClockService\",\n        \"name\": \"ClockService\"\n      },\n      \"pid\": \"org.eclipse.kura.clock.ClockService\",\n      \"properties\": {\n        \"clock.ntp.host\": {\n          \"type\": \"STRING\",\n          \"value\": \"0.pool.ntp.org\"\n        },\n        \"clock.ntp.max-retry\": {\n          \"type\": \"INTEGER\",\n          \"value\": 0\n        },\n        \"clock.ntp.port\": {\n          \"type\": \"INTEGER\",\n          \"value\": 123\n        },\n        \"clock.ntp.refresh-interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 3600\n        },\n        \"clock.ntp.retry.interval\": {\n          \"type\": \"INTEGER\",\n          \"value\": 5\n        },\n        \"clock.ntp.timeout\": {\n          \"type\": \"INTEGER\",\n          \"value\": 10000\n        },\n        \"clock.provider\": {\n          \"type\": \"STRING\",\n          \"value\": \"java-ntp\"\n        },\n        \"clock.set.hwclock\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"enabled\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.clock.ClockService\"\n        },\n        \"rtc.filename\": {\n          \"type\": \"STRING\",\n          \"value\": \"/dev/rtc0\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.clock.ClockService\"\n        }\n      }\n    },\n    {\n      \"definition\": {\n        \"ad\": [\n          {\n            \"defaultValue\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\",\n            \"description\": \"Specifies, as an OSGi target filter, the pid of the SslManagerService used to create SSL connections for downloading packages.\",\n            \"id\": \"SslManagerService.target\",\n            \"isRequired\": true,\n            \"name\": \"SslManagerService Target Filter\",\n            \"type\": \"STRING\"\n          }\n        ],\n        \"description\": \"This service is responsible of managing the deployment packages installed on the system.\",\n        \"id\": \"org.eclipse.kura.deployment.agent\",\n        \"name\": \"DeploymentAgent\"\n      },\n      \"pid\": \"org.eclipse.kura.deployment.agent\",\n      \"properties\": {\n        \"SslManagerService.target\": {\n          \"type\": \"STRING\",\n          \"value\": \"(kura.service.pid=org.eclipse.kura.ssl.SslManagerService)\"\n        },\n        \"kura.service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.deployment.agent\"\n        },\n        \"service.pid\": {\n          \"type\": \"STRING\",\n          \"value\": \"org.eclipse.kura.deployment.agent\"\n        }\n      }\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#createfactorycomponentconfigurationsrequest","title":"CreateFactoryComponentConfigurationsRequest","text":"

                                        An object describing a factory component instance creation request. Properties:

                                        • configs: array The set of configurations to be created

                                          • array elements: object An object describing a factory component confguration. Properties:

                                          • pid: string The component pid.

                                          • factoryPid: string The component factory pid
                                          • properties: object
                                            • optional If omitted, the component innstance will be created with default configuration.
                                            • ConfigurationProperties
                                          • takeSnapshot: bool
                                          • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the factory component configuration instances have been created.
                                        {\n  \"configs\": [\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n      \"pid\": \"testComponent\",\n      \"properties\": {\n        \"db.server.type\": {\n          \"type\": \"STRING\",\n          \"value\": \"WEB\"\n        }\n      }\n    },\n    {\n      \"factoryPid\": \"org.eclipse.kura.core.db.H2DbServer\",\n      \"pid\": \"thirdComponent\"\n    }\n  ],\n  \"takeSnapshot\": true\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#updatecomponentconfigurationrequest","title":"UpdateComponentConfigurationRequest","text":"

                                        An object that describes a set of configurations that need to be updated. Properties:

                                        • configs: array The configurations to be updated. The ocd field can be omitted, it will be ignored if specified.
                                          • array elements: object
                                          • ComponentConfiguration
                                        • takeSnapshot: bool
                                        • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the component configurations have been applied.
                                        {\n  \"configs\": [\n    {\n      \"pid\": \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n      \"properties\": {\n        \"command.enable\": {\n          \"type\": \"BOOLEAN\",\n          \"value\": true\n        },\n        \"command.timeout\": {\n          \"type\": \"INTEGER\",\n          \"value\": 60\n        }\n      }\n    },\n    {\n      \"pid\": \"org.eclipse.kura.position.PositionService\",\n      \"properties\": {\n        \"parity\": {\n          \"type\": \"STRING\",\n          \"value\": 0\n        }\n      }\n    }\n  ],\n  \"takeSnapshot\": true\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#deletefactorycomponentconfigurationsrequest","title":"DeleteFactoryComponentConfigurationsRequest","text":"

                                        An object describing a factory component instance delete request. Properties:

                                        • pids: array The list of the pids of the factory component instances to be deleted.
                                          • array elements: string The component pid.
                                        • takeSnapshot: bool
                                        • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the factory component configuration instances have been deleted.
                                        {\n  \"pids\": [\n    \"testComponent\",\n    \"otherComponent\"\n  ],\n  \"takeSnapshot\": true\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#pidset","title":"PidSet","text":"

                                        Represents a set of pids or factory pids. Properties:

                                        • pids: array The set of pids
                                          • array elements: string The pid
                                        {\n  \"pids\": [\n    \"org.eclipse.kura.deployment.agent\",\n    \"org.eclipse.kura.clock.ClockService\"\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#snaphsotid","title":"SnaphsotId","text":"

                                        An object describing the identifier of a configuration snapshot. Properties:

                                        • id: number The snapshot id
                                        {\n  \"id\": 163655959932\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#genericfailurereport","title":"GenericFailureReport","text":"

                                        An object reporting a failure message. Properties:

                                        • message: string A message describing the failure.
                                        {\n  \"message\": \"An unexpected error occurred.\"\n}\n
                                        "},{"location":"references/rest-apis/rest-configuration-service-v2/#batchfailurereport","title":"BatchFailureReport","text":"

                                        An object that is returned by requests that involve multiple operations, when at least one operation failed. This object report an error message for each of the operations that failed. The operations are identified by an id, see request documentation for more details. If an operation is not listed by this object, then the operation succeeded. Properties:

                                        • failures: array The list of operations that failed.

                                          • array elements: object An object reporting details about an operation failure. Properties:

                                          • id: string An identifier of the failed operation.

                                          • message: string A message describing the failure.
                                        {\n  \"failures\": [\n    {\n      \"id\": \"create:testComponent\",\n      \"message\": \"Invalid parameter. pid testComponent already exists\"\n    },\n    {\n      \"id\": \"create:otherComponent\",\n      \"message\": \"Invalid parameter. pid otherComponent already exists\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/","title":"Rest Deploy v2 API","text":"

                                        The DeploymentRestService APIs provides methods to manage the installed deployment packages. Identities with rest.deploy permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-deploy-api/#get-installed-packages","title":"Get installed packages","text":"
                                        • Description: Provides the list of all the deployment packages installed and tracked by the framework.
                                        • Method: GET
                                        • API PATH: /deploy/v2/
                                        "},{"location":"references/rest-apis/rest-deploy-api/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        [{ \"name\": \"packageName\", \"version\": \"packageVersion\"}]\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#install-package-from-url","title":"Install package from URL","text":"
                                        • Description: Installs the deployment package specified in the InstallRequest. If the request was already issued for the same InstallRequest, it returns the status of the installation process.
                                        • Method: POST
                                        • API PATH: /deploy/v2/_install
                                        "},{"location":"references/rest-apis/rest-deploy-api/#request-body","title":"Request Body","text":"
                                        {\n  \"url\": \"deploymentPackageUrl\"\n}\n

                                        Example:

                                        {\n  \"url\": \"http://download.eclipse.org/kura/releases/4.1.0/org.eclipse.kura.demo.heater_1.0.500.dp\"\n}\n

                                        Please note that the url can refer to a .dp already in the device filesystem.

                                        "},{"location":"references/rest-apis/rest-deploy-api/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad request
                                        \"REQUEST_RECEIVED\"\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#install-package-from-upload","title":"Install package from upload","text":"
                                        • Description: Upload and install a Deployment Package.
                                        • Method: POST
                                        • API PATH: /deploy/v2/_upload
                                        "},{"location":"references/rest-apis/rest-deploy-api/#request-body_1","title":"Request Body","text":"

                                        The POST request body should be encoded in the multipart/form-data enctype, thus allowing for the upload of the Deployment Package file. The uploaded file is expected to be added in the file field of the form.

                                        "},{"location":"references/rest-apis/rest-deploy-api/#headers","title":"Headers","text":"
                                        • Content-Type: multipart/form-data
                                        "},{"location":"references/rest-apis/rest-deploy-api/#body-formdata","title":"Body (formdata)","text":"
                                        • file
                                        "},{"location":"references/rest-apis/rest-deploy-api/#example","title":"Example","text":"

                                        Example using curl:

                                        curl -X POST -k -u $USERNAME:$PASSWORD \\\n    --header 'Content-Type: multipart/form-data' \\\n    --form 'file=@\"/path/to/your/file.dp\"' \\\n    https://$ADDRESS/services/deploy/v2/_upload\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#responses_2","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad request
                                        \"REQUEST_RECEIVED\"\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#uninstall-a-package","title":"Uninstall a package","text":"
                                        • Description: Uninstalls the deployment package identified by the specified name. If the request was already issued, it reports the status of the uninstallation operation.
                                        • Method: DELETE
                                        • API PATH: /deploy/v2/{name}
                                        "},{"location":"references/rest-apis/rest-deploy-api/#responses_3","title":"Responses","text":"
                                        • 200 OK status
                                        \"REQUEST_RECEIVED\"\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#get-eclipse-marketplace-package-descriptor","title":"Get Eclipse Marketplace Package Descriptor","text":"
                                        • Description: Provides the Eclipse Marketplace Package Descriptor information of the deployment package identified by URL passed in the request.
                                        • Method: PUT
                                        • API PATH: /deploy/v2/_packageDescriptor
                                        "},{"location":"references/rest-apis/rest-deploy-api/#request-body_2","title":"Request Body","text":"
                                        {\n  \"url\": \"deploymentPackageUrl\"\n}\n

                                        Example:

                                        {\n  \"url\": \"http://marketplace.eclipse.org/marketplace-client-intro?mpc_install=5514714\"\n}\n
                                        "},{"location":"references/rest-apis/rest-deploy-api/#responses_4","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad request
                                        {\n   \"nodeId\":\"5514714\",\n   \"url\":\"https://marketplace.eclipse.org/content/ai-wire-component-eclipse-kura-5\",\n   \"dpUrl\":\"https://download.eclipse.org/kura/releases/5.3.0/org.eclipse.kura.wire.ai.component.provider-1.2.0.dp\",\n   \"minKuraVersion\":\"5.1.0\",\n   \"maxKuraVersion\":\"\",\n   \"currentKuraVersion\":\"5.4.0\",\n   \"isCompatible\":true\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/","title":"Rest Identity V1 API","text":"

                                        Warning

                                        This API id deprecated and superseded by the Identity V2 REST APIs.

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: IDN-V1.

                                        The IdentityRestService APIs provides methods to manage the system identities. Unless otherwise specified, identities with rest.identity permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#create-user","title":"Create User","text":"
                                        • Description: This method allows to create a new user in the system.
                                        • Method: POST
                                        • API PATH: services/identity/v1/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#request","title":"Request","text":"
                                        {\n    \"userName\": \"username\",\n    \"password\": \"password\",\n    \"passwordChangeNeeded\": false,\n    \"passwordAuthEnabled\": true,\n    \"permissions\": [\n        \"rest.identity\"\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad Request (Password strenght requirements not satisfied)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#get-user-by-name","title":"Get User by Name","text":"
                                        • Description: This method allows to get data about an user in the system. The only considered field is the userName.
                                        • Method: POST
                                        • API PATH: services/identity/v1/identities/byName
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#request_1","title":"Request","text":"
                                        {\n    \"userName\": \"username\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_1","title":"Responses","text":"
                                        {\n    \"userName\": \"kura.user.username\",\n    \"passwordAuthEnabled\": false,\n    \"passwordChangeNeeded\": false,\n    \"permissions\": []\n}\n
                                        • 200 OK status
                                        • 404 userName does not exist
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#get-defined-permissions","title":"Get defined permissions","text":"
                                        • Description: This method allows you to get the list of the permissions defined in the system
                                        • Method: GET
                                        • API PATH: services/identity/v1/definedPermissions

                                        No specific permission is required to access this resource.

                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_2","title":"Responses","text":"
                                        {\n    \"permissions\": [\n        \"rest.command\",\n        \"rest.inventory\",\n        \"rest.configuration\",\n        \"rest.tamper.detection\",\n        \"rest.security\",\n        \"kura.cloud.connection.admin\",\n        \"rest.position\",\n        \"kura.packages.admin\",\n        \"kura.device\",\n        \"rest.wires.admin\",\n        \"kura.admin\",\n        \"rest.keystores\",\n        \"rest.assets\",\n        \"rest.system\",\n        \"kura.maintenance\",\n        \"kura.wires.admin\",\n        \"rest.identity\"\n    ]\n}\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#get-users-configuration","title":"Get users configuration","text":"
                                        • Description: This method allows you to get the list of the users and their configuration on the system.
                                        • Method: GET
                                        • API PATH: services/identity/v1/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_3","title":"Responses","text":"
                                        {\n    \"userConfig\": [\n        {\n            \"userName\": \"admin\",\n            \"passwordAuthEnabled\": true,\n            \"passwordChangeNeeded\": false,\n            \"permissions\": [\n                \"kura.admin\"\n            ]\n        },\n        {\n            \"userName\": \"appadmin\",\n            \"passwordAuthEnabled\": true,\n            \"passwordChangeNeeded\": true,\n            \"permissions\": [\n                \"kura.cloud.connection.admin\",\n                \"kura.packages.admin\",\n                \"kura.wires.admin\"\n            ]\n        }\n    ]\n}\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#get-password-requirements","title":"Get password requirements","text":"
                                        • Description: This method allows you to get the password requirements.
                                        • Method: GET
                                        • API PATH: services/identity/v1/passwordRequirements

                                        No specific permission is required to access this resource.

                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_4","title":"Responses","text":"
                                        {\n    \"passwordMinimumLength\": 8,\n    \"passwordRequireDigits\": false,\n    \"passwordRequireSpecialChars\": false,\n    \"passwordRequireBothCases\": false\n}\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#update-user","title":"Update User","text":"
                                        • Description: This method allows to update an existing user in the system.
                                        • Method: PUT
                                        • API PATH: services/identity/v1/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#request_2","title":"Request","text":"
                                        {\n    \"userName\": \"username\",\n    \"password\": \"password\",\n    \"passwordChangeNeeded\": false,\n    \"passwordAuthEnabled\": true,\n    \"permissions\": [\n        \"rest.identity\"\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_5","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad Request (Password strenght requirements not satisfied)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-identity-api-v1/#delete-user","title":"Delete User","text":"
                                        • Description: This method allows to delete an existing user in the system. The only considered field is the userName.
                                        • Method: DELETE
                                        • API PATH: services/identity/v1/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#request_3","title":"Request","text":"
                                        {\n    \"userName\": \"username\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v1/#responses_6","title":"Responses","text":"
                                        • 200 OK status
                                        • 404 userName does not exist
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/","title":"Rest Identity V2 API","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: IDN-V2.

                                        The IdentityRestService APIs provides methods to manage the system identities. Unless otherwise specified, identities with rest.identity permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#create-user","title":"Create User","text":"
                                        • Description: This method allows to create a new identity in the system. Identity name must respect the requirements enforced by the IdentityService.
                                        • Method: POST
                                        • API PATH: services/identity/v2/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request","title":"Request","text":"
                                        {\n    \"name\": \"username\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 409 Conflict (Identity already exists)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#get-user-by-name","title":"Get User by Name","text":"
                                        • Description: This method allows to get data about an identity in the system. The request body's identity field is used to get only the name of specific identity. It is also possible to retrieve information about the specific user's component configuration, specifying the type of interest.
                                        • Method: POST
                                        • API PATH: services/identity/v2/identities/byName
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_1","title":"Request","text":"
                                        {\n    \"identity\": {\n        \"name\": \"test\"\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#response","title":"Response","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_2","title":"Request","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#response_1","title":"Response","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": [\n            {\n                \"name\": \"rest.identity\"\n            }\n        ]\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": true\n    },\n    \"additionalConfigurations\": {\n        \"configurations\": []\n    }\n}\n
                                        • 200 OK status
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 404 Identity does not exist
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#get-user-default-configuration-by-name","title":"Get User Default Configuration by Name","text":"
                                        • Description: This method allows to get the default configuration data about an identity in the system. The request body's identity field is used to get only the name of specific identity. It is also possible to retrieve information about the specific user's component default configuration, specifying the type of interest. This method accepts also non-existing user's name as input: in this way it's possible to retrieve which is the default configuration applied when a user is created with the name field only.
                                        • Method: POST
                                        • API PATH: services/identity/v2/identities/default/byName
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_3","title":"Request","text":"
                                        {\n\"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#response_2","title":"Response","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_4","title":"Request","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#response_3","title":"Response","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": []\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": false\n    },\n    \"additionalConfigurations\": {\n        \"configurations\": []\n    }\n}\n
                                        • 200 OK status
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#create-permission","title":"Create Permission","text":"
                                        • Description: This method allows to create a new permission in the system. Permission name must respect the requirements enforced by the IdentityService.
                                        • Method: POST
                                        • API PATH: services/identity/v2/permissions
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_5","title":"Request","text":"
                                        {\n    \"name\": \"permission\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad Request (Permission name not valid)
                                        • 409 Conflict (Permission already exists)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#validate-identity-configuration","title":"Validate Identity Configuration","text":"
                                        • Description: Validates the provided list of identity configurations without performing any change to the system. It is possible to specify only the identity body field, or also the configurationComponents one.
                                        • Method: POST
                                        • API PATH: services/identity/v2/identities/validate
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_6","title":"Request","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    }, \n    \"configurationComponents\": [\"AdditionalConfigurations\", \"AssignedPermissions\", \"PasswordConfiguration\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_2","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#get-defined-permissions","title":"Get defined permissions","text":"
                                        • Description: This method allows you to get the list of the permissions defined in the system
                                        • Method: GET
                                        • API PATH: services/identity/v2/definedPermissions

                                        No specific permission is required to access this resource.

                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_3","title":"Responses","text":"
                                        [\n    {\n        \"name\": \"rest.identity\"\n    },\n    {\n        \"name\": \"rest.wires.admin\"\n    },\n    {\n        \"name\": \"kura.wires.admin\"\n    },\n    {\n        \"name\": \"kura.network.admin\"\n    },\n    {\n        \"name\": \"rest.network.status\"\n    },\n    {\n        \"name\": \"test-permission\"\n    },\n    {\n        \"name\": \"rest.keystores\"\n    },\n    {\n        \"name\": \"rest.assets\"\n    },\n    {\n        \"name\": \"rest.network.configuration\"\n    },\n    {\n        \"name\": \"kura.admin\"\n    },\n    {\n        \"name\": \"rest.cloudconnection\"\n    },\n    {\n        \"name\": \"kura.device\"\n    },\n    {\n        \"name\": \"rest.system\"\n    },\n    {\n        \"name\": \"kura.maintenance\"\n    },\n    {\n        \"name\": \"kura.packages.admin\"\n    },\n    {\n        \"name\": \"rest.tamper.detection\"\n    },\n    {\n        \"name\": \"rest.deploy\"\n    },\n    {\n        \"name\": \"rest.configuration\"\n    },\n    {\n        \"name\": \"kura.cloud.connection.admin\"\n    },\n    {\n        \"name\": \"rest.command\"\n    },\n    {\n        \"name\": \"rest.inventory\"\n    },\n    {\n        \"name\": \"rest.position\"\n    },\n    {\n        \"name\": \"rest.security\"\n    }\n]\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#get-users-configuration","title":"Get users configuration","text":"
                                        • Description: This method allows you to get the list of the users and their configuration on the system.
                                        • Method: GET
                                        • API PATH: services/identity/v2/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_4","title":"Responses","text":"
                                        [\n    {\n        \"identity\": {\n            \"name\": \"admin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": false,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    },\n    {\n        \"identity\": {\n            \"name\": \"appadmin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.packages.admin\"\n                },\n                {\n                    \"name\": \"kura.cloud.connection.admin\"\n                },\n                {\n                    \"name\": \"kura.wires.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": true,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    },\n    {\n        \"identity\": {\n            \"name\": \"netadmin\"\n        },\n        \"permissionConfiguration\": {\n            \"permissions\": [\n                {\n                    \"name\": \"kura.device\"\n                },\n                {\n                    \"name\": \"kura.network.admin\"\n                },\n                {\n                    \"name\": \"kura.cloud.connection.admin\"\n                }\n            ]\n        },\n        \"passwordConfiguration\": {\n            \"passwordChangeNeeded\": true,\n            \"passwordAuthEnabled\": true\n        },\n        \"additionalConfigurations\": {\n            \"configurations\": []\n        }\n    }\n]\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#get-password-strenght-requirements","title":"Get Password Strenght Requirements","text":"
                                        • Description: This method allows you to get the password requirements.
                                        • Method: GET
                                        • API PATH: services/identity/v2/passwordStrenghtRequirements

                                        No specific permission is required to access this resource.

                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_5","title":"Responses","text":"
                                        {\n    \"passwordMinimumLength\": 8,\n    \"digitsRequired\": false,\n    \"specialCharactersRequired\": false,\n    \"bothCasesRequired\": false\n}\n
                                        • 200 OK status
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#put-methods","title":"PUT methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#update-identity","title":"Update Identity","text":"
                                        • Description: This method allows to update an existing identity in the system. New passwords must respect the requirements enforced by the IdentityService.
                                        • Method: PUT
                                        • API PATH: services/identity/v2/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_7","title":"Request","text":"
                                        {\n    \"identity\": {\n        \"name\": \"username\"\n    },\n    \"permissionConfiguration\": {\n        \"permissions\": [\n            {\n                \"name\": \"rest.identity\"\n            }\n        ]\n    },\n    \"passwordConfiguration\": {\n        \"passwordChangeNeeded\": false,\n        \"passwordAuthEnabled\": true,\n        \"password\": \"password123\"\n    }\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_6","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#delete-methods","title":"DELETE methods","text":""},{"location":"references/rest-apis/rest-identity-api-v2/#delete-user","title":"Delete User","text":"
                                        • Description: This method allows to delete an existing user in the system. The only considered field is the name.
                                        • Method: DELETE
                                        • API PATH: services/identity/v2/identities
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_8","title":"Request","text":"
                                        {\n    \"name\": \"username\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_7","title":"Responses","text":"
                                        • 200 OK status
                                        • 404 username does not exist
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#delete-permission","title":"Delete Permission","text":"
                                        • Description: This method allows to delete an existing permission in the system. The only considered field is the name.
                                        • Method: DELETE
                                        • API PATH: services/identity/v2/permissions
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#request_9","title":"Request","text":"
                                        {\n    \"name\": \"permission\"\n}\n
                                        "},{"location":"references/rest-apis/rest-identity-api-v2/#responses_8","title":"Responses","text":"
                                        • 200 OK status
                                        • 404 permission does not exist
                                        • 400 Missing or mispelled field name (eg: nme)
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/","title":"Rest Inventory v1 API","text":""},{"location":"references/rest-apis/rest-inventory-api/#global","title":"Global","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-inventory-summary","title":"GET Inventory Summary","text":"
                                        • Method: GET
                                        • Description: returns a list of All Inventory Items
                                        • API PATH: /services/inventory/v1/inventory
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response","title":"Response","text":"
                                        • 200 OK
                                          {\n    \"inventory\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"com.eclipsesource.jaxrs.provider.gson\",\n            \"version\":\"2.3.0.201602281253\",\n            \"type\":\"BUNDLE\"\n        },\n              {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"type\":\"DP\"\n        }\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#bundles","title":"Bundles","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-bundles","title":"GET bundles","text":"
                                        • Method: GET
                                        • Description: returns a list of bundles.
                                        • API PATH: /services/inventory/v1/bundles
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response_1","title":"Response","text":"
                                        • 200 OK
                                          {\n    \"bundles\":[\n        {\n            \"name\":\"org.eclipse.osgi\",\n            \"version\":\"3.16.0.v20200828-0759\",\n            \"id\":0,\n            \"state\":\"ACTIVE\",\n            \"signed\":true\n        },\n        {\n            \"name\":\"org.eclipse.equinox.cm\",\n            \"version\":\"1.4.400.v20200422-1833\",\n            \"id\":1,\n            \"state\":\"ACTIVE\",\n            \"signed\":false\n        }\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#start-bundle","title":"Start bundle","text":"
                                        • Method: POST
                                        • API PATH: /services/inventory/v1/bundles/_start
                                        "},{"location":"references/rest-apis/rest-inventory-api/#request-body","title":"Request Body","text":"
                                        { \n\"name\":\"org.eclipse.osgi\",\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/#stop-bundle","title":"Stop bundle","text":"
                                        • Method: POST
                                        • API PATH: /services/inventory/v1/bundles/_stop
                                        "},{"location":"references/rest-apis/rest-inventory-api/#request-body_1","title":"Request Body","text":"
                                        { \n\"name\":\"org.eclipse.osgi\",\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/#deployment-packages","title":"Deployment Packages","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-packages","title":"GET packages","text":"
                                        • Method: GET
                                        • Description: returns a list of deployment packages.
                                        • API PATH: /services/inventory/v1/deploymentPackages
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response_2","title":"Response","text":"
                                        • 200 OK
                                          {\n    \"deploymentPackages\":[\n        {\n            \"name\":\"org.eclipse.kura.example.beacon\",\n            \"version\":\"1.0.500\",\n            \"signed\":false,\n            \"bundles\":[\n                {\n                    \"name\":\"org.eclipse.kura.example.beacon\",\n                    \"version\":\"1.0.500\",\n                    \"id\": 171,\n                    \"state\": \"ACTIVE\",\n                    \"signed\": false\n                }\n            ]\n        }\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#system-packages-debrpmapk","title":"System Packages (DEB/RPM/APK)","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-system-packages","title":"GET System Packages","text":"
                                        • Method: GET
                                        • Description: returns a list of system packages.
                                        • API PATH: /services/inventory/v1/systemPackages
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response_3","title":"Response","text":"
                                        • 200 OK
                                          {\n    \"systemPackages\":[\n        {\n            \"name\":\"adduser\",\n            \"version\":\"3.118\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"alsa-utils\",\n            \"version\":\"1.1.8-2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"ansible\",\n            \"version\":\"2.7.7+dfsg-1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apparmor\",\n            \"version\":\"2.13.2-10\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-listchanges\",\n            \"version\":\"3.19\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-transport-https\",\n            \"version\":\"1.8.2.2\",\n            \"type\":\"DEB\"\n        },\n        {\n            \"name\":\"apt-utils\",\n            \"version\":\"1.8.2.1\",\n            \"type\":\"DEB\"\n        }\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#containers","title":"Containers","text":""},{"location":"references/rest-apis/rest-inventory-api/#get-containers","title":"GET Containers","text":"
                                        • Method: Get
                                        • Description: returns a list of Containers.
                                        • API PATH: /services/inventory/v1/containers
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response_4","title":"Response","text":"
                                        • 200 OK
                                          {\n  \"containers\":\n  [\n    {\n      \"name\":\"container_1\",\n      \"version\":\"nginx:latest\",\n      \"type\":\"DOCKER\",\n      \"state\":\"active\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#start-container","title":"Start container","text":"
                                        • Method: POST
                                        • API PATH: /services/inventory/v1/containers/_start
                                        "},{"location":"references/rest-apis/rest-inventory-api/#request-body_2","title":"Request Body","text":"
                                        {\n    \"name\":\"container_1\",\n    \"version\": \"nginx:latest\",\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#responses_2","title":"Responses","text":"
                                        • 200 OK
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/#stop-container","title":"Stop container","text":"
                                        • Method: POST
                                        • API PATH: /services/inventory/v1/containers/_stop
                                        "},{"location":"references/rest-apis/rest-inventory-api/#request-body_3","title":"Request Body","text":"
                                        {\n    \"name\":\"container_1\",\n    \"version\": \"nginx:latest\",\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#responses_3","title":"Responses","text":"
                                        • 200 OK
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/#images","title":"Images","text":""},{"location":"references/rest-apis/rest-inventory-api/#images-images","title":"Images Images","text":"
                                        • Method: Get
                                        • Description: returns a list of Images.
                                        • API PATH: /services/inventory/v1/images
                                        "},{"location":"references/rest-apis/rest-inventory-api/#response_5","title":"Response","text":"
                                        • 200 OK
                                          {\n  \"containers\":\n  [\n    {\n      \"name\":\"nginx\",\n      \"version\":\"latest\",\n      \"type\":\"ContainerImage\",\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#delete-image","title":"Delete Image","text":"
                                        • Method: POST
                                        • API PATH: /services/inventory/v1/images/_delete
                                        "},{"location":"references/rest-apis/rest-inventory-api/#request-body_4","title":"Request Body","text":"
                                        {\n      \"name\":\"nginx\",\n      \"version\":\"latest\",\n}\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#responses_4","title":"Responses","text":"
                                        • 200 OK
                                        • 400 Bad Request (Malformed Client JSON)
                                        • 404 Resource Not Found
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-inventory-api/#states","title":"States","text":""},{"location":"references/rest-apis/rest-inventory-api/#bundle-states","title":"Bundle States","text":"
                                        -   `ACTIVE`: Container is running\n-   `INSTALLED`: Container is starting\n-   `UNINSTALLED`: Container has failed, or is stopped\n-   `UNKNOWN`: Container state can not be determined\n
                                        "},{"location":"references/rest-apis/rest-inventory-api/#container-states","title":"Container States","text":"
                                        -   `active`: Container is running\n-   `installed`: Container is starting\n-   `uninstalled`: Container has failed, or is stopped\n-   `unknown`: Container state can not be determined\n
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/","title":"Network Configuration V1 REST APIs","text":"

                                        This section describes the networkConfiguration/v1 REST APIs, that allow to retrieve information about the network configuration of the running system. The services pid for which it's possible to obtain and update the configurations are:

                                        • org.eclipse.kura.net.admin.NetworkConfigurationService: which manages the network configuration of the system, so the network interfaces along with their network configuration (Ip4/Ip6 settings, DHCP, Nat, and so on)
                                        • org.eclipse.kura.net.admin.FirewallConfigurationService: which manages the Ip4 firewall configurations (open ports, port forwarding and masquerading for IPv4 protocol)
                                        • org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6: which manages the Ip6 firewall configurations (open ports, port forwarding and masquerading for IPv6 protocol)

                                        To access these REST APIs, an identity with rest.network.configuration permission assigned is required.

                                        • Request definitions
                                          • GET/configurableComponents
                                          • GET/configurableComponents/configurations
                                          • POST/configurableComponents/configurations/byPid
                                          • POST/configurableComponents/configurations/byPid/_default
                                          • PUT/configurableComponents/configurations/_update
                                          • GET/factoryComponents
                                          • POST/factoryComponents
                                          • DEL/factoryComponents/byPid
                                          • GET/factoryComponents/ocd
                                          • POST/factoryComponents/ocd/byFactoryPid
                                        • JSON definitions
                                          • BatchFailureReport
                                          • ComponentConfigurationList
                                          • GenericFailureReport
                                          • PidSet
                                          • UpdateComponentConfigurationRequest
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-network-configuration-api/#getconfigurablecomponents","title":"GET/configurableComponents","text":"
                                        • REST API path : /services/networkConfiguration/v1/configurableComponents
                                        • description : Returns the list of the available services that manages the network configurations on the system.
                                        • responses :
                                          • 200
                                            • description : The request succeeded.
                                            • response body :
                                              • PidSet
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#getconfigurablecomponentsconfigurations","title":"GET/configurableComponents/configurations","text":"
                                        • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations
                                        • description : Returns all of network component configurations available on the system. This request will return the pid, ocd and properties.
                                        • responses :
                                          • 200
                                            • description : The request succeeded.
                                            • response body :
                                              • ComponentConfigurationList
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#postconfigurablecomponentsconfigurationsbypid","title":"POST/configurableComponents/configurations/byPid","text":"
                                        • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/byPid
                                        • description : Returns a user selected set of network configurations. This request will return the pid, ocd and properties.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                            • description : The request succeeded. If the network configuration for a given pid cannot be found, it will not be included in the result.
                                            • response body :
                                              • ComponentConfigurationList
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#postconfigurablecomponentsconfigurationsbypid_default","title":"POST/configurableComponents/configurations/byPid/_default","text":"
                                        • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/byPid/_default
                                        • description : Returns the default network configuration for a given set of network component pids. The default configurations are generated basing on component definition only, user applied modifications will not be taken into account. This request will return the pid, ocd and properties.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                            • description : The request succeeded. If the network configuration for a given pid cannot be found, it will not be included in the result.
                                            • response body :
                                              • ComponentConfigurationList
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#putconfigurablecomponentsconfigurations_update","title":"PUT/configurableComponents/configurations/_update","text":"
                                        • REST API path : /services/networkConfiguration/v1/configurableComponents/configurations/_update
                                        • description : Updates a given set of network component configurations. This request can be also used to apply a network configuration snapshot.
                                        • request body :
                                          • UpdateComponentConfigurationRequest
                                        • responses :
                                          • 200
                                            • description : The request succeeded.
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: update:$pid for component update operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                                            • response body :
                                              • Variants:
                                                • object
                                                  • GenericFailureReport
                                                • object
                                                  • BatchFailureReport

                                        Warning

                                        factoryComponents endopoints are available in the current version of Kura for future compatibility. Currently, as of Kura 5.4.0, there are no network related components that are factory components.

                                        The endopoints that should return a list of pids will always return an empty array, while those that should create, delete or update a component will always return a 500 error.

                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#getfactorycomponents","title":"GET/factoryComponents","text":"
                                        • REST API path : /services/networkConfiguration/v1/factoryComponents
                                        • description : Returns the ids of the network component factories available on the device.
                                        • responses :
                                          • 200
                                            • description : The factory pid set.
                                            • response body :
                                              • PidSet
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#postfactorycomponents","title":"POST/factoryComponents","text":"
                                        • REST API path : /services/networkConfiguration/v1/factoryComponents
                                        • description : This is a batch request that allows to create one or more network factory component instances and optionally create a new snapshot.
                                        • request body :
                                          • CreateFactoryComponentConfigurationsRequest
                                        • responses :
                                          • 200
                                            • description : The request succeeded.
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500

                                            • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: create:$pid for component creation operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.

                                            • response body :

                                              • Variants:
                                                • object
                                                  • GenericFailureReport
                                                • object
                                                  • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#delfactorycomponentsbypid","title":"DEL/factoryComponents/byPid","text":"
                                        • REST API path : /services/networkConfiguration/v1/factoryComponents/byPid
                                        • description : This is a batch request that allows to delete one or more network factory component instances and optionally create a new snapshot.
                                        • request body :
                                          • DeleteFactoryComponentConfigurationsRequest
                                        • responses :
                                          • 200
                                            • description : The request succeeded.
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : In case of processing errors, the device will attempt to return a detailed error response containing a message describing the failure reason for each operation. The operation ids are the following: delete:$pid for component delete operations, where $pid is the pid of the instance, and snapshot, for the snapshot creation operation. In case of an unexpected failure, a generic error response will be returned.
                                            • response body :
                                              • Variants:
                                                • object
                                                  • GenericFailureReport
                                                • object
                                                  • BatchFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#getfactorycomponentsocd","title":"GET/factoryComponents/ocd","text":"
                                        • REST API path : /services/networkConfiguration/v1/factoryComponents/ocd
                                        • description : Returns the OCD of the network components created by the factories available on the device without the need of creating an instance. This request returns the information related to all available network factories.
                                        • responses :
                                          • 200
                                            • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present.
                                            • response body :
                                              • ComponentConfigurationList
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#postfactorycomponentsocdbyfactorypid","title":"POST/factoryComponents/ocd/byFactoryPid","text":"
                                        • REST API path : /services/networkConfiguration/v1/factoryComponents/ocd/byFactoryPid
                                        • description : Returns the OCD of the components created by the factories available on the device without the need of creating an instance. This request returns the information related to a user selected set of factories.
                                        • request body :
                                          • PidSet
                                        • responses :
                                          • 200
                                            • description : The request succeeded. The pid property of the received configurations will report the factory pid, the ocd field will contain the definition, the properties field will not be present. If the OCD for a given factory pid cannot be found, it will not be included in the result.
                                            • response body :
                                              • ComponentConfigurationList
                                          • 400
                                            • description : The request body is not valid JSON or it contains invalid parameters.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : An unexpected internal error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-network-configuration-api/#batchfailurereport","title":"BatchFailureReport","text":"

                                        An object that is returned by requests that involve multiple operations, when at least one operation failed. This object report an error message for each of the operations that failed. The operations are identified by an id, see request documentation for more details. If an operation is not listed by this object, then the operation succeeded. Properties:

                                        • failures: array The list of operations that failed.

                                          • array elements: object An object reporting details about an operation failure. Properties:

                                          • id: string An identifier of the failed operation.

                                          • message: string A message describing the failure.
                                        {\n  \"failures\": [\n    {\n      \"id\": \"create:testComponent\",\n      \"message\": \"Invalid parameter. pid testComponent already exists\"\n    },\n    {\n      \"id\": \"create:otherComponent\",\n      \"message\": \"Invalid parameter. pid otherComponent already exists\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#componentconfigurationlist","title":"ComponentConfigurationList","text":"

                                        Represents a list of component configurations. Properties:

                                        • configs: array The component configurations
                                          • array elements: object
                                          • ComponentConfiguration
                                        {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"firewall.open.ports\",\n                        \"description\": \"The list of firewall opened ports.\",\n                        \"id\": \"firewall.open.ports\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.port.forwarding\",\n                        \"description\": \"The list of firewall port forwarding rules.\",\n                        \"id\": \"firewall.port.forwarding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.nat\",\n                        \"description\": \"The list of firewall NAT rules.\",\n                        \"id\": \"firewall.nat\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"FirewallConfigurationService\",\n                \"description\": \"Firewall Configuration Service\",\n                \"id\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\"\n            },\n            \"properties\": {\n                \"firewall.open.ports\": {\n                    \"value\": \"22,tcp,0.0.0.0/0,eth0,,,,#;22,tcp,0.0.0.0/0,wlan0,,,,#;443,tcp,0.0.0.0/0,eth0,,,,#;443,tcp,0.0.0.0/0,wlan0,,,,#;4443,tcp,0.0.0.0/0,eth0,,,,#;4443,tcp,0.0.0.0/0,wlan0,,,,#;1450,tcp,0.0.0.0/0,eth0,,,,#;1450,tcp,0.0.0.0/0,wlan0,,,,#;5002,tcp,127.0.0.1/32,,,,,#;53,udp,0.0.0.0/0,eth0,,,,#;53,udp,0.0.0.0/0,wlan0,,,,#;67,udp,0.0.0.0/0,eth0,,,,#;67,udp,0.0.0.0/0,wlan0,,,,#;8000,tcp,0.0.0.0/0,eth0,,,,#;8000,tcp,0.0.0.0/0,wlan0,,,,#\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.port.forwarding\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.nat\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.FirewallConfigurationService\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n            \"definition\": {\n                \"ad\": [\n                    {\n                        \"name\": \"firewall.ipv6.open.ports\",\n                        \"description\": \"The list of firewall opened ports.\",\n                        \"id\": \"firewall.ipv6.open.ports\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.ipv6.port.forwarding\",\n                        \"description\": \"The list of firewall port forwarding rules.\",\n                        \"id\": \"firewall.ipv6.port.forwarding\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    },\n                    {\n                        \"name\": \"firewall.ipv6.nat\",\n                        \"description\": \"The list of firewall NAT rules.\",\n                        \"id\": \"firewall.ipv6.nat\",\n                        \"type\": \"STRING\",\n                        \"cardinality\": 0,\n                        \"defaultValue\": \"\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"name\": \"FirewallConfigurationServiceIPv6\",\n                \"description\": \"Firewall Configuration Service IPV6\",\n                \"id\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\"\n            },\n            \"properties\": {\n                \"firewall.ipv6.port.forwarding\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.ipv6.nat\": {\n                    \"value\": \"\",\n                    \"type\": \"STRING\"\n                },\n                \"firewall.ipv6.open.ports\": {\n                    \"value\": \"1234,tcp,0:0:0:0:0:0:0:0/0,,,,,#\",\n                    \"type\": \"STRING\"\n                },\n                \"kura.service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n                    \"type\": \"STRING\"\n                },\n                \"service.pid\": {\n                    \"value\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#genericfailurereport","title":"GenericFailureReport","text":"

                                        An object reporting a failure message. Properties:

                                        • message: string A message describing the failure.
                                        {\n  \"message\": \"An unexpected error occurred.\"\n}\n
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#pidset","title":"PidSet","text":"

                                        Represents a set of pids or factory pids. Properties:

                                        • pids: array The set of pids
                                          • array elements: string The pid
                                        {\n    \"pids\": [\n        \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n        \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n        \"org.eclipse.kura.net.admin.FirewallConfigurationService\"\n    ]\n}\n
                                        "},{"location":"references/rest-apis/rest-network-configuration-api/#updatecomponentconfigurationrequest","title":"UpdateComponentConfigurationRequest","text":"

                                        An object that describes a set of configurations that need to be updated. Properties:

                                        • configs: array The configurations to be updated. The ocd field can be omitted, it will be ignored if specified.
                                          • array elements: object
                                          • ComponentConfiguration
                                        • takeSnapshot: bool
                                        • optional The true value will be used as default if not explicitly specified Defines whether a new snapshot should be created after that the component configurations have been applied.
                                        {\n    \"configs\": [\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.ipv6.FirewallConfigurationServiceIPv6\",\n            \"properties\": {\n                \"firewall.ipv6.open.ports\": {\n                    \"value\": \"1234,tcp,0:0:0:0:0:0:0:0/0,,,,,#\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"pid\": \"org.eclipse.kura.net.admin.NetworkConfigurationService\",\n            \"properties\": {\n                \"net.interface.eth0.config.ip6.status\": {\n                    \"value\": \"netIPv6StatusUnmanaged\",\n                    \"type\": \"STRING\"\n                }\n            }\n        }\n    ],\n    \"takeSnapshot\": true\n}\n
                                        "},{"location":"references/rest-apis/rest-position-api/","title":"Position","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: POS-V1.

                                        "},{"location":"references/rest-apis/rest-position-api/#get-position","title":"Get Position","text":"
                                        • Method: GET
                                        • API PATH: /services/position/v1/position
                                        "},{"location":"references/rest-apis/rest-position-api/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    //longitude of this position in degrees.\n    \"longitude\": 7.729571119959449,\n\n    //latitude of this position in degrees.\n    \"latitude\": 50.45345802194789,\n\n    //altitude of this position in meters.\n    \"altitude\": 1000.0,\n\n    //the ground speed of this position in meters per second.\n    \"speed\": 1000.0,\n\n    //the track of this position in degrees as a compass heading.\n    \"track\": 1000.0\n\n    //the gnss system used to retrieve position information\n    \"gnssType\": [\n        \"Gps\"\n    ]\n}\n
                                        • 500 Internal Server Error
                                          • will also occur when GPS Position is not locked
                                            {\n    \"message\": \"Service unavailable. Position is not locked.\"\n}\n
                                        "},{"location":"references/rest-apis/rest-position-api/#get-date-time","title":"Get Date Time","text":"
                                        • Method: GET
                                        • API PATH: /services/position/v1/dateTime
                                        "},{"location":"references/rest-apis/rest-position-api/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    //The dateTime string is formatted according to the ISO 8601 standard, and will always be in the UTC timezone. \n    \"dateTime\": \"2023-07-19T18:26:38Z\"\n}\n
                                        • 500 Internal Server Error
                                          • will also occur when GPS Position is not locked
                                            {\n    \"message\": \"Service unavailable. Position is not locked.\"\n}\n
                                        "},{"location":"references/rest-apis/rest-position-api/#get-is-position-locked","title":"Get Is Position Locked","text":"
                                        • Method: GET
                                        • API PATH: /services/position/v1/isLocked
                                        "},{"location":"references/rest-apis/rest-position-api/#responses_2","title":"Responses","text":"
                                        • 200 OK status

                                        {\n    \"islocked\": false\n}\n
                                        - 500 Internal Server Error

                                        "},{"location":"references/rest-apis/rest-security-api-v1/","title":"Rest Security V1 API","text":"

                                        Warning

                                        This API id deprecated and superseded by the Security V2 REST APIs.

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: SEC-V1.

                                        This REST API requires a SecurityService implementation to be registered on the framework, which is not provided by the standard Kura distribution.

                                        The SecurityRestService APIs provides methods to manage the system security. Identities with rest.security permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-security-api-v1/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-security-api-v1/#security-policy-fingerprint-reload","title":"Security policy fingerprint reload","text":"
                                        • Description: This method allows the reload of the security policy's fingerprint
                                        • Method: POST
                                        • API PATH: services/security/v1/security-policy-fingerprint/reload
                                        "},{"location":"references/rest-apis/rest-security-api-v1/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v1/#reload-command-line-fingerprint","title":"Reload command line fingerprint","text":"
                                        • Description: This method allows the reload of the command line fingerprint
                                        • Method: POST
                                        • API PATH: services/security/v1/command-line-fingerprint/reload
                                        "},{"location":"references/rest-apis/rest-security-api-v1/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v1/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-security-api-v1/#debug-enabled","title":"Debug enabled","text":"

                                        Note

                                        Access to this resource doesn't require the rest.security permission.

                                        • Description: This method allows you to check whether debug mode is enabled in the system.
                                        • Method: GET
                                        • API PATH: services/security/v1/debug-enabled
                                        "},{"location":"references/rest-apis/rest-security-api-v1/#responses_2","title":"Responses","text":"
                                        • 200 OK status
                                          {\n    \"enabled\":true\n}\n
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v2/","title":"Rest Security V2 API","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: SEC-V2.

                                        This REST API requires a SecurityService implementation to be registered on the framework, which is not provided by the standard Kura distribution.

                                        The SecurityRestService APIs provides methods to manage the system security. Identities with rest.security permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-security-api-v2/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-security-api-v2/#security-policy-fingerprint-reload","title":"Security policy fingerprint reload","text":"
                                        • Description: This method allows the reload of the security policy's fingerprint
                                        • Method: POST
                                        • API PATH: services/security/v2/security-policy-fingerprint/reload
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#reload-command-line-fingerprint","title":"Reload command line fingerprint","text":"
                                        • Description: This method allows the reload of the command line fingerprint
                                        • Method: POST
                                        • API PATH: services/security/v2/command-line-fingerprint/reload
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#apply-default-production-security-policy","title":"Apply default production security policy","text":"
                                        • Description: This method allows to apply the default production security policy available in the system
                                        • Method: POST
                                        • API PATH: services/security/v2/security-policy/apply-default-production
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#responses_2","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#apply-security-policy","title":"Apply security policy","text":"
                                        • Description: This method allows to apply the user provided security policy. The maximum allowed security policy size is 1MB.
                                        • Method: POST
                                        • API PATH: services/security/v2/security-policy/apply
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#request","title":"Request","text":"
                                        <plain text security policy>\n
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#responses_3","title":"Responses","text":"
                                        • 200 OK status
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-security-api-v2/#debug-enabled","title":"Debug enabled","text":"

                                        Note

                                        Access to this resource doesn't require the rest.security permission.

                                        • Description: This method allows you to check whether debug mode is enabled in the system.
                                        • Method: GET
                                        • API PATH: services/security/v2/debug-enabled
                                        "},{"location":"references/rest-apis/rest-security-api-v2/#responses_4","title":"Responses","text":"
                                        • 200 OK status
                                          {\n    \"enabled\":true\n}\n
                                        • 500 Internal Server Error (also returned when no SecurityService implementation is available)
                                        "},{"location":"references/rest-apis/rest-service-listing-api/","title":"Service Listing V1 REST APIs","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: SVCLIST-V1.

                                        The SVCLIST-V1 cloud request handler and the corresponding REST APIs allow to retrieve the identifiers (kura.service.pid) of the service running on the system that match user provided search criteria.

                                        • Request definitions
                                          • GET/servicePids
                                          • POST/servicePids/byInterface
                                          • POST/servicePids/byProperty
                                          • POST/servicePids/satisfyingReference
                                          • GET/factoryPids
                                          • POST/factoryPids/byInterface
                                          • POST/factoryPids/byProperty
                                        • JSON definitions
                                          • InterfaceNames
                                          • Reference
                                          • Filter
                                          • PropertyFilter
                                          • NotFilter
                                          • AndFilter
                                          • OrFilter
                                          • PidSet
                                          • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-service-listing-api/#getservicepids","title":"GET/servicePids","text":"
                                        • REST API path : /services/serviceListing/v1/servicePids
                                        • description : Returns the pid of all services running in the framework.
                                        • responses :
                                          • 200
                                            • description : An object reporting the pid of all services running in the framework
                                            • response body :
                                              • PidSet
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the service pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidsbyinterface","title":"POST/servicePids/byInterface","text":"
                                        • REST API path : /services/serviceListing/v1/servicePids/byInterface
                                        • description : Returns the pid of the services providing all of the service interfaces specified in the request
                                        • request body :
                                          • InterfaceNames
                                        • responses :
                                          • 200
                                            • description : An object reporting the pid of the matching services.
                                            • response body :
                                              • PidSet
                                          • 400
                                            • description : If the request body is not syntactically correct.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the service pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidsbyproperty","title":"POST/servicePids/byProperty","text":"
                                        • REST API path : /services/serviceListing/v1/servicePids/byProperty
                                        • description : Returns the pid of the services whose properties match the specified filter.
                                        • request body :
                                          • Filter
                                        • responses :
                                          • 200
                                            • description : An object reporting the pid of the matching services.
                                            • response body :
                                              • PidSet
                                          • 400
                                            • description : If the request body is not syntactically correct.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the service pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#postservicepidssatisfyingreference","title":"POST/servicePids/satisfyingReference","text":"
                                        • REST API path : /services/serviceListing/v1/servicePids/satisfyingReference
                                        • description : Returns the pid of the services that provide an interface compatible with a Declarative Service reference. Reference examples are KeystoreService and TruststoreKeystoreService defined by the org.eclipse.kura.ssl.SslManagerService component.
                                        • request body :
                                          • Reference
                                        • responses :
                                          • 200
                                            • description : An object reporting the pid of the matching services.
                                            • response body :
                                              • PidSet
                                          • 400
                                            • description : If the request body is not syntactically correct.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the service pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#getfactorypids","title":"GET/factoryPids","text":"
                                        • REST API path : /services/serviceListing/v1/factoryPids
                                        • description : Returns the factory pids defined in the framework.
                                        • responses :
                                          • 200
                                            • description : An object reporting the factory pids defined in the framework.
                                            • response body :
                                              • PidSet
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the factory pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#postfactorypidsbyinterface","title":"POST/factoryPids/byInterface","text":"
                                        • REST API path : /services/serviceListing/v1/factoryPids/byInterface
                                        • description : Returns the factory pid of the services that provide all of the specified interfaces.
                                        • request body :
                                          • InterfaceNames
                                        • responses :
                                          • 200
                                            • description : An object reporting the matching factory pids.
                                            • response body :
                                              • PidSet
                                          • 400
                                            • description : If the request body is not syntactically correct.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the factory pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#postfactorypidsbyproperty","title":"POST/factoryPids/byProperty","text":"
                                        • REST API path : /services/serviceListing/v1/factoryPids/byProperty
                                        • description : Returns the list of factory pids whose properties match the specified filter.
                                        • request body :
                                          • Filter
                                        • responses :
                                          • 200
                                            • description : An object reporting the matching factory pids.
                                            • response body :
                                              • PidSet
                                          • 400
                                            • description : If the request body is not syntactically correct.
                                            • response body :
                                              • GenericFailureReport
                                          • 500
                                            • description : If an unexpected failure occurs while retrieving the factory pid list.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-service-listing-api/#interfacenames","title":"InterfaceNames","text":"

                                        A list of serviceinterface names.

                                        Properties:

                                        • interfaceNames: array The list of service interface names.

                                          • array element type: string A service interface name.
                                        {\n  \"interfaceNames\": [\n    \"org.eclipse.kura.security.keystore.KeystoreService\",\n    \"org.eclipse.kura.configuration.ConfigurableComponent\"\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#reference","title":"Reference","text":"

                                        A object specifying a service pid and a reference name.

                                        Properties:

                                        • pid: string The pid of the service containing the reference

                                        • referenceName: string The reference name

                                        {\n  \"pid\": \"org.eclipse.kura.ssl.SslManagerService\",\n  \"referenceName\": \"KeystoreService\"\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#propertyfilter","title":"PropertyFilter","text":"

                                        A filter matching property keys and values. If the value property omitted, the filter will match if the property is set, regardless of the value. If the service property value is of array or list type, the filter will match if at least one of the elements match.

                                        Properties:

                                        • name: string The property name that should be matched, it must not contain spaces

                                        • value: string (optional) The property value that should be matched.

                                        {\n  \"name\": \"foo\",\n  \"value\": \"bar\"\n}\n
                                        {\n  \"name\": \"foo\"\n}\n

                                        "},{"location":"references/rest-apis/rest-service-listing-api/#notfilter","title":"NotFilter","text":"

                                        A filter that negates the result returned by the filter specified as the \"not\" propertiy.

                                        Properties:

                                        • not: object
                                          • Filter
                                        {\n  \"not\": {\n    \"name\": \"foo\",\n    \"value\": \"bar\"\n  }\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#andfilter","title":"AndFilter","text":"

                                        A filter that matches if all filters specified by the \"and\" propertiy match.

                                        Properties:

                                        • and: array A list of filters that should be combined with the and operator

                                          • array element type: object
                                            • Filter
                                        {\n  \"and\": [\n    {\n      \"name\": \"foo\",\n      \"value\": \"bar\"\n    },\n    {\n      \"name\": \"baz\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#orfilter","title":"OrFilter","text":"

                                        A filter that matches if any of the filters specified by the \"or\" propertiy match.

                                        Properties:

                                        • or: array A list of filters that should be combined with the or operator

                                          • array element type: object
                                            • Filter
                                        {\n  \"or\": [\n    {\n      \"name\": \"foo\",\n      \"value\": \"bar\"\n    },\n    {\n      \"name\": \"baz\"\n    }\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#filter","title":"Filter","text":"

                                        A filter that operates on service properties. This object allows to specify basic property key/value matchers and the and or and not operators.

                                        • Variants:
                                        • object
                                          • PropertyFilter
                                        • object
                                          • NotFilter
                                        • object
                                          • AndFilter
                                        • object
                                          • OrFilter
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#pidset","title":"PidSet","text":"

                                        Represents a set of pids or factory pids.

                                        Properties:

                                        • pids: array The set of pids

                                          • array element type: string The pid
                                        {\n  \"pids\": [\n    \"org.eclipse.kura.cloud.app.command.CommandCloudApp\",\n    \"org.eclipse.kura.cloud.CloudService\",\n    \"org.eclipse.kura.cloud.publisher.CloudNotificationPublisher\",\n    \"org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService\",\n    \"org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport\",\n    \"org.eclipse.kura.core.deployment.CloudDeploymentHandlerV2\",\n    \"org.eclipse.kura.crypto.CryptoService\",\n    \"org.eclipse.kura.data.DataService\",\n    \"org.eclipse.kura.db.H2DbService\",\n    \"org.eclipse.kura.deployment.agent\"\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-service-listing-api/#genericfailurereport","title":"GenericFailureReport","text":"

                                        An object reporting a failure message.

                                        Properties:

                                        • message: string A message describing the failure.
                                        "},{"location":"references/rest-apis/rest-session-api/","title":"Session V1 Rest APIs","text":"

                                        The session management REST APIs allow to authenticate and establish an HTTP session. Using this APIs and creating a session is the recommended way for interact with Kura rest APIs from a browser based application.

                                        The supported workflows are the following:

                                        "},{"location":"references/rest-apis/rest-session-api/#login-and-resource-access-workflow","title":"Login and resource access workflow","text":"
                                        1. Try calling the GET/xsrfToken to get an XSRF token, if the request succeeds a vaild session is already available, it is possible to proceed to step 4.

                                          • It is not necessary to call GET/xsrfToken again until the current session expires, the obtained token is valid as long as the current session is valid.
                                        2. Call the POST/login/password or POST/login/certificate providing the credentials to create a new session.

                                          • This request will return a session cookie with the response. The session cookie name is currently JSESSIONID. It is important to provide the received cookies in successive requests using the Cookie HTTP request header. If this is not done, requests will fail with 401 code. If the request is performed by a browser, cookie management should be performed automatically.

                                          • If password authentication has been used and the response object reports that a password change is needed, perform the Update password workflow

                                        3. Repeat step 1. to get an XSRF token.

                                        4. Access the desired resources, set the XSRF token previously obtained as the value of the X-XSRF-Token HTTP header on each request. If a reuqest fails with error code 401, proceed to step 2.

                                        "},{"location":"references/rest-apis/rest-session-api/#login-example-with-curl","title":"Login example with curl","text":""},{"location":"references/rest-apis/rest-session-api/#1-login-with-usernamepassword-and-collect-the-session-cookie","title":"1. Login with username/password and collect the session cookie","text":"
                                        curl -k -X POST \\\n    -H 'Content-Type: application/json' \\\n    https://$ADDRESS/services/session/v1/login/password \\\n    -d '{\"password\": \"$KURA_PASS\", \"username\": \"$KURA_USER\"}' -v\n

                                        where:

                                        • $ADDRESS: is the address of the Kura instance
                                        • $KURA_USER: is the Kura username
                                        • $KURA_PASS: is the Kura password

                                        in the log you should find a JSESSIONID you'll use in subsequent requests

                                        ...\n< HTTP/1.1 200 OK\n< Date: Tue, 14 Nov 2023 08:17:26 GMT\n< Set-Cookie: JSESSIONID=myawesomecookie; Path=/; Secure; HttpOnly\n< Expires: Thu, 01 Jan 1970 00:00:00 GMT\n< Content-Type: application/json\n< Content-Length: 30\n<\n* Connection #0 to host 192.168.1.111 left intact\n{\"passwordChangeNeeded\":false}%\n
                                        "},{"location":"references/rest-apis/rest-session-api/#2-retrieve-the-xsrf-token","title":"2. Retrieve the XSRF token","text":"
                                        curl -k -X GET \\\n    -b \"JSESSIONID=myawesomecookie\" \\\n    https://$ADDRESS/services/session/v1/xsrfToken\n

                                        in the response you'll find your XSRF token you'll need to use in subsequent requests

                                        {\"xsrfToken\":\"myawesometoken\"}%\n
                                        "},{"location":"references/rest-apis/rest-session-api/#3-access-the-resource","title":"3. Access the resource","text":"

                                        Using the Cookie and the XSRF token you just retrieved you can access your desired resource

                                        curl -k -X GET \\\n    -H 'X-XSRF-Token: myawesometoken' \\\n    -b \"JSESSIONID=myawesomecookie\" \\\n    https://$ADDRESS/services/deploy/v2/\n
                                        "},{"location":"references/rest-apis/rest-session-api/#update-password-workflow","title":"Update password workflow","text":"
                                        1. Get an XSRF token using the GET/xsrfToken endpoint.

                                        2. Call the POST/changePassword, providing both the new and old password, make sure to include the X-XSRF-Token HTTP header.

                                        3. Repeat the Authentication and resource access workflow, starting from step 1.

                                        Sessions will expire after an inactivity interval that can be configured using the Session Inactivity Interval (Seconds) RestService configuration parameter. After session expiration, a new login will be necessary.

                                        In order to add protection against XSRF attacks, it is necessary to provide an token using the X-XSRF-Token HTTP header in all requests. The token can be obtained using the GET/xsrfToken endpoint after a successful login.

                                        Session will be invalidated if the current identity credentials are changed, in this case a new login will be necessary.

                                        If a password change is required for the current identity, it will be necessary to perform the Update password workflow before being able to access the other resources.

                                        "},{"location":"references/rest-apis/rest-session-api/#reference","title":"Reference","text":"
                                        • Request definitions
                                          • POST/login/password
                                          • POST/login/certificate
                                          • GET/xsrfToken
                                          • POST/logout
                                          • POST/changePassword
                                          • GET/currentIdentity
                                          • GET/authenticationMethods
                                        • JSON definitions
                                          • AuthenticationResponse
                                          • UsernamePassword
                                          • XSRFToken
                                          • PasswordChangeRequest
                                          • IdentityInfo
                                          • AuthenticationInfo
                                          • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#request-definitions","title":"Request definitions","text":""},{"location":"references/rest-apis/rest-session-api/#postloginpassword","title":"POST/login/password","text":"
                                        • REST API path : /services/session/v1/login/password
                                        • description : Creates a new session by providing identity name and password. If the response reports that a password change is needed, it is necessary to update the current password in order to be able to access other REST APIs.
                                        • request body :
                                          • UsernamePassword
                                        • responses :
                                          • 200
                                            • description : Request succeeded.
                                            • response body :
                                              • AuthenticationResponse
                                          • 401
                                            • description : The provided credentials are not correct.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#postlogincertificate","title":"POST/login/certificate","text":"
                                        • REST API path : /services/session/v1/login/certificate
                                        • description : Creates a new session using certificate based authentication. The response will report if the current identity needs a password change for informational purposes only. If authentication is performed using this endpoint, access to other REST APIs will be allowd even if password change is required for the current identity.
                                        • responses :
                                          • 200
                                            • description : Request succeeded.
                                            • response body :
                                              • AuthenticationResponse
                                          • 401
                                            • description : The provided credentials are not correct.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#getxsrftoken","title":"GET/xsrfToken","text":"
                                        • REST API path : /services/session/v1/xsrfToken
                                        • description : Gets the XSRF token associated with the current session. It is not necessary to call this method again until the current session expires, the obtained token is valid as long as the current session is valid.
                                        • responses :
                                          • 200
                                            • description : Request succeeded, the XSRF token is returned in response body.
                                            • response body :
                                              • XSRFToken
                                          • 401
                                            • description : The current session is not valid.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#postlogout","title":"POST/logout","text":"
                                        • REST API path : /services/session/v1/logout
                                        • description : Terminates the current session.
                                        • responses :
                                          • 204
                                            • description : Request succeeded, the session is no longer valid.
                                          • 401
                                            • description : The current session is not valid
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#postchangepassword","title":"POST/changePassword","text":"
                                        • REST API path : /services/session/v1/changePassword
                                        • description : Changes the password associated with the current identity. The new password will be validated against the currently configured password strength requirements. The current password strenght requirements can be retrieved using the identity/v1/passwordRequirements endpoint.
                                        • request body :
                                          • PasswordChangeRequest
                                        • responses :
                                          • 204
                                            • description : Request succeeded. The current password has been changed. The session is no longer valid, a new login is required.
                                          • 400
                                            • description : The new password is not valid. This can be due to the fact that it does not fullfill the current password strenght requirements.
                                          • 401
                                            • description : The current session is not valid.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#getcurrentidentity","title":"GET/currentIdentity","text":"
                                        • REST API path : /services/session/v1/currentIdentity
                                        • description : Provides information about the currently authenticated identity.
                                        • responses :
                                          • 200
                                            • description : Request succeeded
                                            • response body :
                                              • IdentityInfo
                                          • 401
                                            • description : The current session is not valid.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#getauthenticationmethods","title":"GET/authenticationMethods","text":"
                                        • REST API path : /services/session/v1/authenticationInfo
                                        • description : Provides information about the available authentication methods.
                                        • responses :
                                          • 200
                                            • description : Request succeeded
                                            • response body :
                                              • AuthenticationInfo
                                          • 401
                                            • description : The current session is not valid.
                                          • 500
                                            • description : An unexpected error occurred.
                                            • response body :
                                              • GenericFailureReport
                                        "},{"location":"references/rest-apis/rest-session-api/#json-definitions","title":"JSON definitions","text":""},{"location":"references/rest-apis/rest-session-api/#authenticationresponse","title":"AuthenticationResponse","text":"

                                        Represents the response for a successful authentication request.

                                        Properties:

                                        • passwordChangeNeeded: bool Determines whether a password change is required for the current identity.
                                        {\n  \"passwordChangeNeeded\": true\n}\n
                                        "},{"location":"references/rest-apis/rest-session-api/#usernamepassword","title":"UsernamePassword","text":"

                                        Contains an username and password.

                                        Properties:

                                        • username: string The user name.

                                        • password: string The user password.

                                        {\n  \"password\": \"bar\",\n  \"username\": \"foo\"\n}\n
                                        "},{"location":"references/rest-apis/rest-session-api/#xsrftoken","title":"XSRFToken","text":"

                                        An object containing an XSRF token.

                                        Properties:

                                        • xsrfToken: string The XSRF token.
                                        {\n  \"xsrfToken\": \"d2b68613-152f-41d5-8b5b-a19448ed0e4e\"\n}\n
                                        "},{"location":"references/rest-apis/rest-session-api/#passwordchangerequest","title":"PasswordChangeRequest","text":"

                                        An object containing the current password and a new password.

                                        Properties:

                                        • currentPassword: string The current password.

                                        • newPassword: string The new password.

                                        {\n  \"currentPassword\": \"foo\",\n  \"newPassword\": \"bar\"\n}\n
                                        "},{"location":"references/rest-apis/rest-session-api/#identityinfo","title":"IdentityInfo","text":"

                                        An object containing information about the current identity

                                        Properties:

                                        • name: string The name of the current identity.

                                        • passwordChangeNeeded: bool Determines whether a password change is required for the current identity.

                                        • permissions: array The list of permissions assigned to the current identity.

                                          • array element type: string The permission name.
                                        {\n  \"name\": \"foo\",\n  \"passwordChangeNeeded\": false,\n  \"permissions\": [\n    \"rest.bar\",\n    \"rest.assets\",\n    \"rest.foo\"\n  ]\n}\n
                                        "},{"location":"references/rest-apis/rest-session-api/#authenticationinfo","title":"AuthenticationInfo","text":"

                                        An object containing information about the enabled authentication methods.

                                        Properties:

                                        • passwordAuthenticationEnabled: bool Reports whether authentication using the login/password endpoint is enabled.

                                        • certificateAuthenticationEnabled: bool Reports whether authentication using the login/certificate endpoint is enabled.

                                        • certificateAuthenticationPorts: array (optional) The list of ports available for certificate based authentication. This property will be present only if certificateAuthenticationEnabled is true

                                          • array element type: string A port that can be used for certificate based authentication.
                                        • message: string (optional) Reports the content of the Login Banner, if configured on the device. A browser based application should display this message to the user before login if this property is set. This property will be missing if the login banner is not enabled.

                                        {\n  \"certificateAuthenticationEnabled\": false,\n  \"passwordAuthenticationEnabled\": true\n}\n
                                        {\n  \"certificateAuthenticationEnabled\": true,\n  \"certificateAuthenticationPorts\": [\n    4443,\n    4444\n  ],\n  \"passwordAuthenticationEnabled\": true\n}\n
                                        {\n  \"certificateAuthenticationEnabled\": false,\n  \"message\": \"login banner content\",\n  \"passwordAuthenticationEnabled\": true\n}\n

                                        "},{"location":"references/rest-apis/rest-session-api/#genericfailurereport","title":"GenericFailureReport","text":"

                                        An object reporting a failure message.

                                        Properties:

                                        • message: string A message describing the failure.
                                        "},{"location":"references/rest-apis/rest-system-api/","title":"System","text":"

                                        Note

                                        This API can also be accessed via the RequestHandler with app-id: SYS-V1.

                                        The SystemService APIs return properties that are divided into 3 categories:

                                        • framework properties,
                                        • extended properties, and
                                        • kura properties.

                                        Identities with rest.system permissions can access these APIs.

                                        "},{"location":"references/rest-apis/rest-system-api/#framework-properties","title":"Framework properties","text":"Property name Type biosVersion String cpuVersion String deviceName String modelId String modelName String partNumber String platform String numberOfProcessors Integer totalMemory Integer freeMemory Integer serialNumber String javaHome String javaVendor String javaVersion String javaVmInfo String javaVmName String javaVmVersion String javaVmVendor String jdkVendorVersion String osArch String osDistro String osDistroVersion String osName String osVersion String isLegacyBluetoothBeaconScan Boolean isLegacyPPPLoggingEnabled Boolean primaryMacAddress String primaryNetworkInterfaceName String fileSeparator String firmwareVersion String kuraDataDirectory String kuraFrameworkConfigDirectory String kuraHomeDirectory String kuraMarketplaceCompatibilityVersion String kuraSnapshotsCount Integer kuraSnapshotsDirectory String kuraStyleDirectory String kuraTemporaryConfigDirectory String kuraUserConfigDirectory String kuraVersion String kuraHaveWebInterface Boolean kuraHaveNetAdmin Boolean kuraWifiTopChannel Integer kuraDefaultNetVirtualDevicesConfig String osgiFirmwareName String osgiFirmwareVersion String commandUser String commandZipMaxUploadNumber Integer commandZipMaxUploadSize Integer"},{"location":"references/rest-apis/rest-system-api/#kura-properties","title":"Kura properties","text":"Property name Type kura.platform String org.osgi.framework.version String kura.user.config String java.vm.vendor String kura.name String file.command.zip.max.number String kura.legacy.ppp.logging.enabled String kura.tmp String kura.packages String build.version String kura.log.download.journal.fields String kura.data String os.name String dpa.read.timeout String file.upload.size.max String console.device.management.service.ignore String kura.command.user String kura.device.name String kura.partNumber String kura.project String kura.company String java.home String version String kura.style.dir String kura.model.id String file.separator String jdk.vendor.version String kura.model.name String kura.serialNumber.provider String kura.have.web.inter String kura.legacy.bluetooth.beacon.scan String java.runtime.version String kura.bios.version String kura.marketplace.compatibility.version String kura.framework.config String kura.firmware.version String kura.plugins String os.version String kura.version String org.osgi.framework.vendor String java.runtime.name String kura.log.download.sources String os.distribution String java.vm.name String kura.primary.network.interface String kura.home String file.command.zip.max.size String os.arch String os.distribution.version String file.upload.in.memory.size.threshold String kura.net.virtual.devices.config String kura.snapshots String kura.have.net.admin String java.vm.info String java.vm.version String dpa.connection.timeout String build.number String ccs.status.notification.url String

                                        Note

                                        Some of the listed properties, depending on the device, might not be available.

                                        "},{"location":"references/rest-apis/rest-system-api/#get-methods","title":"GET methods","text":""},{"location":"references/rest-apis/rest-system-api/#get-framework-properties","title":"Get framework properties","text":"
                                        • Method: GET
                                        • API PATH: /services/system/v1/properties/framework
                                        "},{"location":"references/rest-apis/rest-system-api/#responses","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"biosVersion\": \"N/A\",\n    \"cpuVersion\": \"unknown\",\n    \"deviceName\": \"raspberry\",\n    \"modelId\": \"raspberry\",\n    \"modelName\": \"raspberry\",\n    \"partNumber\": \"raspberry\",\n    \"platform\": \"aarch64\",\n    \"numberOfProcessors\": 4,\n    \"totalMemory\": 506816,\n    \"freeMemory\": 380379,\n    \"serialNumber\": \"10000000ba7c7bfd\",\n    \"javaHome\": \"/usr/lib/jvm/java-8-openjdk-armhf/jre\",\n    \"javaVendor\": \"OpenJDK Runtime Environment\",\n    \"javaVersion\": \"1.8.0_312-8u312-b07-1+rpi1-b07\",\n    \"javaVmInfo\": \"mixed mode\",\n    \"javaVmName\": \"OpenJDK Client VM\",\n    \"javaVmVersion\": \"25.312-b07\",\n    \"javaVmVendor\": \"Temurin\",\n    \"jdkVendorVersion\": \"Zulu 8.70.0.24-SA-linux64\",\n    \"osArch\": \"arm\",\n    \"osDistro\": \"Linux\",\n    \"osDistroVersion\": \"N/A\",\n    \"osName\": \"Linux\",\n    \"osVersion\": \"6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023\",\n    \"isLegacyBluetoothBeaconScan\": false,\n    \"isLegacyPPPLoggingEnabled\": true,\n    \"primaryMacAddress\": \"E4:5F:01:35:7F:F4\",\n    \"primaryNetworkInterfaceName\": \"eth0\",\n    \"fileSeparator\": \"/\",\n    \"firmwareVersion\": \"N/A\",\n    \"kuraDataDirectory\": \"/opt/eclipse/kura/data\",\n    \"kuraFrameworkConfigDirectory\": \"/opt/eclipse/kura/framework\",\n    \"kuraHomeDirectory\": \"/opt/eclipse/kura\",\n    \"kuraMarketplaceCompatibilityVersion\": \"5.4.0.SNAPSHOT\",\n    \"kuraSnapshotsCount\": 10,\n    \"kuraSnapshotsDirectory\": \"/opt/eclipse/kura/user/snapshots\",\n    \"kuraStyleDirectory\": \"/opt/eclipse/kura/console/skin\",\n    \"kuraTemporaryConfigDirectory\": \"/tmp/.kura\",\n    \"kuraUserConfigDirectory\": \"/opt/eclipse/kura/user\",\n    \"kuraVersion\": \"KURA_5.4.0-SNAPSHOT\",\n    \"kuraHaveWebInterface\": true,\n    \"kuraHaveNetAdmin\": true,\n    \"kuraWifiTopChannel\": 2147483647,\n    \"kuraDefaultNetVirtualDevicesConfig\": \"netIPv4StatusUnmanaged\",\n    \"osgiFirmwareName\": \"Eclipse\",\n    \"osgiFirmwareVersion\": \"1.10.0\",\n    \"commandUser\": \"kura\",\n    \"commandZipMaxUploadNumber\": 1024,\n    \"commandZipMaxUploadSize\": 100\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-system-api/#get-extended-properties","title":"Get extended properties","text":"
                                        • Method: GET
                                        • API PATH: /services/system/v1/properties/extended
                                        "},{"location":"references/rest-apis/rest-system-api/#responses_1","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"version\": \"1.0\",\n    \"extendedProperties\": {\n        \"Device Info 1\": {\n            \"exampleKey1\": \"value1\",\n            \"exampleKey2\": \"value2\"\n        },\n        \"Device Info 2\": {\n            \"key1\": \"val1\",\n            \"key2\": \"val2\"\n        }\n    }\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-system-api/#get-kura-properties","title":"Get Kura properties","text":"
                                        • Method: GET
                                        • API PATH: /services/system/v1/properties/kura
                                        "},{"location":"references/rest-apis/rest-system-api/#responses_2","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"kuraProperties\": {\n        \"kura.platform\": \"aarch64\",\n        \"org.osgi.framework.version\": \"1.10.0\",\n        \"kura.user.config\": \"/opt/eclipse/kura/user\",\n        \"java.vm.vendor\": \"Temurin\",\n        \"kura.name\": \"Eclipse Kura\",\n        \"file.command.zip.max.number\": \"1024\",\n        \"kura.legacy.ppp.logging.enabled\": \"true\",\n        \"kura.tmp\": \"/tmp/.kura\",\n        \"kura.packages\": \"/opt/eclipse/kura/packages\",\n        \"build.version\": \"buildNumber\",\n        \"kura.log.download.journal.fields\": \"SYSLOG_IDENTIFIER,PRIORITY,MESSAGE,STACKTRACE\",\n        \"kura.data\": \"/opt/eclipse/kura/data\",\n        \"os.name\": \"Linux\",\n        \"dpa.read.timeout\": \"60000\",\n        \"file.upload.size.max\": \"-1\",\n        \"console.device.management.service.ignore\": \"org.eclipse.kura.net.admin.NetworkConfigurationService,org.eclipse.kura.net.admin.FirewallConfigurationService\",\n        \"kura.command.user\": \"kura\",\n        \"kura.device.name\": \"raspberry\",\n        \"kura.partNumber\": \"raspberry\",\n        \"kura.project\": \"generic-arm32\",\n        \"kura.company\": \"EUROTECH\",\n        \"java.home\": \"/usr/lib/jvm/java-8-openjdk-armhf/jre\",\n        \"version\": \"5.4.0-SNAPSHOT\",\n        \"kura.style.dir\": \"/opt/eclipse/kura/console/skin\",\n        \"kura.model.id\": \"raspberry\",\n        \"file.separator\": \"/\",\n        \"jdk.vendor.version\": \"Zulu 8.70.0.24-SA-linux64\",\n        \"kura.model.name\": \"raspberry\",\n        \"kura.serialNumber.provider\": \"cat /proc/cpuinfo | grep Serial | cut -d ' ' -f 2\",\n        \"kura.have.web.inter\": \"true\",\n        \"kura.legacy.bluetooth.beacon.scan\": \"false\",\n        \"java.runtime.version\": \"1.8.0_312-8u312-b07-1+rpi1-b07\",\n        \"kura.bios.version\": \"N/A\",\n        \"kura.marketplace.compatibility.version\": \"KURA_5.4.0-SNAPSHOT\",\n        \"kura.framework.config\": \"/opt/eclipse/kura/framework\",\n        \"kura.firmware.version\": \"N/A\",\n        \"kura.plugins\": \"/opt/eclipse/kura/plugins\",\n        \"os.version\": \"6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023\",\n        \"kura.version\": \"KURA_5.4.0-SNAPSHOT\",\n        \"org.osgi.framework.vendor\": \"Eclipse\",\n        \"java.runtime.name\": \"OpenJDK Runtime Environment\",\n        \"kura.log.download.sources\": \"/var/log\",\n        \"os.distribution\": \"Linux\",\n        \"java.vm.name\": \"OpenJDK Client VM\",\n        \"kura.primary.network.interface\": \"eth0\",\n        \"kura.home\": \"/opt/eclipse/kura\",\n        \"file.command.zip.max.size\": \"100\",\n        \"os.arch\": \"arm\",\n        \"os.distribution.version\": \"N/A\",\n        \"file.upload.in.memory.size.threshold\": \"10240\",\n        \"kura.net.virtual.devices.config\": \"unmanaged\",\n        \"kura.snapshots\": \"/opt/eclipse/kura/user/snapshots\",\n        \"kura.have.net.admin\": \"true\",\n        \"java.vm.info\": \"mixed mode\",\n        \"java.vm.version\": \"25.312-b07\",\n        \"dpa.connection.timeout\": \"60000\",\n        \"build.number\": \"generic-arm32-buildNumber\",\n        \"ccs.status.notification.url\": \"ccs:log\"\n    }\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-system-api/#post-methods","title":"POST methods","text":""},{"location":"references/rest-apis/rest-system-api/#filter-framework-properties","title":"Filter framework properties","text":"

                                        This method allows to retrieve framework-related properties by their name. Available properties are in table Framework properties.

                                        • Method: POST
                                        • API PATH: /services/system/v1/properties/framework/filter
                                        "},{"location":"references/rest-apis/rest-system-api/#request-body","title":"Request Body","text":"
                                        {\n    \"names\": [\"deviceName\", \"numberOfProcessors\", \"kuraHaveNetAdmin\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-system-api/#responses_3","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"deviceName\": \"RASBPERRY PI 4\",\n    \"numberOfProcessors\": 4,\n    \"kuraHaveNetAdmin\": true\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-system-api/#filter-extended-properties","title":"Filter extended properties","text":"

                                        This method allows to retrieve the extended properties and to filter them by group name.

                                        • Method: POST
                                        • API PATH: /services/system/v1/properties/extended/filter
                                        "},{"location":"references/rest-apis/rest-system-api/#request-body_1","title":"Request Body","text":"
                                        {\n    \"groupNames\": [\"Device Info 1\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-system-api/#responses_4","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"extendedProperties\": {\n        \"Device Info 1\": {\n            \"exampleKey1\": \"value1\",\n            \"exampleKey2\": \"value2\"\n        }\n    }\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"references/rest-apis/rest-system-api/#filter-kura-properties","title":"Filter kura properties","text":"

                                        This method allows to retrieve Kura-related properties (derived from the kura.properties file) and filter by their name. Available properties are listed in the table Kura properties.

                                        • Method: POST
                                        • API PATH: /services/system/v1/properties/kura/filter
                                        "},{"location":"references/rest-apis/rest-system-api/#request-body_2","title":"Request Body","text":"
                                        {\n    \"names\": [\"kura.platform\", \"file.upload.size.max\", \"kura.have.net.admin\"]\n}\n
                                        "},{"location":"references/rest-apis/rest-system-api/#responses_5","title":"Responses","text":"
                                        • 200 OK status
                                        {\n    \"kuraProperties\": {\n        \"kura.platform\": \"aarch64\",\n        \"kura.have.net.admin\": \"true\",\n        \"file.upload.size.max\": \"-1\"\n    }\n}\n
                                        • 500 Internal Server Error
                                        "},{"location":"tutorials/AD-EdgeAI/","title":"Edge AI Anomaly Detection","text":"In\u00a0[1]: Copied!
                                        !wget https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv\n
                                        !wget https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv
                                        --2022-10-18 15:32:34--  https://raw.githubusercontent.com/mattdibi/eclipsecon-edgeAI-talk/master/notebook/train-data-raw.csv\nResolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...\nConnecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.\nHTTP request sent, awaiting response... 200 OK\nLength: 13288126 (13M) [text/plain]\nSaving to: \u2018train-data-raw.csv.1\u2019\n\ntrain-data-raw.csv. 100%[===================>]  12,67M  4,56MB/s    in 2,8s    \n\n2022-10-18 15:32:38 (4,56 MB/s) - \u2018train-data-raw.csv.1\u2019 saved [13288126/13288126]\n\n
                                        In\u00a0[2]: Copied!
                                        !ls *.csv\n
                                        !ls *.csv
                                        train-data-raw.csv\n

                                        Let's start taking a look at the content of this dataset, we'll use pandas (Python Data Analysis library) for this.

                                        In\u00a0[3]: Copied!
                                        import pandas as pd\n\nraw_data = pd.read_csv(\"./train-data-raw.csv\")\n\nraw_data.head()\n
                                        import pandas as pd raw_data = pd.read_csv(\"./train-data-raw.csv\") raw_data.head() Out[3]: ID TIMESTAMP MAGNET_X TEMP_HUM_timestamp MAGNET_Z MAGNET_Y ACC_Y ACC_X GYRO_Y_timestamp ACC_Z ... PRESSURE_timestamp MAGNET_X_timestamp ACC_X_timestamp GYRO_Z_timestamp HUMIDITY_timestamp assetName ACC_Z_timestamp GYRO_X GYRO_Y GYRO_Z 0 1 1645778791786 -2.680372 1645778791413 5.036951 8.646852 0.004364 0.080122 1645778791413 0.984048 ... 1645778791413 1645778791413 1645778791413 1645778791413 1645778791413 asset-sensehat 1645778791413 0.053243 0.028920 0.036950 1 2 1645778792381 -3.110756 1645778792378 5.952562 10.521458 0.005091 0.080122 1645778792378 0.992090 ... 1645778792378 1645778792378 1645778792378 1645778792378 1645778792378 asset-sensehat 1645778792378 -0.051105 -0.028920 -0.037256 2 3 1645778793412 -3.482263 1645778793408 6.719675 11.944528 0.005334 0.080122 1645778793408 0.986729 ... 1645778793408 1645778793408 1645778793408 1645778793408 1645778793408 asset-sensehat 1645778793408 -0.025253 0.025560 0.038478 3 4 1645778794411 -3.813552 1645778794407 7.375115 13.093461 0.006061 0.080122 1645778794407 0.990384 ... 1645778794407 1645778794407 1645778794407 1645778794407 1645778794407 asset-sensehat 1645778794407 0.100695 -0.023422 -0.037867 4 5 1645778795411 -4.050513 1645778795407 7.854155 14.029530 0.004849 0.080607 1645778795407 0.988922 ... 1645778795407 1645778795407 1645778795407 1645778795407 1645778795407 asset-sensehat 1645778795407 -0.100389 0.021895 0.038172

                                        5 rows \u00d7 29 columns

                                        In\u00a0[4]: Copied!
                                        features = ['ACC_Y', 'ACC_X', 'ACC_Z',\n            'PRESSURE', 'TEMP_PRESS', 'TEMP_HUM',\n            'HUMIDITY', 'GYRO_X', 'GYRO_Y', 'GYRO_Z']\n\ndata = raw_data[features]\n\ndata.head()\n
                                        features = ['ACC_Y', 'ACC_X', 'ACC_Z', 'PRESSURE', 'TEMP_PRESS', 'TEMP_HUM', 'HUMIDITY', 'GYRO_X', 'GYRO_Y', 'GYRO_Z'] data = raw_data[features] data.head() Out[4]: ACC_Y ACC_X ACC_Z PRESSURE TEMP_PRESS TEMP_HUM HUMIDITY GYRO_X GYRO_Y GYRO_Z 0 0.004364 0.080122 0.984048 992.322998 38.724998 40.330822 19.487146 0.053243 0.028920 0.036950 1 0.005091 0.080122 0.992090 992.288330 38.772915 40.385788 19.465750 -0.051105 -0.028920 -0.037256 2 0.005334 0.080122 0.986729 992.275635 38.795834 40.349144 19.572731 -0.025253 0.025560 0.038478 3 0.006061 0.080122 0.990384 992.279053 38.797916 40.330822 19.358767 0.100695 -0.023422 -0.037867 4 0.004849 0.080607 0.988922 992.333008 38.845833 40.385788 19.390862 -0.100389 0.021895 0.038172 In\u00a0[5]: Copied!
                                        %matplotlib inline\nimport matplotlib.pyplot as plt\n\ndata.hist(bins=50, figsize=(20,15))\nplt.show()\n
                                        %matplotlib inline import matplotlib.pyplot as plt data.hist(bins=50, figsize=(20,15)) plt.show()

                                        Note: Some of you might notice that this is a really simple dataset: some of the input data (like GYRO_* and ACC_*) do not change much over time. Such a dataset is not very challenging and a few, well-placed, thresholds might be sufficient to spot anomalous behaviour. For this tutorial we decided to keep things simple and easy to replicate. Anomalies can be simply triggered by moving the Raspberry Pi around.

                                        Keep in mind that this approach is generic: any dataset from any appliance/connected device can be processed in the same way we're showing here. That's the magic of neural networks!

                                        In\u00a0[6]: Copied!
                                        print(\"Data used in the Triton preprocessor\")\nprint(\"-----------Min-----------\")\nprint(data.min())\nprint(\"-----------Max-----------\")\nprint(data.max())\nprint(\"-------------------------\")\n
                                        print(\"Data used in the Triton preprocessor\") print(\"-----------Min-----------\") print(data.min()) print(\"-----------Max-----------\") print(data.max()) print(\"-------------------------\")
                                        Data used in the Triton preprocessor\n-----------Min-----------\nACC_Y          -0.132551\nACC_X          -0.049693\nACC_Z           0.759847\nPRESSURE      976.001709\nTEMP_PRESS     38.724998\nTEMP_HUM       40.220890\nHUMIDITY       13.003981\nGYRO_X         -1.937896\nGYRO_Y         -0.265019\nGYRO_Z         -0.250647\ndtype: float64\n-----------Max-----------\nACC_Y            0.093099\nACC_X            0.150289\nACC_Z            1.177543\nPRESSURE      1007.996338\nTEMP_PRESS      46.093750\nTEMP_HUM        48.355824\nHUMIDITY        23.506138\nGYRO_X           1.923712\nGYRO_Y           0.219204\nGYRO_Z           0.671759\ndtype: float64\n-------------------------\n
                                        In\u00a0[7]: Copied!
                                        from sklearn.preprocessing import MinMaxScaler\n\nscaler = MinMaxScaler()\nscaled_data = scaler.fit_transform(data.to_numpy())\n
                                        from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() scaled_data = scaler.fit_transform(data.to_numpy()) In\u00a0[8]: Copied!
                                        pd.DataFrame(scaled_data).describe()\n
                                        pd.DataFrame(scaled_data).describe() Out[8]: 0 1 2 3 4 5 6 7 8 9 count 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 25278.000000 mean 0.603124 0.674196 0.550454 0.526446 0.605576 0.552252 0.466400 0.501160 0.545457 0.271295 std 0.049333 0.015135 0.031627 0.054050 0.288300 0.256587 0.176293 0.062908 0.067678 0.014665 min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 25% 0.597087 0.667343 0.544924 0.481917 0.501060 0.441442 0.325637 0.501348 0.544670 0.270709 50% 0.603534 0.673413 0.551342 0.521377 0.655357 0.608108 0.511715 0.501841 0.547096 0.271685 75% 0.611055 0.680698 0.555426 0.552892 0.819339 0.734234 0.575212 0.502407 0.549386 0.272577 max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 In\u00a0[9]: Copied!
                                        from sklearn.model_selection import train_test_split\nimport numpy as np\n\nx_train, x_test = train_test_split(scaled_data, test_size=0.3, random_state=42)\nx_train = x_train.astype(np.float32)\nx_test = x_test.astype(np.float32)\n
                                        from sklearn.model_selection import train_test_split import numpy as np x_train, x_test = train_test_split(scaled_data, test_size=0.3, random_state=42) x_train = x_train.astype(np.float32) x_test = x_test.astype(np.float32) In\u00a0[10]: Copied!
                                        import os\nos.environ['TF_CPP_MIN_LOG_LEVEL']='2' # Avoid AVX2 error\n\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Input, Dense, Dropout\n\ndef create_model(input_dim):\n    # The encoder will consist of a number of dense layers that decrease in size\n    # as we taper down towards the bottleneck of the network, the latent space\n    input_data = Input(shape=(input_dim,), name='INPUT0')\n\n    # hidden layers\n    encoder = Dense(9, activation='tanh', name='encoder_1')(input_data)\n    encoder = Dropout(.15)(encoder)\n    encoder = Dense(6, activation='tanh', name='encoder_2')(encoder)\n    encoder = Dropout(.15)(encoder)\n\n    # bottleneck layer\n    latent_encoding = Dense(3, activation='linear', name='latent_encoding')(encoder)\n\n    # The decoder network is a mirror image of the encoder network\n    decoder = Dense(6, activation='tanh', name='decoder_1')(latent_encoding)\n    decoder = Dropout(.15)(decoder)\n    decoder = Dense(9, activation='tanh', name='decoder_2')(decoder)\n    decoder = Dropout(.15)(decoder)\n\n    # The output is the same dimension as the input data we are reconstructing\n    reconstructed_data = Dense(input_dim, activation='linear', name='OUTPUT0')(decoder)\n\n    autoencoder_model = Model(input_data, reconstructed_data)\n\n    return autoencoder_model\n
                                        import os os.environ['TF_CPP_MIN_LOG_LEVEL']='2' # Avoid AVX2 error from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Dense, Dropout def create_model(input_dim): # The encoder will consist of a number of dense layers that decrease in size # as we taper down towards the bottleneck of the network, the latent space input_data = Input(shape=(input_dim,), name='INPUT0') # hidden layers encoder = Dense(9, activation='tanh', name='encoder_1')(input_data) encoder = Dropout(.15)(encoder) encoder = Dense(6, activation='tanh', name='encoder_2')(encoder) encoder = Dropout(.15)(encoder) # bottleneck layer latent_encoding = Dense(3, activation='linear', name='latent_encoding')(encoder) # The decoder network is a mirror image of the encoder network decoder = Dense(6, activation='tanh', name='decoder_1')(latent_encoding) decoder = Dropout(.15)(decoder) decoder = Dense(9, activation='tanh', name='decoder_2')(decoder) decoder = Dropout(.15)(decoder) # The output is the same dimension as the input data we are reconstructing reconstructed_data = Dense(input_dim, activation='linear', name='OUTPUT0')(decoder) autoencoder_model = Model(input_data, reconstructed_data) return autoencoder_model In\u00a0[11]: Copied!
                                        autoencoder_model = create_model(len(features))\nautoencoder_model.summary()\n
                                        autoencoder_model = create_model(len(features)) autoencoder_model.summary()
                                        Model: \"model\"\n_________________________________________________________________\n Layer (type)                Output Shape              Param #   \n=================================================================\n INPUT0 (InputLayer)         [(None, 10)]              0         \n                                                                 \n encoder_1 (Dense)           (None, 9)                 99        \n                                                                 \n dropout (Dropout)           (None, 9)                 0         \n                                                                 \n encoder_2 (Dense)           (None, 6)                 60        \n                                                                 \n dropout_1 (Dropout)         (None, 6)                 0         \n                                                                 \n latent_encoding (Dense)     (None, 3)                 21        \n                                                                 \n decoder_1 (Dense)           (None, 6)                 24        \n                                                                 \n dropout_2 (Dropout)         (None, 6)                 0         \n                                                                 \n decoder_2 (Dense)           (None, 9)                 63        \n                                                                 \n dropout_3 (Dropout)         (None, 9)                 0         \n                                                                 \n OUTPUT0 (Dense)             (None, 10)                100       \n                                                                 \n=================================================================\nTotal params: 367\nTrainable params: 367\nNon-trainable params: 0\n_________________________________________________________________\n

                                        In\u00a0[12]: Copied!
                                        from tensorflow.keras import optimizers\n\nbatch_size = 32\nmax_epochs = 15\nlearning_rate = .0001\n\nopt = optimizers.Adam(learning_rate=learning_rate)\nautoencoder_model.compile(optimizer=opt, loss='mse', metrics=['accuracy'])\ntrain_history = autoencoder_model.fit(x_train, x_train,\n                      shuffle=True,\n                      epochs=max_epochs,\n                      batch_size=batch_size,\n                      validation_data=(x_test, x_test))\n
                                        from tensorflow.keras import optimizers batch_size = 32 max_epochs = 15 learning_rate = .0001 opt = optimizers.Adam(learning_rate=learning_rate) autoencoder_model.compile(optimizer=opt, loss='mse', metrics=['accuracy']) train_history = autoencoder_model.fit(x_train, x_train, shuffle=True, epochs=max_epochs, batch_size=batch_size, validation_data=(x_test, x_test))
                                        Epoch 1/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.2282 - accuracy: 0.1129 - val_loss: 0.0922 - val_accuracy: 0.0045\nEpoch 2/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0949 - accuracy: 0.1541 - val_loss: 0.0279 - val_accuracy: 0.4210\nEpoch 3/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0613 - accuracy: 0.1779 - val_loss: 0.0206 - val_accuracy: 0.4426\nEpoch 4/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0466 - accuracy: 0.2152 - val_loss: 0.0186 - val_accuracy: 0.5276\nEpoch 5/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0366 - accuracy: 0.2514 - val_loss: 0.0157 - val_accuracy: 0.5944\nEpoch 6/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0290 - accuracy: 0.3083 - val_loss: 0.0119 - val_accuracy: 0.6403\nEpoch 7/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0228 - accuracy: 0.3930 - val_loss: 0.0078 - val_accuracy: 0.7182\nEpoch 8/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0186 - accuracy: 0.4668 - val_loss: 0.0059 - val_accuracy: 0.8195\nEpoch 9/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0157 - accuracy: 0.5021 - val_loss: 0.0048 - val_accuracy: 0.8256\nEpoch 10/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0136 - accuracy: 0.5277 - val_loss: 0.0042 - val_accuracy: 0.8263\nEpoch 11/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0121 - accuracy: 0.5409 - val_loss: 0.0037 - val_accuracy: 0.8296\nEpoch 12/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0107 - accuracy: 0.5569 - val_loss: 0.0036 - val_accuracy: 0.8306\nEpoch 13/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0098 - accuracy: 0.5857 - val_loss: 0.0034 - val_accuracy: 0.8256\nEpoch 14/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0089 - accuracy: 0.6076 - val_loss: 0.0033 - val_accuracy: 0.8281\nEpoch 15/15\n553/553 [==============================] - 1s 1ms/step - loss: 0.0083 - accuracy: 0.6337 - val_loss: 0.0032 - val_accuracy: 0.8262\n
                                        In\u00a0[13]: Copied!
                                        plt.plot(train_history.history['loss'])\nplt.plot(train_history.history['val_loss'])\nplt.legend(['loss on train data', 'loss on test data'])\n
                                        plt.plot(train_history.history['loss']) plt.plot(train_history.history['val_loss']) plt.legend(['loss on train data', 'loss on test data']) Out[13]:
                                        <matplotlib.legend.Legend at 0x16d0c3400>

                                        Here we can see the loss for the training set and the test set on the epochs.

                                        Some of you might notice that this graph is somewhat unexpected. Why the validation loss is lower than the train loss? This is the effect of the regularization: regularization terms and dropout layer are affecting the network during training. A good writeup of this effect can be found here.

                                        As an excercise try and compute the average MSE on the training set and the test set. You'll find that the MSE is lower in the training set!

                                        We can now save the model on disk as we'll use this later.

                                        In\u00a0[14]: Copied!
                                        autoencoder_model.save(\"./saved_model/autoencoder\")\n
                                        autoencoder_model.save(\"./saved_model/autoencoder\")
                                        WARNING:absl:Function `_wrapped_model` contains input name(s) INPUT0 with unsupported characters which will be renamed to input0 in the SavedModel.\n
                                        INFO:tensorflow:Assets written to: ./saved_model/autoencoder/assets\n
                                        INFO:tensorflow:Assets written to: ./saved_model/autoencoder/assets\n
                                        In\u00a0[15]: Copied!
                                        !ls ./saved_model/autoencoder\n
                                        !ls ./saved_model/autoencoder
                                        assets            keras_metadata.pb saved_model.pb    variables\n
                                        In\u00a0[16]: Copied!
                                        input_sample = x_test[3:4].copy() # Deep copy\n\nreconstructed_sample = autoencoder_model.predict(input_sample)\n\nprint(input_sample)\nprint(reconstructed_sample)\n
                                        input_sample = x_test[3:4].copy() # Deep copy reconstructed_sample = autoencoder_model.predict(input_sample) print(input_sample) print(reconstructed_sample)
                                        1/1 [==============================] - 0s 109ms/step\n[[0.603534   0.6770555  0.54900813 0.5327966  0.6680801  0.6171171\n  0.5198642  0.50135666 0.54716927 0.2718224 ]]\n[[0.59638697 0.67410123 0.5484349  0.52024144 0.64766663 0.5916597\n  0.4445051  0.499677   0.54471916 0.26904327]]\n
                                        In\u00a0[17]: Copied!
                                        import matplotlib.pyplot as plt\n\nindex = np.arange(10)\nbar_width = 0.35\n\nfigure, ax = plt.subplots()\n\ninbar = ax.bar(index, input_sample[0], bar_width, label=\"Input data\")\nrecbar = ax.bar(index+bar_width, reconstructed_sample[0], bar_width, label=\"Reconstruced data\")\n\nax.set_xlabel('Features')\nax.set_xticks(index + bar_width / 2)\nax.set_xticklabels(features, rotation = 45)\nax.legend()\n
                                        import matplotlib.pyplot as plt index = np.arange(10) bar_width = 0.35 figure, ax = plt.subplots() inbar = ax.bar(index, input_sample[0], bar_width, label=\"Input data\") recbar = ax.bar(index+bar_width, reconstructed_sample[0], bar_width, label=\"Reconstruced data\") ax.set_xlabel('Features') ax.set_xticks(index + bar_width / 2) ax.set_xticklabels(features, rotation = 45) ax.legend() Out[17]:
                                        <matplotlib.legend.Legend at 0x16d184880>

                                        As we can see from the graph above it reconstructed the input fairly well. It is not perfect since the Autoencoder is lossy but it is good enough

                                        What happens if we manipulate this sample in a way the autoencoder doesn't expect (i.e. we introduce an anomaly)?

                                        Let's try and set the ACC_Z to a value the autoencoder has never seen before.

                                        In\u00a0[18]: Copied!
                                        input_anomaly = input_sample.copy() # Deep copy\n\ninput_anomaly[0][2] = 0.15\n\nreconstructed_anomaly = autoencoder_model.predict(input_anomaly)\n\nprint(input_anomaly)\nprint(reconstructed_anomaly)\n
                                        input_anomaly = input_sample.copy() # Deep copy input_anomaly[0][2] = 0.15 reconstructed_anomaly = autoencoder_model.predict(input_anomaly) print(input_anomaly) print(reconstructed_anomaly)
                                        1/1 [==============================] - 0s 21ms/step\n[[0.603534   0.6770555  0.15       0.5327966  0.6680801  0.6171171\n  0.5198642  0.50135666 0.54716927 0.2718224 ]]\n[[0.60162103 0.69035804 0.55594885 0.51874125 0.7346029  0.6700014\n  0.40932336 0.5034408  0.5424664  0.26861513]]\n
                                        In\u00a0[19]: Copied!
                                        figure, ax = plt.subplots()\n\ninbar = ax.bar(index, input_anomaly[0], bar_width, label=\"Input anomaly\")\nrecbar = ax.bar(index+bar_width, reconstructed_anomaly[0], bar_width, label=\"Reconstruced anomaly\")\n\nax.set_xlabel('Features')\nax.set_xticks(index + bar_width / 2)\nax.set_xticklabels(features, rotation = 45)\nax.legend()\n
                                        figure, ax = plt.subplots() inbar = ax.bar(index, input_anomaly[0], bar_width, label=\"Input anomaly\") recbar = ax.bar(index+bar_width, reconstructed_anomaly[0], bar_width, label=\"Reconstruced anomaly\") ax.set_xlabel('Features') ax.set_xticks(index + bar_width / 2) ax.set_xticklabels(features, rotation = 45) ax.legend() Out[19]:
                                        <matplotlib.legend.Legend at 0x16d3c0220>

                                        The autoencoder fails to reconstruct the data it received at the input. This means that the reconstruction error is very high.

                                        In\u00a0[20]: Copied!
                                        from sklearn.metrics import mean_squared_error\n\nprint(\"Anomaly %f\"% mean_squared_error(input_anomaly[0], reconstructed_anomaly[0]))\nprint(\"Normal  %f\"% mean_squared_error(input_sample[0], reconstructed_sample[0]))\n
                                        from sklearn.metrics import mean_squared_error print(\"Anomaly %f\"% mean_squared_error(input_anomaly[0], reconstructed_anomaly[0])) print(\"Normal %f\"% mean_squared_error(input_sample[0], reconstructed_sample[0]))
                                        Anomaly 0.018465\nNormal  0.000698\n

                                        It's working as expected!

                                        We now need to decide when to trigger an alarm (i.e. classify an input sample as anomalous) from this reconstruction error. In other words we need to decide our threshold.

                                        There are multiple ways to set this value, in this example we'll use the Z-Score.

                                        From Wikipedia:

                                        In statistics, the standard score is the number of standard deviations by which the value of a raw score (i.e., an observed value or data point) is above or below the mean value of what is being observed or measured.[...]

                                        It is calculated by subtracting the population mean from an individual raw score and then dividing the difference by the population standard deviation.

                                        We'll consider a sample an anomaly if the Reconstruction Error Z-Score is not in the range [-2, +2]. This means that if the reconstruction error for a sample is more than 2 standard deviation away from the average reconstruction error computed on the test set, the sample is an anomaly. This choice is arbirtary, we can control the sensitivity of the detector by changing this range.

                                        In\u00a0[21]: Copied!
                                        x_test_recon = autoencoder_model.predict(x_test)\nreconstruction_scores = np.mean((x_test - x_test_recon)**2, axis=1)  # MSE\n\nreconstruction_scores_pd = pd.DataFrame({'recon_score': reconstruction_scores})\nprint(reconstruction_scores_pd.describe())\n
                                        x_test_recon = autoencoder_model.predict(x_test) reconstruction_scores = np.mean((x_test - x_test_recon)**2, axis=1) # MSE reconstruction_scores_pd = pd.DataFrame({'recon_score': reconstruction_scores}) print(reconstruction_scores_pd.describe())
                                        237/237 [==============================] - 0s 620us/step\n       recon_score\ncount  7584.000000\nmean      0.003175\nstd       0.005438\nmin       0.000098\n25%       0.000816\n50%       0.001211\n75%       0.002108\nmax       0.106237\n
                                        In\u00a0[22]: Copied!
                                        def z_score(mse_sample):\n    return (mse_sample - reconstruction_scores_pd.mean())/reconstruction_scores_pd.std()\n
                                        def z_score(mse_sample): return (mse_sample - reconstruction_scores_pd.mean())/reconstruction_scores_pd.std() In\u00a0[23]: Copied!
                                        mse_anomaly = mean_squared_error(input_anomaly[0], reconstructed_anomaly[0])\nmse_normal = mean_squared_error(input_sample[0], reconstructed_sample[0])\n\nz_score_anomaly = z_score(mse_anomaly)\nz_score_normal = z_score(mse_normal)\n\nprint(\"Anomaly Z-score %f\"% z_score_anomaly)\nprint(\"Normal Z-score %f\"% z_score_normal)\n
                                        mse_anomaly = mean_squared_error(input_anomaly[0], reconstructed_anomaly[0]) mse_normal = mean_squared_error(input_sample[0], reconstructed_sample[0]) z_score_anomaly = z_score(mse_anomaly) z_score_normal = z_score(mse_normal) print(\"Anomaly Z-score %f\"% z_score_anomaly) print(\"Normal Z-score %f\"% z_score_normal)
                                        Anomaly Z-score 2.811887\nNormal Z-score -0.455488\n

                                        We now have our anomaly detector... let's see how we can deploy it on our Kura\u2122-powered edge device.

                                        In\u00a0[24]: Copied!
                                        !rm -rf ./tf_autoencoder_fp32/ && mkdir -p ./tf_autoencoder_fp32/1\n
                                        !rm -rf ./tf_autoencoder_fp32/ && mkdir -p ./tf_autoencoder_fp32/1 In\u00a0[25]: Copied!
                                        !ls\n
                                        !ls
                                        AD-EdgeAI.ipynb      requirements.txt     train-data-raw.csv\nREADME.md            saved_model          train-data-raw.csv.1\nimgs                 tf_autoencoder_fp32\n
                                        In\u00a0[26]: Copied!
                                        cp -r ./saved_model/autoencoder tf_autoencoder_fp32/1/model.savedmodel\n
                                        cp -r ./saved_model/autoencoder tf_autoencoder_fp32/1/model.savedmodel In\u00a0[27]: Copied!
                                        !tree tf_autoencoder_fp32\n
                                        !tree tf_autoencoder_fp32
                                        tf_autoencoder_fp32\n\u2514\u2500\u2500 1\n    \u2514\u2500\u2500 model.savedmodel\n        \u251c\u2500\u2500 assets\n        \u251c\u2500\u2500 keras_metadata.pb\n        \u251c\u2500\u2500 saved_model.pb\n        \u2514\u2500\u2500 variables\n            \u251c\u2500\u2500 variables.data-00000-of-00001\n            \u2514\u2500\u2500 variables.index\n\n4 directories, 4 files\n

                                        Now comes the hard part: we need to provide the model configuration (i.e. the config.pbtxt file). In the case of the autoencoder is pretty simple:

                                        name: \"tf_autoencoder_fp32\"\nbackend: \"tensorflow\"\nmax_batch_size: 0\ninput [\n    {\n    name: \"INPUT0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n    }\n]\noutput [\n    {\n    name: \"OUTPUT0\"\n    data_type: TYPE_FP32\n    dims: [ -1, 10 ]\n    }\n]\nversion_policy: { all { }}\ninstance_group [{ kind: KIND_CPU }]\n

                                        Each model input and output must specify the name, data_type and dims. We already know all of these:

                                        • name: corresponds to the layer name we've seen in the Model Training section. INPUT0 for the input and OUTPUT0 for the output.
                                        • data_type: will be float since we didn't perform any quantization
                                        • dims: is the shape of the in/out tensor. In this case it will correspond to an array with the same length as the number of features.

                                        Other interesting parameters of this configuration are:

                                        • backend: where we set the backend for the model. In this case it will be the Tensorflow backend
                                        • name: the name of the model that must correspond to the name of the folder
                                        • instance_group: where we set where we want the model to run. In this case we'll use the CPU since we're on a Raspberry Pi but keep in mind that Triton support multiple accelerators.

                                        for a deep dive into the model configuration parameter take a look at the official documentation.

                                        "},{"location":"tutorials/AD-EdgeAI/#edge-ai-anomaly-detection","title":"Edge AI Anomaly Detection\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                                        This document contains the code and the instructions for our EclipseCON 2022 Talk: \"How to Train Your Dragon and Its Friends: AI on the Edge with Eclipse Kura\u2122\"

                                        This notebook can also be viewed and ran on Google Colab.

                                        In this example scenario we will collect the data provided by a Raspberry Pi Sense HAT using Eclipse Kura\u2122 and upload them to a Eclipse Kapua\u2122 instance. We will then download this data and train an AI-based anomaly detector using TensorFlow. Finally we will deploy the trained anomaly detector model leveraging Nvidia Triton\u2122 Inference Server and Eclipse Kura\u2122 integration.

                                        We'll subdivide this example scenario in three main sections:

                                        1. Data collection: in this section we'll discuss how to retrieve training data from the field leveraging Eclipse Kura\u2122 and Eclipse Kapua\u2122
                                        2. Model building and training: we'll further divide this section in three subsections:
                                          • Data processing: where we'll show how to explore our training data and manipulate them to make them suitable for training (feature selection, scaling and dataset splitting). This will provide us with the \"Preprocessing\" stage of the resulting AI data-processing pipeline
                                          • Model training: where we'll discuss how we can create a simple Autoencoder in Tensorflow Keras and how to train it. This will provide us with the \"Inference\" stage of the AI pipeline
                                          • Model evaluation: where we'll cover how can we extract the high level data from the model output and ensure the model was trained correctly. This will provide us with the \"Postprocessing\" stage of the AI pipeline
                                        3. Model deployment: finally we will convert the model to make it suitable for running on Eclipse Kura\u2122 and Nvidia Triton\u2122 and deploy it on the edge.
                                        "},{"location":"tutorials/AD-EdgeAI/#data-collection","title":"Data collection\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                                        In this setup we'll leverage Eclipe Kura\u2122 and Kapua\u2122 for retrieving data from a Raspberry Pi Sense HAT and upload them to the cloud.

                                        The Sense HAT is an add-on board for Raspberry Pi which provides an 8\u00d78 RGB LED matrix, a five-button joystick and includes the following sensors:

                                        • Gyroscope
                                        • Accelerometer
                                        • Magnetometer
                                        • Temperature
                                        • Barometric pressure
                                        • Humidity

                                        "},{"location":"tutorials/AD-EdgeAI/#kuratm-installation","title":"Kura\u2122 installation\u00b6","text":"

                                        Requirement: A Raspberry Pi 3/4 running the latest version of Raspberry Pi OS 64 bit.

                                        To make everything work on the Raspberry Pi we need to use the develop version of the raspberry-pi-ubuntu-20-nn Kura installer (yes, I know we're installing the Ubuntu package on the Raspberry Pi OS but bear with me...) . You can do so by downloading the repo and building locally or by downloading a pre-built installer from the Kura CI artifacts.

                                        Copy the resulting file kura_<version>_raspberry-pi-ubuntu-20_installer-nn.deb on the target device.

                                        On the target device run the following commands:

                                        sudo apt-get install -y wget apt-transport-https gnupg\n
                                        sudo wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | sudo apt-key add -\n
                                        sudo echo \"deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main\" | sudo tee /etc/apt/sources.list.d/adoptium.list\n
                                        sudo apt-get update && sudo apt-get install temurin-8-jdk chrony\n

                                        Finally install Kura with:

                                        sudo apt install ./kura_<version>_raspberry-pi-ubuntu-20_installer-nn.deb\n
                                        "},{"location":"tutorials/AD-EdgeAI/#cloud-connection","title":"Cloud connection\u00b6","text":"

                                        After setting up an Eclipse Kura\u2122 instance on the Raspberry Pi we'll need to connect it to an Eclipse Kapua\u2122 instance.

                                        An excellent tutorial on how to deploy a Kapua\u2122 instance using Docker is available in the official repository. For the purpose of this tutorial we'll assume a Kapua\u2122 instance is already running and is available for connection from Kura\u2122

                                        After setting up the Kapua\u2122 instance you can refer to the official Kura\u2122 documentation for connecting the Raspberry Pi to the Kapua\u2122 instance. For the remaining of this tutorial we'll assume a connection with the Kapua\u2122 was correctly established.

                                        "},{"location":"tutorials/AD-EdgeAI/#data-publisher","title":"Data publisher\u00b6","text":"

                                        To publish the collected data on the Cloud we'll need to create a new Cloud Publisher through the Kura\u2122 web interface. Go to \"Cloud Connections\" and press \"New Pub/Sub\", in the example below we'll call our new publisher KapuaSenseHatPublisher.

                                        To keep things clean we'll create a new topic called SenseHat. To do so we'll move to the KapuaSenseHatPublisher configuration and we'll update the Application Topic field to A1/SenseHat

                                        "},{"location":"tutorials/AD-EdgeAI/#sensehat-driver","title":"SenseHat driver\u00b6","text":"

                                        Kura\u2122 provides a driver that allows to interact to a RaspberryPi SenseHat device using Kura Driver, Asset and Wires frameworks.

                                        From the Kura\u2122 documentation:

                                        Eclipse Kura introduces a model based on the concepts of Drivers and Assets to simplify the communication with the field devices attached to a gateway.

                                        A Driver encapsulates the communication protocol and its configuration parameters, dealing with the low-level characteristics of the field protocol. It opens, closes and performs the communication with the end field device. It also exposes field protocol specific information that can be used by upper levels of abstraction to simplify the interaction with the end devices.

                                        An Asset is a logical representation of a field device, described by a list of Channels. The Asset uses a specific Driver instance to communicate with the underlying device and it models a generic device resource as a Channel. A register in a PLC or a GATT Characteristic in a Bluetooth device are examples of Channels. In this way, each Asset has multiple Channels for reading and writing data from/to an Industrial Device.

                                        The Kura Sense Hat driver requires a few changes on the Raspberry Pi:

                                        • Configured SenseHat: see SenseHat documentation
                                        • I2C interface should be unlocked using sudo raspi-config

                                        As others Drivers supported by Kura, it is distributed as a deployment package on the Eclipse Marketplace. It consists of two packages:

                                        • SenseHat Example Driver.
                                        • SenseHat Support Library

                                        We need to install both. Complete installation instructions are available here.

                                        "},{"location":"tutorials/AD-EdgeAI/#driver-configuration","title":"Driver configuration\u00b6","text":"

                                        We now need to configure the driver to access the sensors on the SenseHat. Move to the \"Driver and Assets\" section of the web UI and create a new driver. We'll call it driver-sensehat.

                                        Then add a new Asset (which we'll call asset-sensehat) to this driver and configure it as per the screenshots below. We'll need a Channel for every sensor we want to access.

                                        Refer to the following table for the driver parameters:

                                        name type value.type resource ACC_X READ FLOAT ACCELERATION_X ACC_Y READ FLOAT ACCELERATION_Y ACC_Z READ FLOAT ACCELERATION_Z GYRO_X READ FLOAT GYROSCOPE_X GYRO_Y READ FLOAT GYROSCOPE_Y GYRO_Z READ FLOAT GYROSCOPE_Z HUMIDITY READ FLOAT HUMIDITY PRESSURE READ FLOAT PRESSURE TEMP_HUM READ FLOAT TEMPERATURE_FROM_HUMIDITY TEMP_PRESS READ FLOAT TEMPERATURE_FROM_PRESSURE

                                        After correctly configuring it you should see the data in the \"Data\" page of the UI.

                                        "},{"location":"tutorials/AD-EdgeAI/#wire-graph","title":"Wire graph\u00b6","text":"

                                        Now that we have our Driver and Cloud Publisher ready we can put everything together with a Kura Wire Graph.

                                        From Kura\u2122 documentation:

                                        The Kura\u2122 Wires feature aims to simplify the development of IoT Edge Computing Applications leveraging reusable configurable components that can be wired together and which, eventually, allows configurable cooperation between these components.

                                        In the dataflow programming model, the application logic is expressed as a directed graph (flow) where each node can have inputs, outputs, and independent processing units. There are nodes that only produce outputs and ones that only consume inputs, which usually represent the start and the end of the flow. The inner-graph nodes process the inputs and produce outputs for downstream nodes. The processing unit of a node executes independently and does not affect the execution of other nodes. Thus, the nodes are highly reusable and portable.

                                        Move to the \"Wire Graph\" section of the UI. We'll need a graph with three components:

                                        • A Timer which will dictate the sample rate at which we will collect data coming from the Sense Hat
                                        • A WireAsset for the Sense Hat driver asset
                                        • A Publisher for the Kapua publisher we created before.

                                        The resulting Wire Graph will look like this:

                                        "},{"location":"tutorials/AD-EdgeAI/#timer","title":"Timer\u00b6","text":"

                                        Configure the timer such that it will poll the SenseHat each second, this can be done by setting the simple.interval to 1.

                                        "},{"location":"tutorials/AD-EdgeAI/#wireasset","title":"WireAsset\u00b6","text":"

                                        Select the driver-sensehat when creating the WireAsset. No further configuration is needed for this component.

                                        "},{"location":"tutorials/AD-EdgeAI/#publisher","title":"Publisher\u00b6","text":"

                                        Create a \"Publisher\" Wire component and select the KapuaSensehatPublisher from the target filter.

                                        Don't forget to press \"Apply\" to start the Wire Graph!

                                        "},{"location":"tutorials/AD-EdgeAI/#collect-the-data","title":"Collect the data\u00b6","text":"

                                        At this point you should see data coming from the Rasperry Pi from the Kapua\u2122 console under the SenseHat topic.

                                        You can download the .csv file directly from the console using the \"Export to CSV\" button.

                                        "},{"location":"tutorials/AD-EdgeAI/#model-building-and-training","title":"Model building and training\u00b6","text":""},{"location":"tutorials/AD-EdgeAI/#overview","title":"Overview\u00b6","text":"

                                        We will now use the data collected in the previous section to train an artificial neural network-based Anomaly Detector of our design. To this end we will use an Autoencoder model. To understand why we choose such model we need to understand how it works. From Wikipedia:

                                        An autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data (unsupervised learning). The encoding is validated and refined by attempting to regenerate the input from the encoding. The autoencoder learns a representation (encoding) for a set of data, typically for dimensionality reduction, by training the network to ignore insignificant data (\u201cnoise\u201d).

                                        Another application for autoencoders is anomaly detection. By learning to replicate the most salient features in the training data [...] the model is encouraged to learn to precisely reproduce the most frequently observed characteristics. When facing anomalies, the model should worsen its reconstruction performance. In most cases, only data with normal instances are used to train the autoencoder; in others, the frequency of anomalies is small compared to the observation set so that its contribution to the learned representation could be ignored. After training, the autoencoder will accurately reconstruct \"normal\" data, while failing to do so with unfamiliar anomalous data. Reconstruction error (the error between the original data and its low dimensional reconstruction) is used as an anomaly score to detect anomalies

                                        In simple terms:

                                        • The Autoencoder is a artificial neural network model that learns how to reconstruct the input data at the output.
                                        • If trained on \"normal\" data, it learns to recontruct only normal data and fails to reconstruct anomalies.
                                        • We can detect anomalies by computing the reconstruction error of the Autoencoder. If the error is above a certain threshold (which we will decide) the input sample is an anomaly.

                                        Why did we choose this approach over others?

                                        • The Autoencoder falls in the \"Unsupervised Learning\" category: it doesn't need labeled data to be trained i.e. we don't need to go through all the dataset and manually label the samples as \"normal\" or \"anomaly\" (Supervised Learning).
                                        • Simpler data collection: we just need to provide it with the \"normal\" data. We don't need to artificially generate anomalies to train it on them.
                                        "},{"location":"tutorials/AD-EdgeAI/#data-processing","title":"Data Processing\u00b6","text":"

                                        We can now work on our .csv file downloaded from Kapua. For demonstration purposes an already available dataset is provided within this repository.

                                        If you're running this notebook through Google Colab you'll need to download the dataset running the cell below:

                                        "},{"location":"tutorials/AD-EdgeAI/#feature-selection","title":"Feature selection\u00b6","text":"

                                        As you might notice there's some information in the dataset we don't care about and are not meaningful for our application:

                                        • ID
                                        • The various timestamps
                                        • assetName which doesn't change

                                        Then we can remove them from the dataset.

                                        "},{"location":"tutorials/AD-EdgeAI/#feature-scaling","title":"Feature scaling\u00b6","text":"

                                        AI models don't perform well when the input numerical attributes have very different scales. As you can see ACC_X, ACC_Y and ACC_Z range from 0 to 1, while the PRESSURE have far higher values.

                                        There are two common ways to address this: normalization and standardization.

                                        Normalization (a.k.a. Min-max scaling) shifts and rescales values so that they end up ranging from 0 to 1. This can be done by subtracting the min value and dividing by the max minus the min.

                                        x' = $\\frac{x - min(x)}{max(x) - min(x)}$

                                        Standardization makes the values of each feature in the data have zero-mean (when subtracting the mean in the numerator) and unit-variance. The general method of calculation is to determine the distribution mean and standard deviation for each feature. Next we subtract the mean from each feature. Then we divide the values (mean is already subtracted) of each feature by its standard deviation.

                                        x' = $\\frac{x - avg(x)}{\\sigma}$

                                        Fortunately for us scikit-learn library provides a function for both of them. In this case we'll use normalization because it works well for this application.

                                        "},{"location":"tutorials/AD-EdgeAI/#train-test-split","title":"Train test split\u00b6","text":"

                                        The only way to know how well a model will generalize to new data points is to try it on new data. To do so we split our data into two sets: the training set and the test set.

                                        To do so we'll use a function from scikit-learn.

                                        "},{"location":"tutorials/AD-EdgeAI/#model-training","title":"Model training\u00b6","text":"

                                        We can now leverage the Keras API of Tensorflow for creating our Autoencoder and then train it on our dataset.

                                        We'll design a neural network architecture such that we impose a bottleneck in the network which forces a compressed knowledge representation of the original input (also called the latent-space representation). If the input features were each independent of one another, this compression and subsequent reconstruction would be a very difficult task. However, if some sort of structure exists in the data (ie. correlations between input features), this structure can be learned and consequently leveraged when forcing the input through the network's bottleneck.

                                        The bottleneck consists of reducing the number of neurons for each layer of the neural network up to a certain point, and then increase the number until the original input number is reached. This will result in a hourglass shape which is typical for the Autoencoders.

                                        "},{"location":"tutorials/AD-EdgeAI/#build-the-autoencoder-model","title":"Build the Autoencoder model\u00b6","text":"

                                        In this example we'll use a basic fully-connected autoencoder but keep in mind that autoencoders can be built with different classes of neural network (i.e. Convolutional Neural Networks, Recurrent Neural Networks etc).

                                        "},{"location":"tutorials/AD-EdgeAI/#model-training","title":"Model training\u00b6","text":"

                                        As we already explained, the autoencoder is a type of artificial neural network used to learn efficient codings of unlabeled data. We'll use that to reconstruct the input at the output. To train an autoencoder we don\u2019t need to do anything fancy, just throw the raw input data at it. Autoencoders are considered an unsupervised learning technique since they don\u2019t need explicit labels to train on but to be more precise they are self-supervised because they generate their own labels from the training data.

                                        To train our neural network we need to have a performance metric to measure how well it is learning to reconstruct the data i.e. our loss function. The loss function in our example, which we need to minimize during our training, is the error between the input data and the data reconstructed by the autoencoder. We'll use the Mean Squared Error.

                                        MSE = $\\frac{1}{n}\\sum_{i=1}^{n}{(Y_i - Y'_i)^2}$

                                        Where:

                                        • $n$: is the number of features (10 in our example)
                                        • $Y_i$: is the original data point i.e. the input of the autoencoder
                                        • $Y'_i$: is the reconstructed data point i.e. the output of the autoencoder

                                        Before starting the training we need to set the hyperparameters). Hyperparameters are parameters whose values control the learning process and determine the values of model parameters that a learning algorithm ends up learning. These are the learning_rate, max_epochs, optimizer and the batch_size you see in the code snippet below. You may ask yourself how to set them, it all comes down to trial and error. Try tweaking them below and see how they affect the learning process...

                                        A good explaination of their meaning can be found in the Keras documentation.

                                        "},{"location":"tutorials/AD-EdgeAI/#model-evaluation","title":"Model evaluation\u00b6","text":"

                                        We now have a model that reconstruct the input at the output... doesn't sounds really useful right?

                                        Let's see it in action. Let's take a sample from the test set and run it through our autoencoder.

                                        "},{"location":"tutorials/AD-EdgeAI/#model-deployment","title":"Model deployment\u00b6","text":"

                                        To deploy our model on the target device we'll leverage Kura\u2122's newly added Nvidia\u2122 Triton Inferece Server integration.

                                        The Nvidia\u2122 Triton Inference Server is an open-source inference service software that enables the user to deploy trained AI models from any framework on GPU or CPU infrastructure. It supports all major frameworks like TensorFlow, TensorRT, PyTorch, ONNX Runtime, and even custom framework backend. With specific backends, it is also possible to run Python scripts, mainly for pre-and post-processing purposes, and exploit the DALI building block for optimized operations.

                                        For installation refer to the official Kura\u2122 and Triton documentation. For the rest of this tutorial we'll assume a Triton container is available on the target device. It can be simply installed with:

                                        docker pull nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3\n

                                        We'll also need to install Kura\u2122's Triton bundles:

                                        • Triton Server Component: for Kura-Triton integration
                                        • AI Wire Component: for making the Triton Inference Server available through the Kura Wires as a Wire component.
                                        "},{"location":"tutorials/AD-EdgeAI/#model-conversion","title":"Model conversion\u00b6","text":"

                                        The first step in using Triton to serve your models is to place one or more models into a model repository i.e. a folder were the model are available for Triton to load. Depending on the type of the model and on what Triton capabilities you want to enable for the model, you may need to create a model configuration for the model. This configuration is a protobuf containing informations about runtime configuration and input/output shape accepted by the model.

                                        For our autoencoder model we'll need three \"models\":

                                        • A Preprocessor for performing the operations described in the \"Data processing\" section (Wire envelop translation, feature selection and scaling)
                                        • The Autoencoder model we exported in the \"Model training\" section
                                        • A Postprocessor for performing the operations described in the \"Model evaluation\" section (Reconstruction error computation)

                                        To simplify the handling of these models and improve inference performance, we'll use an advanced feature of Triton wich is an Ensemble Model. From Triton official documentation:

                                        An ensemble model represents a pipeline of one or more models and the connection of input and output tensors between those models. Ensemble models are intended to be used to encapsulate a procedure that involves multiple models, such as \"data preprocessing -> inference -> data postprocessing\". Using ensemble models for this purpose can avoid the overhead of transferring intermediate tensors and minimize the number of requests that must be sent to Triton.

                                        "},{"location":"tutorials/AD-EdgeAI/#autoencoder","title":"Autoencoder\u00b6","text":"

                                        As seen in the \"Model training\" section, our model is available as a Tensorflow SavedModel which can be simply loaded by the Triton Tensorflow backend. We just need to configure it properly.

                                        We'll start by creating the following folder structure

                                        tf_autoencoder_fp32\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.savedmodel\n\u2502       \u251c\u2500\u2500 assets\n\u2502       \u251c\u2500\u2500 keras_metadata.pb\n\u2502       \u251c\u2500\u2500 saved_model.pb\n\u2502       \u2514\u2500\u2500 variables\n\u2502           \u251c\u2500\u2500 variables.data-00000-of-00001\n\u2502           \u2514\u2500\u2500 variables.index\n\u2514\u2500\u2500 config.pbtxt\n

                                        This can be done by copying the model we saved in the Model Training section:

                                        "},{"location":"tutorials/AD-EdgeAI/#preprocessor","title":"Preprocessor\u00b6","text":"

                                        As discussed in the \"Data processing\" section, before providing the incoming data to the autoencoder, we need to perform feature selection and scaling. In addition to these responsibilites, the Preprocessor will need to perform a sort of serialization of the data to comply to the input shape accepted by the Autoencoder. This is due to how Kura manages the data running on Wires. More details can be found here.

                                        To perform all of this we'll use the Python backend available in Triton.

                                        As described in the previous section we will need to provide the following folder structure:

                                        preprocessor\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.py\n\u2514\u2500\u2500 config.pbtxt\n
                                        "},{"location":"tutorials/AD-EdgeAI/#preprocessor-configuration","title":"Preprocessor Configuration\u00b6","text":"

                                        As discussed in the official Kura documentation:

                                        The AI wire component takes a WireEnvelope as an input, it processes its records and feeds them to the specified preprocessing or inference model.

                                        ...

                                        The models that manage the input and the output must expect a list of inputs such that:

                                        • each input corresponds to an entry of the WireRecord properties
                                        • the entry key will become the input name (e.g. in the case of an asset, the channel name becomes the tensor name)
                                        • input shape will be [1]

                                        Therefore for our input we'll have that each name corresponds to the names we've seen in the Data Collection section. The output needs to correspond to the input accepted by the model (i.e. INPUT0).

                                        name: \"preprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"ACC_X\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"ACC_Y\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\n ...\ninput [\n  {\n    name: \"TEMP_PRESS\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"INPUT0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n
                                        "},{"location":"tutorials/AD-EdgeAI/#preprocessor-model","title":"Preprocessor Model\u00b6","text":"

                                        As we've seen in the Data Processing section the Preprocessor is responsible for scaling the input features and serializing them in the tensor shape expected by the Autoencoder model.

                                        This can be done with the following python script:

                                        import numpy as np\nimport json\n\nimport triton_python_backend_utils as pb_utils\n\n\nclass TritonPythonModel:\n\n    def initialize(self, args):\n        self.model_config = model_config = json.loads(args['model_config'])\n\n        output0_config = pb_utils.get_output_config_by_name(\n            model_config, \"INPUT0\")\n\n        self.output0_dtype = pb_utils.triton_string_to_numpy(\n            output0_config['data_type'])\n\n    def execute(self, requests):\n        output0_dtype = self.output0_dtype\n\n        responses = []\n\n        for request in requests:\n            acc_x      = pb_utils.get_input_tensor_by_name(request, \"ACC_X\").as_numpy()\n            acc_y      = pb_utils.get_input_tensor_by_name(request, \"ACC_Y\").as_numpy()\n            acc_z      = pb_utils.get_input_tensor_by_name(request, \"ACC_Z\").as_numpy()\n            gyro_x     = pb_utils.get_input_tensor_by_name(request, \"GYRO_X\").as_numpy()\n            gyro_y     = pb_utils.get_input_tensor_by_name(request, \"GYRO_Y\").as_numpy()\n            gyro_z     = pb_utils.get_input_tensor_by_name(request, \"GYRO_Z\").as_numpy()\n            humidity   = pb_utils.get_input_tensor_by_name(request, \"HUMIDITY\").as_numpy()\n            pressure   = pb_utils.get_input_tensor_by_name(request, \"PRESSURE\").as_numpy()\n            temp_hum   = pb_utils.get_input_tensor_by_name(request, \"TEMP_HUM\").as_numpy()\n            temp_press = pb_utils.get_input_tensor_by_name(request, \"TEMP_PRESS\").as_numpy()\n\n            out_0 = np.array([acc_y, acc_x, acc_z, pressure, temp_press, temp_hum, humidity, gyro_x, gyro_y, gyro_z]).transpose()\n\n            #                  ACC_Y     ACC_X     ACC_Z    PRESSURE   TEMP_PRESS   TEMP_HUM   HUMIDITY    GYRO_X    GYRO_Y    GYRO_Z\n            min = np.array([-0.132551, -0.049693, 0.759847, 976.001709, 38.724998, 40.220890, 13.003981, -1.937896, -0.265019, -0.250647])\n            max = np.array([ 0.093099, 0.150289, 1.177543, 1007.996338, 46.093750, 48.355824, 23.506138, 1.923712, 0.219204, 0.671759])\n\n            # MinMax scaling\n            out_0_scaled = (out_0 - min)/(max - min)\n\n            # Create output tensor\n            out_tensor_0 = pb_utils.Tensor(\"INPUT0\",\n                                           out_0_scaled.astype(output0_dtype))\n\n            inference_response = pb_utils.InferenceResponse(\n                output_tensors=[out_tensor_0])\n            responses.append(inference_response)\n\n        return responses\n

                                        Here there are two important things to note:

                                        • The template we're using is taken from the Triton documentation and can be found here.
                                        • The MinMax scaling must be the same we used in our training. For illustration purposes we wrote the min and max arrays we found in the Data Processing section but we could have serialized the MinMaxScaler using pickle instead.
                                        "},{"location":"tutorials/AD-EdgeAI/#postprocessor","title":"Postprocessor\u00b6","text":"

                                        As discussed in the \"Data processing\" section, to perform the anomaly detection step we need to compute the Mean Squared Error between the recontructed data and the actual input data. Due to this the configuration of the Postprocessor model will be somewhat more complicated than before: in addition to the output of the Autoencoder model we will need the output of the Preprocessor model.

                                        To perform all of this we'll use the Python backend again.

                                        As described in the previous section we will need to provide the following folder structure:

                                        postprocessor\n\u251c\u2500\u2500 1\n\u2502   \u2514\u2500\u2500 model.py\n\u2514\u2500\u2500 config.pbtxt\n
                                        "},{"location":"tutorials/AD-EdgeAI/#postprocessor-configuration","title":"Postprocessor Configuration\u00b6","text":"
                                        name: \"postprocessor\"\nbackend: \"python\"\n\ninput [\n  {\n    name: \"RECONSTR0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\ninput [\n  {\n    name: \"ORIG0\"\n    data_type: TYPE_FP32\n    dims: [ 1, 10 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY_SCORE0\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY0\"\n    data_type: TYPE_BOOL\n    dims: [ 1 ]\n  }\n]\ninstance_group [{ kind: KIND_CPU }]\n

                                        As we can see we have two inputs and two outputs:

                                        • The first input tensor is the reconstruction performed by the autoencoder model
                                        • The second input tensor is the original data (already scaled and serialized by the Preprocessor model)
                                        • The first output is the anomaly score i.e. the reconstruction error between the original and the reconstructed data.
                                        • The second output is a boolean representing whether the data constitute an anomaly or not

                                        Let's see how this is computed by the Python model.

                                        "},{"location":"tutorials/AD-EdgeAI/#postprocessor-model","title":"Postprocessor Model\u00b6","text":"
                                        import numpy as np\nimport json\n\nimport triton_python_backend_utils as pb_utils\n\ndef z_score(mse):\n    return (mse - MEAN_MSE)/STD_MSE\n\n\nclass TritonPythonModel:\n\n    def initialize(self, args):\n        self.model_config = model_config = json.loads(args['model_config'])\n\n        output0_config = pb_utils.get_output_config_by_name(\n            model_config, \"ANOMALY_SCORE0\")\n        output1_config = pb_utils.get_output_config_by_name(\n            model_config, \"ANOMALY0\")\n\n        self.output0_dtype = pb_utils.triton_string_to_numpy(\n            output0_config['data_type'])\n        self.output1_dtype = pb_utils.triton_string_to_numpy(\n            output1_config['data_type'])\n\n    def execute(self, requests):\n        output0_dtype = self.output0_dtype\n        output1_dtype = self.output1_dtype\n\n        responses = []\n\n        for request in requests:\n            # Get input\n            x_recon = pb_utils.get_input_tensor_by_name(request, \"RECONSTR0\").as_numpy()\n            x_orig = pb_utils.get_input_tensor_by_name(request, \"ORIG0\").as_numpy()\n\n            # Get Mean square error between reconstructed input and original input\n            reconstruction_score = np.mean((x_orig - x_recon)**2, axis=1)\n            \n            #\u00a0Z-Score of Mean square error must be inside [-2; 2]\n            anomaly = np.array([z_score(reconstruction_score) < -2.0 or z_score(reconstruction_score) > 2.0])\n\n            # Create output tensors\n            out_tensor_0 = pb_utils.Tensor(\"ANOMALY_SCORE0\",\n                                           reconstruction_score.astype(output0_dtype))\n            out_tensor_1 = pb_utils.Tensor(\"ANOMALY0\",\n                                           anomaly.astype(output1_dtype))\n\n            inference_response = pb_utils.InferenceResponse(\n                output_tensors=[out_tensor_0, out_tensor_1])\n            responses.append(inference_response)\n\n        return responses\n

                                        As you can see the script is simple:

                                        • It gets the input tensors
                                        • It computes the Mean Squared Error between the inputs (which is what we called the reconstruction error)
                                        • It computes the Z-Score of the MSE computed for the current sample and flags it as an anomaly if it is farther than 2 standard deviations away from the average MSE.

                                        Note: MEAN_MSE and STD_MSE are the mean value and the standard deviation of the Mean Squared Error computed on the test set and correspond to the reconstruction_scores_pd.mean() and reconstruction_scores_pd.std() we used in the previous section. We didn't set them as they change for every training performed on the Autoencoder. Be sure to set it to their proper values before trying this model on the Triton server!

                                        "},{"location":"tutorials/AD-EdgeAI/#ensemble-model","title":"Ensemble model\u00b6","text":"

                                        To make things easier for ourselves and improve performance we'll consolidate the AI pipeline into an Ensemble Model.

                                        We will need to provide the following folder structure:

                                        ensemble_pipeline\n\u251c\u2500\u2500 1\n\u2514\u2500\u2500 config.pbtxt\n

                                        Note that the 1 folder is empty. The ensemble model essentially describe how to connect the models that belong to the processing pipeline.

                                        Therefore we'll need to focus on the configuration only.

                                        name: \"ensemble_pipeline\"\nplatform: \"ensemble\"\nmax_batch_size: 0\ninput [\n  {\n    name: \"ACC_X\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\ninput [\n  {\n    name: \"ACC_Y\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\n ...\ninput [\n  {\n    name: \"TEMP_PRESS\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY_SCORE0\"\n    data_type: TYPE_FP32\n    dims: [ 1 ]\n  }\n]\noutput [\n  {\n    name: \"ANOMALY0\"\n    data_type: TYPE_BOOL\n    dims: [ 1 ]\n  }\n]\nensemble_scheduling {\n  step [\n    {\n      model_name: \"preprocessor\"\n      model_version: -1\n      input_map{\n          key: \"ACC_X\"\n          value: \"ACC_X\"\n      }\n      input_map{\n          key: \"ACC_Y\"\n          value: \"ACC_Y\"\n      }\n       ...\n      input_map{\n          key: \"TEMP_PRESS\"\n          value: \"TEMP_PRESS\"\n      }\n      output_map {\n        key: \"INPUT0\"\n        value: \"preprocess_out\"\n      }\n    },\n    {\n      model_name: \"tf_autoencoder_fp32\"\n      model_version: -1\n      input_map {\n        key: \"INPUT0\"\n        value: \"preprocess_out\"\n      }\n      output_map {\n        key: \"OUTPUT0\"\n        value: \"autoencoder_output\"\n      }\n    },\n    {\n      model_name: \"postprocessor\"\n      model_version: -1\n      input_map {\n        key: \"RECONSTR0\"\n        value: \"autoencoder_output\"\n      }\n      input_map {\n        key: \"ORIG0\"\n        value: \"preprocess_out\"\n      }\n      output_map {\n        key: \"ANOMALY_SCORE0\"\n        value: \"ANOMALY_SCORE0\"\n      }\n      output_map {\n        key: \"ANOMALY0\"\n        value: \"ANOMALY0\"\n      }\n    }\n  ]\n}\n

                                        The configuration is split in two main parts:

                                        • The first is the usual configuration we've seen before: we describe what are the input and the output of our model. In this case the input will correspond to the input of the first model of the pipeline (the Preprocessor) and the output to the output of the last model of the pipeline (the Postprocessor)
                                        • The second part describe how to map the input/output of the models within the pipeline

                                        To better visualize the configuration we can look at the graph below.

                                        "},{"location":"tutorials/AD-EdgeAI/#conversion-results","title":"Conversion results\u00b6","text":"

                                        At this point we should have a folder structure that looks like this:

                                        models\n\u251c\u2500\u2500 ensemble_pipeline\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u251c\u2500\u2500 postprocessor\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u251c\u2500\u2500 preprocessor\n\u2502\u00a0\u00a0 \u251c\u2500\u2500 1\n\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.py\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 config.pbtxt\n\u2514\u2500\u2500 tf_autoencoder_fp32\n    \u251c\u2500\u2500 1\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 model.savedmodel\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 assets\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 keras_metadata.pb\n    \u2502\u00a0\u00a0     \u251c\u2500\u2500 saved_model.pb\n    \u2502\u00a0\u00a0     \u2514\u2500\u2500 variables\n    \u2502\u00a0\u00a0         \u251c\u2500\u2500 variables.data-00000-of-00001\n    \u2502\u00a0\u00a0         \u2514\u2500\u2500 variables.index\n    \u2514\u2500\u2500 config.pbtxt\n
                                        "},{"location":"tutorials/AD-EdgeAI/#kura-deployment","title":"Kura Deployment\u00b6","text":"

                                        We can now move our pipeline to the target device for inference on the edge.

                                        We want to perform anomaly detection in real time, directly within the edge device, using the same data we used to collect for our training.

                                        "},{"location":"tutorials/AD-EdgeAI/#triton-component-configuration","title":"Triton component configuration\u00b6","text":"

                                        To do so we need to copy the models folder on the target device. For this example we'll use the /home/pi/models path.

                                        We can now move to the Kura web UI and create a new Triton Server Container Service component instance. The complete documentation can be found here.

                                        In this example we'll call it TritonContainerService.

                                        Then we'll need to configure it to run our models. Move to the TritonContainerService configuration interface and set the following parameters:

                                        • Image name/Image tag: use the name and tag of the Triton container image you installed. We're using nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3 in this example.
                                        • Local model repository path: in our example is /home/pi/models
                                        • Inference Models: we'll need to load all the models of the pipeline so: preprocessor,postprocessor,tf_autoencoder_fp32,ensemble_pipeline
                                        • Optional configuration for the local backends: tensorflow,version=2 since Tensorflow 2 is the only available Tensorflow backend in the Triton container image we're using.

                                        You can leave everything else as default.

                                        Once you press the \"Apply\" button Kura will create a new container from the Triton image we set and spin up the service with our models loaded.

                                        pi@raspberrypi:~ $ docker ps\nCONTAINER ID   IMAGE                                              COMMAND                  CREATED          STATUS          PORTS                                                                                                                             NAMES\n4deae2857b6f   nvcr.io/nvidia/tritonserver:22.07-tf2-python-py3   \"tritonserver --mode\u2026\"   13 seconds ago   Up 11 seconds   0.0.0.0:4000->8000/tcp, :::4000->8000/tcp, 0.0.0.0:4001->8001/tcp, :::4001->8001/tcp, 0.0.0.0:4002->8002/tcp, :::4002->8002/tcp   tritonserver-kura\n

                                        Note: if no container is created check that the \"Container Orchestration Service\" is enabled in the Kura UI. Full documentation for the service can be found here.

                                        Note: if you see an error in the logs like \"Internal: Unable to initialize shared memory key 'triton_python_backend_shm_region_2' to requested size (67108864 bytes). If you are running Triton inside docker, use '--shm-size' flag to control the shared memory region size. Each Python backend model instance requires at least 64MBs of shared memory.\", you can update the default shared memory size allocated by the Docker daemon. Go to /etc/docker/daemon.json, set \"default-shm-size\": \"200m\" and restart the Docker daemon with: sudo systemctl restart docker.

                                        "},{"location":"tutorials/AD-EdgeAI/#wire-graph","title":"Wire Graph\u00b6","text":"

                                        Finally we can move to the \"Wire Graph\" UI and create the AI component (in the Emitters/Receiver menu) for interfacing with the Triton instance we just created. We'll call it Triton in this example.

                                        We just need to change two parameter in the configuration:

                                        • InferenceEngineService Target Filter: we need to select the TritonContainerService we created at the step above
                                        • inference.model.name: Since we're using an ensemble pipeline we need only that as our inference model.

                                        The resulting wire graph is the following:

                                        And that's it! We should now see the anomaly detection results coming to Kapua in addition to the SenseHat data.

                                        "},{"location":"tutorials/AD-EdgeAI/#complete-example","title":"Complete Example\u00b6","text":"

                                        A similar but more complete example of the feature presented in this notebook is available in the official Kura\u2122 repository containing all the code and the configuration needed to make it work.

                                        Give it a try!

                                        "}]} \ No newline at end of file diff --git a/docs-develop/sitemap.xml.gz b/docs-develop/sitemap.xml.gz index 0a870f87702b7f87a28000d191c366bc2a305437..0f6200ed7398b29a30af1dbd432c20d98c6553f1 100644 GIT binary patch delta 12 Tcmb=gXOr*d;PAXZk*yK{7^wsI delta 12 Tcmb=gXOr*d;JA5rB3mT@8*v1M